5、Web Audio API 音频处理与常量源节点应用

Web Audio API 音频处理与常量源节点应用

1. 音频参数设置与交互

在音频处理中,我们常常需要对音频的各种参数进行设置和交互。例如,通过以下代码可以设置音频的循环、循环起始和结束位置:

loop.oninput = () => source.loop = loop.checked;
loopstart.oninput = () => source.loopStart = loopstart.value;
loopend.oninput = () => source.loopEnd = loopend.value;

每次用户点击开始按钮时,会创建一个新的缓冲区源节点,因为 AudioBufferSourceNode 只能启动一次。需要注意的是, playbackRate 是一个音频参数,而 loop loopStart loopEnd 是标准的 JavaScript 类型,因此可以直接设置。同时,我们为 playbackRate loop loopStart loopEnd 提供了回调函数,以便可以动态更改它们的值。但对于 offset duration ,我们不需要回调函数,因为它们仅在调用 start() 方法时使用。

1.1 参数交互要点

  • 即使音频处于循环状态,播放的总时长也不会超过 duration 参数。如果希望循环无限继续,则不应指定 duration
  • offset 仅影响声音首次开始播放的位置,无论其他参数有何影响。例如,如果有 2 秒的偏移且没有循环,则只能听到缓冲区从 2 秒开始的部分;如果有 2 秒的偏移且循环范围是从 0 到 1 秒,则只能听到循环部分。

1.2 流程图

graph TD;
    A[点击开始按钮] --> B[创建新的缓冲区源节点];
    B --> C[设置参数];
    C --> D[启动节点];
    D --> E[播放音频];
    E --> F{是否循环};
    F -- 是 --> G[继续循环播放];
    F -- 否 --> H[播放结束];

2. 波表合成示例

波表合成是一种音频合成技术,通过存储波形的单个周期或多个波形,然后将它们拼接在一起、塑造其包络并以不同的速率播放来构建声音。以下是一个使用波表合成重建方波的示例:

<button onclick='context.resume()'>Start</button>
<span>Frequency: </span>
<input type='range' min=0 max=1000 step='any' value=100 id='F'>
<script>
    let context = new AudioContext();
    // 创建一个方波一个周期的缓冲区源
    let bufferLength = context.sampleRate;
    let array = new Float32Array(bufferLength);
    for (var i = 0; i < array.length; i++)
        array[i] = Math.sin(2 * Math.PI * i / context.sampleRate);
    var buffer = context.createBuffer(1, bufferLength, context.sampleRate);
    buffer.copyToChannel(array, 0);
    var source = [], amplitude = [];
    // 为傅里叶级数的每一项使用这个缓冲区
    for (i = 0; i < 10; i++) {
        var n = 2 * i + 1;
        source[i] = new AudioBufferSourceNode(context);
        source[i].buffer = buffer;
        // 为每个傅里叶项以不同的速率播放缓冲区
        source[i].playbackRate.value = n * F.value;
        source[i].loop = true;
        amplitude[i] = new GainNode(context);
        amplitude[i].gain.value = 4 / (n * Math.PI);
        source[i].connect(amplitude[i]);
        amplitude[i].connect(context.destination);
        source[i].start();
    }
    F.oninput = () => {
        for (i = 0; i < 10; i++) source[i].playbackRate.value = (2 * i + 1) * F.value;
    }
</script>

在这个示例中,我们首先创建一个长度为 1 秒的数组,它代表 1 Hz 正弦波的一个周期。然后将这个数组复制到缓冲区源中。接着,为傅里叶级数的每一项创建一个缓冲区源节点,并使用之前创建的缓冲区源。通过改变播放速率来确定与这个傅里叶级数分量相关的频率,并将 loop 参数设置为 true ,以确保这个正弦波的单个周期变成一个连续的正弦波振荡器。最后,将缓冲区源节点连接到一个增益节点,应用适当的傅里叶系数作为增益。用户可以通过调整范围控制滑块来改变基频,这会导致每个缓冲区源节点的播放速率也发生变化,从而使这个方波近似的频率随着滑块的移动而平滑变化。

