Cómo Funcionan los Generadores Digitales de Ruido

Los Cimientos: Generación de Números Pseudoaleatorios

En mi trabajo construyendo el motor de ruido de WhiteNoise.top, he dedicado cientos de horas a optimizar el flujo que convierte la aleatoriedad matemática en audio convincente. Todo generador de ruido digital comienza con el mismo componente fundamental: un generador de números pseudoaleatorios, o PRNG. Un PRNG es un algoritmo que produce una secuencia determinista de números que aparenta ser aleatoria según pruebas estadísticas. La verdadera aleatoriedad proveniente de fuentes de entropía de hardware es demasiado lenta e impredecible para el audio en tiempo real, por lo que los PRNGs proporcionan la base práctica de toda generación digital de ruido.

El PRNG más básico adecuado para trabajo de audio es el generador lineal congruencial (LCG), que calcula cada valor como una función lineal del valor anterior, módulo una constante grande. Aunque es rápido, los LCGs tienen debilidades conocidas: exhiben correlaciones entre muestras sucesivas que pueden producir artefactos audibles en señales de ruido. En mis primeros prototipos, usé un LCG simple y podía escuchar patrones periódicos débiles en la salida al escuchar con auriculares de alta calidad. Estos patrones aparecían como picos espectrales estrechos en el análisis FFT, confirmando que las debilidades estadísticas del PRNG se filtraban al dominio de audio.

Para ruido de calidad de producción, cambié al algoritmo xorshift128+, que es el PRNG utilizado internamente por la mayoría de los motores JavaScript para Math.random(). Tiene un periodo de 2 elevado a la potencia de 128 menos 1, excelentes propiedades estadísticas y un costo computacional insignificante. Cuando ejecuto la batería de pruebas de aleatoriedad TestU01 en su salida, pasa todas las suites SmallCrush y Crush. El resultado es una señal de ruido libre de artefactos audibles, con un espectro plano verificado dentro de 0,3 dB en todo el rango audible.

De Números Aleatorios a Muestras de Audio

Un PRNG produce números, pero un sistema de audio necesita muestras de forma de onda con amplitudes y frecuencias de muestreo específicas. El proceso de conversión implica escalado, conformación de distribución y gestión de buffers. En mi implementación, el PRNG genera enteros sin signo de 32 bits, que luego normalizo a valores de punto flotante en el rango de menos uno a más uno. Este es el rango de amplitud estándar para audio digital en la Web Audio API y la mayoría de los demás frameworks de audio.

La distribución de los valores aleatorios importa para el carácter del ruido. Una distribución uniforme, donde todos los valores en el rango son igualmente probables, produce ruido con una distribución de amplitud ligeramente diferente a la distribución gaussiana que caracteriza el ruido térmico en circuitos analógicos. En la práctica, la diferencia es sutil: el ruido blanco uniforme tiene un factor de cresta de aproximadamente 4,8 dB, mientras que el ruido blanco gaussiano tiene un factor de cresta teóricamente ilimitado, aunque en la práctica es de alrededor de 12 dB para longitudes de buffer típicas. Para la mayoría de las aplicaciones, la distribución uniforme es perfectamente aceptable y computacionalmente más simple.

Cuando necesito ruido con distribución gaussiana para aplicaciones que lo requieren, utilizo la transformada de Box-Muller, que convierte pares de números aleatorios con distribución uniforme en pares de valores con distribución gaussiana. La sobrecarga computacional es modesta, aproximadamente duplicando el costo por muestra, pero el resultado es una señal de ruido que se aproxima más a las estadísticas de amplitud de fuentes de ruido analógicas. En pruebas de escucha, la diferencia entre ruido blanco uniforme y gaussiano es casi inaudible, pero la variante gaussiana rinde mejor en ciertas aplicaciones de procesamiento de señales donde las estadísticas de amplitud importan.

Conformación Espectral: Convirtiendo Blanco en Colores

El ruido blanco es la materia prima a partir de la cual se crean todos los demás colores de ruido. La transformación se logra mediante filtrado digital, aplicando una curva de ganancia dependiente de la frecuencia al espectro del ruido blanco. En mi motor de ruido, implemento la conformación espectral usando tanto filtros IIR (respuesta infinita al impulso) como FIR (respuesta finita al impulso), cada uno con ventajas distintas.

Para el ruido rosa (pendiente de menos tres decibelios por octava), utilizo el algoritmo Voss-McCartney como mi método principal. Este algoritmo mantiene múltiples generadores de números aleatorios independientes que se actualizan a diferentes velocidades: uno se actualiza cada muestra, otro cada dos muestras, otro cada cuatro muestras, y así sucesivamente. Las salidas se suman para producir una señal cuyo espectro se aproxima a la pendiente ideal del ruido rosa. En mi implementación, utilizo 16 capas de octava, lo que proporciona una conformación espectral precisa desde por debajo de 1 Hz hasta por encima de 20 kHz. El error relativo a la pendiente ideal es inferior a 0,5 dB en todo el rango audible.

