1. 核心概念与技术背景

在 Web 前端开发中,环境白噪音、专注声效与睡眠辅助工具通常采用挂载音频文件(如 .mp3, .ogg, .wav)的形式。然而,依赖静态音频文件存在以下局限:

  1. 体积与带宽占用:长时间无缝音频文件体积常达数十兆字节,初次加载延迟显著;
  2. 循环缝隙 (Loop Seam):传统静态音频在端点回放时容易产生微小卡顿;
  3. 灵活性欠缺:无法通过实时参数动态调制风速、浪涌周期、滤波器衰减等物理参数。

Web 网页音频接口 (Web Audio API) 提供了一种在客户端通过数学公式进行过程化声音合成 (Procedural Audio Synthesis) 的高效方案。无需下载任何静态音频切片,纯依靠 JavaScript 算法填充 AudioBuffer,配合双二阶滤波器 (BiquadFilterNode) 与低频振荡器 (OscillatorNode, LFO),即可实现极低内存开销、无限长、零请求开销的环境声学模拟。


2. 基础颜色噪声 (Color Noise) 数学建模

音频工程中的颜色噪声由功率谱密度 (Power Spectral Density, PSD) 的频响斜率定义:

2.1 白噪声 (White Noise)

  • 物理特性:在全频带范围内功率谱密度恒定,能量均匀分布,每倍频程能量增加 $3\text{ dB}$。
  • 合成算法:直接使用均匀分布随机数填充在 $[-1.0, 1.0]$ 区间:

    function generateWhiteNoise(audioCtx, seconds = 2) {
    const length = audioCtx.sampleRate * seconds;
    const buffer = audioCtx.createBuffer(1, length, audioCtx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < length; i++) {
      data[i] = Math.random() * 2 - 1;
    }
    return buffer;
    }

2.2 粉红噪声 (Pink Noise / $1/f$ 噪声)

  • 物理特性:功率谱密度与频率成反比,每倍频程能量衰减 $3\text{ dB}$($-3\text{ dB/octave}$),贴近人类耳蜗感知平衡,在自然雨声、瀑布中极为普遍。
  • 合成算法:采用经典的 保罗·凯莱特 (Paul Kellet) 三阶有限冲激响应/无限冲激响应逼近算法,通过三个加权低通极点对白噪声进行平滑滤波:

    function generatePinkNoise(audioCtx, seconds = 2) {
    const length = audioCtx.sampleRate * seconds;
    const buffer = audioCtx.createBuffer(1, length, audioCtx.sampleRate);
    const data = buffer.getChannelData(0);
    let b0 = 0, b1 = 0, b2 = 0;
    for (let i = 0; i < length; i++) {
      const white = Math.random() * 2 - 1;
      b0 = 0.99765 * b0 + white * 0.099;
      b1 = 0.96300 * b1 + white * 0.2965;
      b2 = 0.57000 * b2 + white * 1.0526;
      data[i] = (b0 + b1 + b2 + white * 0.1848) * 0.18;
    }
    return buffer;
    }

2.3 棕色噪声 / 红噪声 (Brownian Noise / Red Noise / $1/f^2$ 噪声)

  • 物理特性:每倍频程能量衰减 $6\text{ dB}$($-6\text{ dB/octave}$),低频能量极高,类似于远雷声或深沉的机械轰鸣。
  • 合成算法:通过一阶泄漏积分器 (Leaky Integrator) 对白噪声进行连续积分,模拟布朗粒子的随机游走位移:

    function generateBrownNoise(audioCtx, seconds = 2) {
    const length = audioCtx.sampleRate * seconds;
    const buffer = audioCtx.createBuffer(1, length, audioCtx.sampleRate);
    const data = buffer.getChannelData(0);
    let lastOutput = 0.0;
    for (let i = 0; i < length; i++) {
      const white = Math.random() * 2 - 1;
      lastOutput = (lastOutput + 0.02 * white) / 1.02;
      data[i] = lastOutput * 3.5; // 增益补偿
    }
    return buffer;
    }

3. 复杂自然环境音的动态声学调制管线

借助 Web Audio API 的音频处理节点图 (Audio Node Graph),以合成噪声源为基础串联滤波器和动态调制器,可模拟真实自然声:

[AudioBufferSourceNode (噪声源)]
         │
         ▼
[BiquadFilterNode (滤波整形)]
         │
         ▼