2.1 步骤总结

  1. 创建一个长度为 1 秒的数组,代表 1 Hz 正弦波的一个周期。
  2. 将数组复制到缓冲区源中。
  3. 为傅里叶级数的每一项创建一个缓冲区源节点,并使用之前创建的缓冲区源。
  4. 改变播放速率来确定与傅里叶级数分量相关的频率。
  5. loop 参数设置为 true ,使正弦波的单个周期变成连续的正弦波振荡器。
  6. 将缓冲区源节点连接到增益节点,应用适当的傅里叶系数作为增益。
  7. 用户调整滑块改变基频,更新每个缓冲区源节点的播放速率。

3. 音频暂停示例

在 Web Audio API 中,有多种方法可以停止声音,例如暂停音频上下文、断开节点连接、将连接的增益节点的增益设置为 0 或停止预定的源节点。但如果只想暂停 AudioBufferSourceNode 的播放,可以通过设置 playbackRate 为 0 来实现。以下是一个示例:

<button onclick='Play()'>Play</button>
<button onclick='Pause()'>Pause</button>
<script>
    var FirstTime = true;
    var context = new AudioContext();
    var source = context.createBufferSource();
    source.connect(context.destination);
    let bufferDuration = 4;
    let bufferLength = bufferDuration * context.sampleRate;
    let array = new Float32Array(bufferLength);
    let instantFreq;
    for (var i = 0; i < array.length; i++) {
        instantFreq = 1000 * i / bufferLength;
        array[i] = Math.sin(2 * Math.PI * instantFreq * i / context.sampleRate);
    }
    source.buffer = context.createBuffer(1, bufferLength, context.sampleRate);
    source.buffer.copyToChannel(array, 0);
    function Play() {
        context.resume();
        if (FirstTime) {
            source.start();
            FirstTime = false;
        }
        else source.playbackRate.value = 1;
    }
    function Pause() {
        source.playbackRate.value = 0;
    }
</script>

在这个示例中,我们再次将缓冲区填充为啁啾信号。点击“Play”按钮会在需要时恢复上下文,如果节点尚未启动则启动它,或者如果已经启动则将 playbackRate 重置为 1。点击“Pause”按钮会通过将 playbackRate 设置为 0 来暂停播放。

3.1 操作步骤

  1. 创建音频上下文和缓冲区源节点。
  2. 填充缓冲区为啁啾信号。
  3. 点击“Play”按钮:
    • 如果是第一次点击,启动节点。
    • 否则,将 playbackRate 重置为 1。
  4. 点击“Pause”按钮,将 playbackRate 设置为 0。

4. 音频反向播放示例

虽然 Web Audio API 文档中关于此方面的信息存在一些冲突,但 AudioBufferSourceNode 不接受负的播放速率。如果将 playbackRate 参数设置为负数,当前版本的 Chrome 不会产生错误,但节点只会产生静音。要实现音频的反向播放,可以通过以下方法:

<button onclick='Start()'>Start</button>
<button onclick='Reverse()'>Reverse</button>
<script>
    var context = new AudioContext();
    var Forwards = true;
    var source, sourceReversed;
    let bufferDuration = 10;
    let bufferLength = bufferDuration * context.sampleRate;
    let array = new Float32Array(bufferLength);
    let instantFreq;
    for (var i = 0; i < array.length; i++) {
        instantFreq = 1000 * i / bufferLength;
        array[i] = Math.sin(2 * Math.PI * instantFreq * i / context.sampleRate);
    }
    var reversedArray = array.slice().reverse();
    function Start() {
        context.resume();
        source = new AudioBufferSourceNode(context);
        source.loop = true;
        source.connect(context.destination);
        source.buffer = context.createBuffer(1, bufferLength, context.sampleRate);
        source.buffer.copyToChannel(array, 0);
        source.start();
        startTime = context.currentTime;
        playheadPosition = 0;
    }
    function Reverse() {
        source.stop();
        source = new AudioBufferSourceNode(context);
        source.loop = true;
        source.connect(context.destination);
        source.buffer = context.createBuffer(1, bufferLength, context.sampleRate);
        playheadPosition += context.currentTime - startTime;
        playheadPosition = playheadPosition % source.buffer.duration;
        playheadPosition = source.buffer.duration - playheadPosition;
        if (Forwards)
            source.buffer.copyToChannel(reversedArray, 0);
        else source.buffer.copyToChannel(array, 0);
        source.start(0, playheadPosition);
        startTime = context.currentTime;
        Forwards = !Forwards;
    }