Para el ruido marrón (pendiente de menos seis decibelios por octava), utilizo integración simple: cada muestra de salida es la suma de la salida anterior y una nueva muestra de ruido blanco, escalada por un coeficiente pequeño. Matemáticamente, esto es un filtro pasa-bajos IIR de primer orden con un polo muy cercano a uno en el círculo unitario. El desafío con este enfoque es la deriva de DC: la suma acumulada puede alejarse de cero con el tiempo, eventualmente superando el umbral de recorte. Lo resuelvo añadiendo un filtro pasa-altos de primer orden muy suave a 5 Hz, que restringe la deriva sin afectar audiblemente el espectro por encima de 20 Hz.

Para formas espectrales personalizadas solicitadas por usuarios avanzados, utilizo un ecualizador paramétrico construido con secciones de filtros biquad de segundo orden en cascada. Cada sección implementa un tipo de filtro estándar (pico, estante bajo, estante alto o muesca) con parámetros ajustables de frecuencia, ganancia y ancho de banda. Al encadenar de cuatro a seis secciones, puedo aproximar virtualmente cualquier forma espectral suave que un usuario pueda desear, desde inclinaciones suaves hasta contornos complejos de múltiples bandas.

Generación en Tiempo Real con la Web Audio API

La Web Audio API, disponible en todos los navegadores modernos, proporciona la infraestructura para generar y reproducir ruido en tiempo real sin ningún procesamiento del lado del servidor. En mi implementación en WhiteNoise.top, utilizo tres componentes principales de la Web Audio API: el AudioContext, el ScriptProcessorNode (o su reemplazo moderno, el AudioWorkletNode) y el BiquadFilterNode.

El AudioWorkletNode es donde ocurre la generación principal. Registro un AudioWorkletProcessor personalizado que se ejecuta en un hilo separado del hilo principal del navegador, asegurando que las interacciones de la interfaz no causen fallos de audio. El método process() del procesador se llama repetidamente con un buffer de salida, típicamente 128 muestras por llamada a 44,1 kHz. Dentro de este método, genero muestras de ruido blanco usando el PRNG, aplico cualquier conformación espectral y escribo los resultados en el buffer de salida. Todo el flujo se ejecuta en aproximadamente 0,01 milisegundos por buffer en hardware moderno, muy dentro del plazo de aproximadamente tres milisegundos para una reproducción sin fallos.

La gestión de buffers es crítica para evitar artefactos audibles. Si el procesador tarda demasiado en llenar un buffer, la salida de audio sufre una sub-ejecución, produciendo un clic o un chasquido. En mis pruebas de estrés, presiono el sistema añadiendo carga computacional al hilo principal (como manipulación pesada del DOM o análisis de JSON grande) y verifico que el hilo del audio worklet sigue entregando buffers a tiempo. El aislamiento de hilos proporcionado por la AudioWorklet API es esencial para esta robustez; el antiguo ScriptProcessorNode se ejecutaba en el hilo principal y era vulnerable a fallos durante la ejecución pesada de JavaScript.

También utilizo el BiquadFilterNode integrado en la Web Audio API para la conformación espectral cuando el filtro deseado es de un tipo estándar. Estos nodos están implementados en código nativo optimizado, lo que los hace significativamente más rápidos que implementaciones equivalentes en JavaScript. Para la generación de ruido rosa en configuraciones más simples, pongo en cascada varios BiquadFilterNodes configurados como filtros pasa-bajos con frecuencias y factores Q cuidadosamente elegidos para aproximar la pendiente ideal de menos tres decibelios por octava.

Optimizaciones y Consideraciones Prácticas

La optimización del rendimiento es una preocupación constante en la generación de audio en tiempo real. En mi proceso de desarrollo, he implementado varias técnicas para maximizar la eficiencia sin sacrificar la calidad. La primera es la precomputación: para colores de ruido que no cambian dinámicamente, genero un buffer grande de ruido (típicamente 10 segundos a 44,1 kHz, o 441.000 muestras) durante la inicialización y lo reproduzco en bucle durante la reproducción. Para evitar que el bucle sea audible, utilizo un fundido cruzado en el límite del bucle, mezclando las últimas 4.096 muestras del buffer con las primeras 4.096 muestras usando una ventana de coseno elevado. El bucle resultante es perceptualmente imperceptible.

La segunda optimización es el procesamiento de estilo SIMD dentro de JavaScript. Aunque JavaScript no proporciona instrucciones SIMD explícitas en el contexto del AudioWorklet, estructuro mis bucles internos para procesar muestras en grupos de cuatro, permitiendo que el compilador JIT del motor JavaScript aplique auto-vectorización. En mis pruebas de referencia, este enfoque produce una mejora de velocidad del 15 al 25 por ciento comparado con procesar una muestra a la vez, dependiendo del motor del navegador.