[GainNode (音量/潮汐振幅控制)] ◄─── [OscillatorNode (LFO 低频正弦调制)]
         │
         ▼
[AudioDestinationNode (系统扬声器输出)]

3.1 降雨声 (Rain Simulation)

  • 实现手段:白噪声通过低通滤波器 (Lowpass Filter),截止频率设置在 $2000 \sim 2500\text{ Hz}$。
  • 声学原理:滤除白噪声中刺耳的高频段,保留雨滴溅落形成的宽频浑厚衰减。

    const filter = audioCtx.createBiquadFilter();
    filter.type = 'lowpass';
    filter.frequency.value = 2500;
    sourceNode.connect(filter);
    filter.connect(gainNode);

3.2 风扇嗡鸣声 (Fan Simulation)

  • 实现手段:白噪声通过带通滤波器 (Bandpass Filter),中心频率设置在 $180 \sim 220\text{ Hz}$,品质因数 $Q$ 设为 $0.5$。
  • 声学原理:压制高低两端频段,突出扇叶气流涡旋与转子旋转产生的窄带机械空气共振。

3.3 海浪潮汐起伏声 (Ocean Wave Simulation)

  • 实现手段:低通滤波噪声(截止频率约 $1200\text{ Hz}$)接入主增益节点,同时构建一个频率为 $0.1\text{ Hz}$(周期约 10 秒)的低频振荡器 (Low-Frequency Oscillator, LFO) 直接控制主增益节点的增益输入参数 (gain.gain)。
  • 代码范式:

    // 1. 低通滤波
    const filter = audioCtx.createBiquadFilter();
    filter.type = 'lowpass';
    filter.frequency.value = 1200;
    
    // 2. 主输出增益
    const mainGain = audioCtx.createGain();
    mainGain.gain.value = 0.4;
    
    // 3. 构建 LFO 周期性呼吸调制
    const lfo = audioCtx.createOscillator();
    lfo.frequency.value = 0.1; // 10秒一个波峰波谷循环
    const lfoGain = audioCtx.createGain();
    lfoGain.gain.value = mainGain.gain.value * 0.6; // 调制深度
    
    lfo.connect(lfoGain);
    lfoGain.connect(mainGain.gain); // 注入 AudioParam
    lfo.start();
    
    sourceNode.connect(filter);
    filter.connect(mainGain);
    mainGain.connect(audioCtx.destination);

4. 关键踩坑与工程避坑指南

4.1 陷阱一:AudioContext 实例化上限与资源耗尽

  • 现象:在按钮点击、倒计时结束触发提示音时,若每次都执行 new AudioContext(),在 Chrome / Safari 运行一段时间后将抛出异常:The number of hardware contexts provided (6/32) is limited,导致页面声音彻底哑火。
  • 正解:全应用维护单例 AudioContext,配合用户首次交互手势(Click / Touch)触发 audioCtx.resume() 解除浏览器的自动播放策略限制。

4.2 陷阱二:循环播放首尾跳变点击杂音 (Loop Glitch Click)

  • 现象:当生成一段 $2\text{ s}$ 的噪声 Buffer 并设置 source.loop = true 时,由于伪随机生成的开头采样与末尾采样存在幅值突变,循环播放时每 2 秒耳机中会出现微小的“咔哒”底噪。
  • 正解:

    1. 延长 Buffer 长度至 $5 \sim 10\text{ s}$;
    2. 在 Buffer 的首尾两端各 $50\text{ ms}$ 应用半升余弦窗 (Hann / Cosine Window) 进行平滑淡入淡出加权,确保首尾采样值平滑过渡至 0。

4.3 陷阱三:断开连接遗漏导致内存驻留

  • 现象:停止播放时仅调用 source.stop(),但未调用 filter.disconnect() 与 gain.disconnect(),导致 Web Audio 渲染线程节点树持续占用内存。
  • 正解:在停止或切换声音时,统一执行 node.disconnect() 并置空引用。

5. 适用场景与演进方向

  • 极简独立单页:冥想助眠、番茄钟、极客专注工具、数字药丸/解压类产品;
  • 自适应动态音频:根据用户呼吸传感器、心率或滚轮滑动速度,实时调整 LFO 频率与滤波器截止频率;
  • AudioWorklet 演进:对于高保真、连续实时的声学合成,可进一步采用现代 AudioWorkletNode 在独立音频渲染线程中通过纯数学物理公式实时流式生成,彻底避免大数组 AudioBuffer 内存占用。

本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Frontend