</script>

在这个示例中,界面上有按钮用于启动音频、播放缓冲区和反向播放。点击“Start”按钮会创建一个启用循环的缓冲区源节点,并将 10 秒的单声道啁啾信号作为其缓冲区。同时,我们创建一个反转的数组。当点击“Reverse”按钮时,当前源会停止,并创建一个新的 AudioBufferSourceNode 来代替它。这个新节点使用之前节点缓冲区数组的反转版本,以实现反向播放。为了确保从播放停止的位置继续播放,我们需要进行以下三步操作:
1. 将播放头位置向前增加自上一个源节点启动以来经过的时间。
2. 由于音频是循环的,应用取余运算符确保播放头位置在缓冲区的持续时间内。
3. 通过计算持续时间减去当前播放头位置来找到新的播放头位置。

4.1 操作步骤

  1. 点击“Start”按钮:
    • 恢复音频上下文。
    • 创建一个启用循环的缓冲区源节点。
    • 将 10 秒的单声道啁啾信号作为缓冲区。
    • 启动节点并记录开始时间和播放头位置。
  2. 点击“Reverse”按钮:
    • 停止当前源。
    • 创建一个新的缓冲区源节点。
    • 计算新的播放头位置。
    • 根据当前播放方向选择使用原始数组或反转数组填充缓冲区。
    • 从新的播放头位置启动节点。
    • 更新开始时间和播放方向。

5. 常量源节点介绍

常量源节点( ConstantSourceNode )是一种简单的音频节点,它代表一个输出为常量值的音频源。尽管这个节点看似简单,但实际上有很多用途。它可以作为一个通用的常量源,并且可以通过自动化其偏移量,然后将其连接到另一个节点或另一个节点的参数,就像一个音频参数一样使用。例如,将其作为增益节点的两个或多个输入之一时,它就像一个加法器,将偏移量与另一个输入相加;将其作为增益节点的增益参数的输入时,它就像一个乘法器,将输入乘以偏移量。

5.1 创建常量源节点

可以使用以下两种方式创建常量源节点:

const Source = context.createConstantSource();

const Source = new ConstantSource(context);

OscillatorNode 一样, ConstantSourceNode 是一个 audioScheduledSourceNode ,这意味着它没有输入,并且必须调用 start() 方法才能产生非零输出。 ConstantSourceNode 有一个单一的参数 offset ,默认值为 1,它指定单声道输出源的常量值。不过, offset 可以变化,从而使 ConstantSourceNode 可以生成任意值。

5.2 示例:使用常量源节点添加直流偏移

信号处理和合成通常涉及对音频流执行简单操作,如将它们相加、从音频流中添加或减去常量值,或用常量值乘以音频流。这对于模拟模拟设备或生成不寻常的声音很有用。直流(DC)偏移是指周期性信号的平均值不为 0 的情况。以下是一个使用常量源节点为源添加直流偏移的示例:

<button onclick='context.resume()'>Start</button><br>
Offset
<input type='range' min=0 max=2 step='any' value=0 id='Offset'>
<script>
    var context = new AudioContext();
    let offsetNode = context.createConstantSource();
    let tone = context.createOscillator();
    let offsetTone = context.createGain();
    offsetNode.start();
    tone.start();
    offsetNode.connect(offsetTone);
    tone.connect(offsetTone);
    offsetTone.connect(context.destination);
    Offset.oninput = () => offsetNode.offset.value = Offset.value;