Una tercera consideración es la gestión de memoria. Asignar y liberar memoria durante el procesamiento de audio puede desencadenar pausas de recolección de basura, que causan fallos audibles. Pre-asigno todos los buffers durante la inicialización y los reutilizo durante toda la sesión. No se crean objetos dentro del método process(), y todos los valores intermedios se almacenan en arrays tipados pre-asignados. Esta disciplina elimina completamente los artefactos de audio relacionados con la recolección de basura.

El consumo de energía es otra consideración práctica, especialmente para dispositivos móviles. En mis pruebas en smartphones, he descubierto que la carga de CPU de un generador de ruido simple es insignificante, típicamente menos del uno por ciento. Sin embargo, mantener la salida de audio activa impide que el dispositivo entre en estados de suspensión profunda, lo que puede agotar la batería durante sesiones prolongadas. Mitigo esto ofreciendo una función de temporizador que detiene el generador después de una duración especificada por el usuario, y usando la Page Visibility API para pausar la generación cuando la pestaña del navegador no está en primer plano.

Validación de la Calidad de Salida del Generador

Un generador de ruido es tan bueno como la calidad de su salida, y la calidad debe verificarse mediante medición objetiva. En mi proceso de aseguramiento de calidad, ejecuto cada configuración de generador a través de una suite de pruebas automatizadas antes de lanzarla a los usuarios. La primera prueba es la planitud espectral: capturo una muestra de 60 segundos de ruido blanco, calculo la magnitud FFT promediada y verifico que ningún bin de frecuencia se desvíe más de 1 dB de la media. Para ruido conformado, comparo el espectro medido con la curva objetivo y verifico que la desviación sea inferior a 0,5 dB.

La segunda prueba es la distribución de amplitud. Calculo un histograma de los valores de las muestras y lo comparo con la distribución esperada, ya sea uniforme o gaussiana, usando la prueba de Kolmogorov-Smirnov. Un valor p superior a 0,05 indica que la distribución coincide con la forma esperada al nivel de confianza del 95 por ciento.

La tercera prueba es la detección de periodicidad. Calculo la función de autocorrelación de una muestra de 10 segundos y verifico que ningún desfase distinto de cero tenga un coeficiente de correlación que exceda el máximo teórico para un proceso aleatorio de esa longitud. Los patrones periódicos en el PRNG aparecerían como picos en la función de autocorrelación, incluso si no son audibles, e indicarían un defecto en el generador que podría causar problemas en aplicaciones de procesamiento de señales.

Finalmente, realizo pruebas de escucha subjetiva con auriculares, verificando clics, chasquidos, artefactos tonales y variaciones en nivel o timbre durante períodos de reproducción prolongados. Las pruebas automatizadas detectan la mayoría de los problemas, pero el oído humano sigue siendo el juez definitivo de la calidad del audio, y me aseguro de escuchar personalmente cada variante del generador antes de que se publique en la plataforma.

Referencias

Preguntas Frecuentes

¿Puede un generador de ruido digital producir una salida verdaderamente aleatoria?

No. Los generadores de ruido digitales usan generadores de números pseudoaleatorios (PRNGs) que producen secuencias deterministas. Sin embargo, un PRNG bien diseñado produce una salida estadísticamente indistinguible de la verdadera aleatoriedad para todas las aplicaciones de audio prácticas.

¿Por qué la Web Audio API usa AudioWorklet en lugar de ScriptProcessorNode?

AudioWorklet se ejecuta en un hilo separado del hilo principal del navegador, evitando que las operaciones de la interfaz causen fallos de audio. ScriptProcessorNode se ejecutaba en el hilo principal y era propenso a interrupciones durante la ejecución pesada de JavaScript. ScriptProcessorNode está ahora obsoleto.

¿Qué causa clics o chasquidos en un generador de ruido?

Los clics y chasquidos son causados típicamente por sub-ejecuciones de buffer, donde el generador no puede llenar el buffer de salida a tiempo, o por pausas de recolección de basura en JavaScript. La gestión adecuada de buffers y la pre-asignación de memoria eliminan estos artefactos.

¿Cuánta CPU usa un generador de ruido?

Un generador de ruido simple típicamente usa menos del uno por ciento de CPU en hardware moderno. La principal preocupación de rendimiento es mantener una temporización consistente para prevenir fallos de audio, no la carga total de CPU.

¿Puedo poner en bucle una muestra de ruido en lugar de generarla en tiempo real?

Sí, pero necesitas aplicar un fundido cruzado en el punto de bucle para prevenir un clic audible. Un fundido cruzado de coseno elevado de aproximadamente 4.096 muestras a 44,1 kHz crea un bucle perceptualmente imperceptible.

Leo Chen

Leo Chen es desarrollador de herramientas y entusiasta del audio, dedicado a crear herramientas prácticas de sonido y productividad en línea.