Cuando diseñamos PomoTask (Pomodoro Flow), nos propusimos un reto de ingeniería concreto: ofrecer a los usuarios paisajes sonoros continuos de alta fidelidad (lluvia suave, ruido marrón de concentración profunda y olas del océano) además de campanas armónicas de notificación, logrando que la aplicación cargue en menos de 100 milisegundos y funcione 100% offline sin obligar al navegador a descargar decenas de megabytes en archivos .mp3 o .ogg.
La solución reside en el procesamiento digital de señales (DSP) en tiempo real mediante la especificación Web Audio API del W3C.
1. Por qué la Síntesis Procedural Supera a los Loops de Audio Estáticos
Las aplicaciones tradicionales de enfoque suelen reproducir un archivo MP3 de 30 o 60 segundos en bucle (<audio loop>). Este método tiene tres desventajas notables:
- Fatiga auditiva por repetición y corte de bucle: El cerebro humano es extraordinariamente hábil detectando patrones repetitivos; tras pocos minutos, el usuario percibe el punto exacto donde el archivo reinicia o escucha artefactos de compresión.
- Consumo de red y almacenamiento en caché: Tres pistas de alta calidad de 5 minutos pesan entre 25 MB y 45 MB, penalizando los tiempos de carga en conexiones móviles.
- Rigidez acústica: Un archivo grabado no permite modificar dinámicamente la densidad espectral del sonido sin distorsionar su tono.
2. Matemáticas del Ruido Blanco, Rosa y Marrón (Red/Brownian Noise)
En procesamiento de señales, el ruido blanco posee una densidad espectral de potencia plana (igual energía en todas las frecuencias), lo que suena estridente al oído humano porque cada octava superior duplica el rango de frecuencias. En cambio, el ruido marrón (o ruido browniano) disminuye su potencia a razón de 6 dB por octava ($1/f^2$) a medida que aumenta la frecuencia, produciendo un sonido cálido, grave y envolvente ideal para enmascarar distracciones ambientales.
En lugar de calcular transformadas de Fourier costosas en cada muestra, podemos integrar muestras aleatorias mediante un filtro recursivo de primer orden (integrador con fuga o leaky integrator) al llenar un AudioBuffer:
function createBrownNoiseNode(audioCtx, durationSeconds = 5) {
const sampleRate = audioCtx.sampleRate;
const bufferSize = sampleRate * durationSeconds;
const buffer = audioCtx.createBuffer(1, bufferSize, sampleRate);
const data = buffer.getChannelData(0);
let lastOut = 0.0;
for (let i = 0; i < bufferSize; i++) {
const white = Math.random() * 2 - 1;
// Integración browniana con amortiguación para evitar saturación (clipping)
data[i] = (lastOut + (0.02 * white)) / 1.02;
lastOut = data[i];
data[i] *= 3.5; // Compensación de ganancia RMS
}
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.loop = true;
// Filtro paso-bajo adicional para suavizar frecuencias superiores a 450 Hz
const lowpass = audioCtx.createBiquadFilter();
lowpass.type = 'lowpass';
lowpass.frequency.value = 450;
source.connect(lowpass);
return { source, outputNode: lowpass };
}
3. Modelado Acústico de Olas del Océano mediante Modulación LFO
¿Cómo se transforma un generador de ruido estático en el sonido orgánico del mar rompiendo en la orilla? En acústica física, una ola es ruido de banda ancha cuya amplitud y frecuencia de corte varían cíclicamente a baja frecuencia (aproximadamente entre 0.08 Hz y 0.15 Hz, es decir, un ciclo cada 7 a 12 segundos).
Conectando un oscilador de baja frecuencia (LFO) al parámetro frequency de un filtro BiquadFilterNode (pasa-banda o pasa-bajo) y al nodo de ganancia GainNode, logramos que el espectro se abra cuando la ola "sube" y se atenúe suavemente cuando la espuma se retira.
4. Síntesis Aditiva para Campanas de Fin de Sesión
Cuando un bloque de estudio finaliza, una alarma estridente rompe el estado de flujo cognitivo. Mediante síntesis aditiva, superponemos tres o cuatro osciladores sinusoidales en relaciones armónicas e inarmónicas suaves (por ejemplo, una frecuencia fundamental de 528 Hz junto a parciales en 1056 Hz y 1584 Hz) aplicando una envolvente ADSR exponencial mediante gain.exponentialRampToValueAtTime(0.0001, now + 2.8).
El resultado es el timbre cristalino de un cuenco tibetano o campana acústica generado con menos de 40 líneas de código JavaScript nativo y cero peticiones de red.