</script>

在这个示例中,创建了一个振荡器和一个常量源,并将它们都连接到一个增益节点(增益默认值为 1),以便输出只是这两个输入的总和。界面上的“Offset”滑块允许用户在 0 到 2 之间改变常量源的偏移量,从而为从增益节点输出的音调添加直流偏移。当偏移量为 0 时,输出只是一个正弦振荡器,范围从 -1 到 +1。随着偏移量的增加,输出的最小值增加,输出占据的允许范围 -1 到 +1 变小,因此它变成一个振幅较小的周期波,声音变得更安静。如果直流偏移设置为 2,则输入到目标的范围将从 -1 + 2 = 1 到 +1 + 2 = 3。Web Audio API 未定义超出 [-1, +1] 范围的行为,但大多数浏览器会将其裁剪到 [-1, +1] 范围,因此这个具有高直流偏移的正弦波经过裁剪后将产生一个常量 +1,从而变得听不见。

5.3 操作步骤

  1. 创建音频上下文、常量源节点、振荡器和增益节点。
  2. 启动常量源节点和振荡器。
  3. 将常量源节点和振荡器连接到增益节点。
  4. 将增益节点连接到音频目标。
  5. 通过滑块改变常量源节点的偏移量。

6. 使用常量源节点影响多个参数

我们已经知道方波的傅里叶级数近似可以通过 OscillatorNode 的类型或使用 PeriodicWave 作为自定义类型来生成。这里我们介绍另一种方法,使用一组振荡器并设置它们的振幅和频率。以下是一个示例:

<button onclick='context.resume()'>Start</button>
<span>Fundamental frequency: </span>
<input type='range' min=0 max=1000 step='any' value=0 id='F'>
<script>
    var context = new AudioContext();
    var sinusoid = [], harmonic = [], amplitude = [];
    var constantNode = new ConstantSourceNode(context, { offset: F.value });
    constantNode.start();
    for (i = 0; i < 10; i++) {
        sinusoid[i] = new OscillatorNode(context, { frequency: 0 });
        harmonic[i] = new GainNode(context);
        amplitude[i] = new GainNode(context);
        amplitude[i].gain.value = 4 / ((2 * i + 1) * Math.PI);
        harmonic[i].gain.value = 2 * i + 1;
        constantNode.connect(harmonic[i]);
        harmonic[i].connect(sinusoid[i].frequency);
        sinusoid[i].connect(amplitude[i]);
        amplitude[i].connect(context.destination);
        sinusoid[i].start();
    }
    F.oninput = () => constantNode.offset.value = F.value;
</script>

在这个示例中,界面上有一个滑块用于控制基频。我们选择保留傅里叶级数的前十个非零谐波,每个谐波都是一个具有适当振幅的振荡器。振幅由一个增益节点控制,其增益根据傅里叶级数系数设置。为了使每个振荡器的频率随着基频的变化而变化,我们使用常量源节点。常量源节点连接到增益节点,这些增益节点再连接到每个振荡器的频率参数。这样,改变滑块直接改变了一个节点的一个参数。虽然在这个例子中不一定需要使用常量源节点,但将音频图的更新保持在音频线程上可以被认为是一种良好的实践。

6.1 步骤总结

  1. 创建音频上下文和常量源节点。
  2. 启动常量源节点。
  3. 为前十个非零谐波创建振荡器、增益节点。
  4. 设置每个振荡器的振幅和频率。
  5. 将常量源节点连接到增益节点,再连接到振荡器的频率参数。
  6. 将振荡器连接到增益节点,再连接到音频目标。
  7. 启动每个振荡器。
  8. 通过滑块改变常量源节点的偏移量,从而更新每个振荡器的频率。

6.2 对比

