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 Hz 正弦波的一个周期。
- 将数组复制到缓冲区源中。
- 为傅里叶级数的每一项创建一个缓冲区源节点,并使用之前创建的缓冲区源。
- 改变播放速率来确定与傅里叶级数分量相关的频率。
-
将
loop参数设置为true,使正弦波的单个周期变成连续的正弦波振荡器。 - 将缓冲区源节点连接到增益节点,应用适当的傅里叶系数作为增益。
- 用户调整滑块改变基频,更新每个缓冲区源节点的播放速率。
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 操作步骤
- 创建音频上下文和缓冲区源节点。
- 填充缓冲区为啁啾信号。
-
点击“Play”按钮:
- 如果是第一次点击,启动节点。
-
否则,将
playbackRate重置为 1。
-
点击“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 操作步骤
-
点击“Start”按钮:
- 恢复音频上下文。
- 创建一个启用循环的缓冲区源节点。
- 将 10 秒的单声道啁啾信号作为缓冲区。
- 启动节点并记录开始时间和播放头位置。
-
点击“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 操作步骤
- 创建音频上下文、常量源节点、振荡器和增益节点。
- 启动常量源节点和振荡器。
- 将常量源节点和振荡器连接到增益节点。
- 将增益节点连接到音频目标。
- 通过滑块改变常量源节点的偏移量。
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 步骤总结
- 创建音频上下文和常量源节点。
- 启动常量源节点。
- 为前十个非零谐波创建振荡器、增益节点。
- 设置每个振荡器的振幅和频率。
- 将常量源节点连接到增益节点,再连接到振荡器的频率参数。
- 将振荡器连接到增益节点,再连接到音频目标。
- 启动每个振荡器。
- 通过滑块改变常量源节点的偏移量,从而更新每个振荡器的频率。
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 的不断发展和完善,我们可以期待更多强大的功能和更便捷的操作方式。例如,可能会有更高效的音频处理算法、更丰富的节点类型和更灵活的参数控制方式。我们可以利用这些新技术进一步拓展音频处理的应用领域,创造出更加丰富多样的音频体验。

906

被折叠的 条评论
为什么被折叠?