与不使用常量源节点的方法相比,使用常量源节点可以更方便地更新多个参数。不使用常量源节点时,当基频改变时,需要重新计算每个谐波的频率,代码虽然更短,但会有更多的开销。

总结

通过以上示例,我们介绍了 Web Audio API 中音频参数的设置与交互、波表合成、音频暂停、音频反向播放以及常量源节点的使用。这些技术可以帮助我们实现各种音频处理和合成任务,如生成方波、添加直流偏移、控制音频的播放和暂停等。在实际应用中,可以根据具体需求选择合适的方法和节点来实现所需的音频效果。

7. 常量源节点更多应用场景分析

常量源节点在音频处理中有广泛的应用场景,下面通过几个具体的场景来进一步分析其作用。

7.1 多节点参数统一设置

在处理多节点音频时,我们常常需要对多个节点的参数进行统一设置。常量源节点可以很好地实现这一需求。例如,在一个多声道音频系统中,我们希望同时调整多个声道的增益。可以使用一个常量源节点,将其偏移量设置为所需的增益值,然后将该常量源节点连接到每个声道的增益节点的增益参数上。这样,只需改变常量源节点的偏移量,就可以同时调整所有声道的增益。

7.2 信号混合与调整

在音频合成中,我们经常需要将多个音频信号进行混合,并对混合后的信号进行调整。常量源节点可以作为一个固定的信号源,与其他音频信号进行混合。例如,我们可以将一个常量源节点的输出与一个振荡器的输出相加,实现信号的直流偏移。以下是一个简单的示例代码:

<button onclick='context.resume()'>Start</button><br>
Offset
<input type='range' min=0 max=2 step='any' value=0 id='Offset'>
<script>
    var context = new AudioContext();
    let constantSource = context.createConstantSource();
    let oscillator = context.createOscillator();
    let gainNode = context.createGain();
    constantSource.start();
    oscillator.start();
    constantSource.connect(gainNode);
    oscillator.connect(gainNode);
    gainNode.connect(context.destination);
    Offset.oninput = () => constantSource.offset.value = Offset.value;
</script>

在这个示例中,常量源节点的输出与振荡器的输出在增益节点处相加,用户可以通过滑块调整常量源节点的偏移量,从而改变混合信号的直流偏移。

7.3 音频效果模拟

常量源节点还可以用于模拟一些音频效果。例如,在模拟一个带有直流偏移的音频信号时,我们可以使用常量源节点来添加直流分量。另外,在模拟一些特殊的音频效果,如音频信号的放大或缩小,也可以使用常量源节点与增益节点的组合来实现。

7.4 操作步骤总结

以下是使用常量源节点进行常见操作的步骤总结:
1. 创建常量源节点 :使用 context.createConstantSource() new ConstantSource(context) 创建常量源节点。
2. 启动常量源节点 :调用 start() 方法启动常量源节点。
3. 设置偏移量 :通过 offset 参数设置常量源节点的偏移量。
4. 连接节点 :将常量源节点连接到目标节点或目标节点的参数上。
5. 调整参数 :根据需要调整常量源节点的偏移量,以实现所需的音频效果。

8. 不同音频处理技术对比

在音频处理中,我们可以使用多种技术来实现相同或相似的音频效果。下面对几种常见的音频处理技术进行对比。

8.1 波表合成与振荡器合成

技术类型 优点 缺点 适用场景
波表合成 可以精确控制波形,适合生成复杂的波形;可以通过改变播放速率轻松调整频率 需要预先存储波形数据,占用一定的内存;对于实时生成复杂变化的波形不够灵活 生成固定波形的音频,如方波、正弦波等
振荡器合成 可以实时生成各种基本波形,如正弦波、方波、三角波等;可以通过参数调整轻松改变波形的频率、振幅等 对于复杂波形的生成需要使用多个振荡器组合,实现起来相对复杂 生成简单波形的音频,如基本的音调、音效等

8.2 音频暂停与停止的不同方法

方法 操作方式 特点 适用场景
暂停 AudioBufferSourceNode (设置 playbackRate 为 0) playbackRate 设置为 0 暂停播放,设置为 1 恢复播放 可以精确控制暂停和恢复的位置;不会影响音频上下文和其他节点 需要临时暂停音频播放,后续继续播放的场景
暂停音频上下文 调用 context.suspend() 暂停音频上下文,调用 context.resume() 恢复 会暂停整个音频图的播放;适用于需要暂停所有音频的情况 需要同时暂停多个音频节点的播放
断开节点连接 调用 node.disconnect() 断开节点连接 节点仍然可以继续处理音频,但不会输出到后续节点 需要暂时移除某个节点的影响,但保留其状态
设置增益为 0 将增益节点的增益设置为 0 音频仍然在播放,但听不到声音;可以随时恢复声音 需要静音某个音频节点,但不停止其播放
停止预定的源节点 调用 source.stop() 停止源节点 节点停止播放,需要重新创建和启动才能继续播放 确定不再需要播放该音频节点的情况

8.3 音频反向播放的不同实现方式

实现方式 操作步骤 特点 适用场景
使用反转数组 创建一个反转的数组,在需要反向播放时使用该数组作为缓冲区 可以实现精确的反向播放;需要额外的内存来存储反转数组 需要实现音频反向播放的场景
尝试负播放速率(部分浏览器不支持) playbackRate 设置为负数 理论上可以实现反向播放,但部分浏览器不支持,会产生静音 对浏览器兼容性要求不高,希望通过简单设置实现反向播放的场景

9. 音频处理流程图总结

9.1 波表合成流程图

graph TD;
    A[创建音频上下文] --> B[创建缓冲区源数组];
    B --> C[填充数组为正弦波];
    C --> D[创建缓冲区];
    D --> E[将数组复制到缓冲区];
    E --> F[为傅里叶级数项创建节点];
    F --> G[设置播放速率和循环];
    G --> H[连接增益节点];
    H --> I[启动节点];
    I --> J[用户调整滑块];
    J --> K[更新播放速率];

9.2 音频暂停流程图

graph TD;
    A[创建音频上下文和源节点] --> B[填充缓冲区为啁啾信号];
    B --> C{点击 Play 按钮};
    C -- 第一次 --> D[启动节点];
    C -- 非第一次 --> E[设置 playbackRate 为 1];
    D --> F[音频播放];
    E --> F;
    F --> G{点击 Pause 按钮};
    G --> H[设置 playbackRate 为 0];
    H --> I[音频暂停];

9.3 音频反向播放流程图

graph TD;
    A[创建音频上下文和源节点] --> B[填充缓冲区为啁啾信号];
    B --> C[创建反转数组];
    C --> D{点击 Start 按钮};
    D --> E[启动节点并记录信息];
    E --> F[音频正向播放];
    F --> G{点击 Reverse 按钮};
    G --> H[停止当前源];
    H --> I[计算新的播放头位置];
    I --> J[选择使用反转数组或原始数组];
    J --> K[启动新节点];
    K --> L[音频反向播放];

10. 总结与展望

通过前面的介绍和分析,我们深入了解了 Web Audio API 中音频参数设置、波表合成、音频暂停、音频反向播放以及常量源节点的使用等重要技术。这些技术为我们实现各种音频处理和合成任务提供了强大的工具。

在实际应用中,我们可以根据具体需求选择合适的技术和节点。例如,在需要生成复杂波形时,可以选择波表合成;在需要统一设置多个节点参数时,可以使用常量源节点。同时,我们也看到了不同音频处理技术的优缺点和适用场景,这有助于我们在设计音频系统时做出更合理的决策。

未来,随着 Web Audio API 的不断发展和完善,我们可以期待更多强大的功能和更便捷的操作方式。例如,可能会有更高效的音频处理算法、更丰富的节点类型和更灵活的参数控制方式。我们可以利用这些新技术进一步拓展音频处理的应用领域,创造出更加丰富多样的音频体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值