第九章:音频处理与效果器 —— 让声音从"能听"变成"好听"
第七章介绍了 Web Audio API 的节点体系,第八章把
AnalyserNode玩到了极致。这一章我们把重心放在音频信号处理本身——均衡器、动态压缩、混响、延迟、失真、合唱、镶边……每一种效果器背后都有清晰的信号处理逻辑,理解原理才能用好参数,调出真正好听的声音。
一、音频效果器的信号处理基础
在动手写代码之前,需要建立几个基础概念,它们贯穿本章所有效果器。
增益与分贝
人耳对音量的感知是对数的,因此音频领域普遍用**分贝(dB)**来描述增益变化:
$$ \text{dB} = 20 \times \log_{10}\left(\frac{A_{\text{out}}}{A_{\text{in}}}\right) $$
几个关键的 dB 值要记住:
- +6dB ≈ 幅度翻倍
- -6dB ≈ 幅度减半
- 0dB = 无变化
- -∞ dB = 静音
在 Web Audio API 中,GainNode.gain.value 是线性幅度比,不是 dB。转换关系:
// dB → 线性幅度
const linearGain = Math.pow(10, dB / 20);
// 线性幅度 → dB
const dB = 20 * Math.log10(linearGain);
// 工具函数
const dbToLinear = db => Math.pow(10, db / 20);
const linearToDb = linear => 20 * Math.log10(Math.max(linear, 1e-6));干湿信号(Dry / Wet)
几乎所有效果器都有**干信号(Dry)和湿信号(Wet)**的概念:
- 干信号:未经处理的原始信号
- 湿信号:经过效果器处理后的信号
- 混合比(Mix):干湿信号的比例,0% 是纯干,100% 是纯湿
// 通用干湿混合节点工厂
function createDryWetMix(audioCtx, effectNode, wetAmount = 0.5) {
const input = audioCtx.createGain(); // 输入节点
const dryGain = audioCtx.createGain();
const wetGain = audioCtx.createGain();
const output = audioCtx.createGain(); // 输出节点
dryGain.gain.value = 1 - wetAmount;
wetGain.gain.value = wetAmount;
input.connect(dryGain);
input.connect(effectNode);
effectNode.connect(wetGain);
dryGain.connect(output);
wetGain.connect(output);
return {
input,
output,
setMix(wet) {
const t = audioCtx.currentTime;
dryGain.gain.setTargetAtTime(1 - wet, t, 0.01);
wetGain.gain.setTargetAtTime(wet, t, 0.01);
},
};
}信号流图约定
本章所有效果器的信号流都遵循同一个结构:
[输入] → [前置增益] → [效果处理] → [干湿混合] → [输出]
↑
[参数控制]
二、均衡器(Equalizer)
均衡器是最基础也最重要的音色塑造工具,通过提升或衰减特定频段来改变音色。
BiquadFilter 滤波器类型详解
Web Audio 的 BiquadFilterNode 实现了 7 种滤波器,每种对应不同的频率响应曲线:
lowpass(低通) highpass(高通) bandpass(带通)
████████╲ ╱████████ ╱██╲
────────→ Hz ────────→ Hz ────────→ Hz
peaking(峰值) notch(陷波) lowshelf(低架)
╭─╮ ╰─╯ █████╲
───┴─┴──→ Hz ───┬─┬──→ Hz ──────→ Hz
highshelf(高架)
╱█████
──────→ Hz
参数均衡器(Parametric EQ)实现
专业均衡器通常有多个频段,每段可以独立调节频率、增益和 Q 值:
class ParametricEQ {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
// 定义 5 段均衡器
this.bands = [
{ type: 'lowshelf', freq: 80, gain: 0, Q: 0.7, label: '低频' },
{ type: 'peaking', freq: 250, gain: 0, Q: 1.0, label: '中低频' },
{ type: 'peaking', freq: 1000, gain: 0, Q: 1.0, label: '中频' },
{ type: 'peaking', freq: 4000, gain: 0, Q: 1.0, label: '中高频' },
{ type: 'highshelf', freq: 8000, gain: 0, Q: 0.7, label: '高频' },
].map(band => {
const node = audioCtx.createBiquadFilter();
node.type = band.type;
node.frequency.value = band.freq;
node.gain.value = band.gain;
node.Q.value = band.Q;
return { ...band, node };
});
// 串联所有滤波器
let prev = this.input;
for (const band of this.bands) {
prev.connect(band.node);
prev = band.node;
}
prev.connect(this.output);
}
// 设置某频段增益(dB)
setBandGain(index, gainDb) {
const band = this.bands[index];
if (!band) return;
band.node.gain.setTargetAtTime(
Math.max(-24, Math.min(24, gainDb)),
this.ctx.currentTime,
0.01
);
band.gain = gainDb;
}
// 设置某频段频率(Hz)
setBandFrequency(index, hz) {
const band = this.bands[index];
if (!band) return;
band.node.frequency.setTargetAtTime(hz, this.ctx.currentTime, 0.01);
band.freq = hz;
}
// 设置某频段 Q 值
setBandQ(index, q) {
const band = this.bands[index];
if (!band) return;
band.node.Q.setTargetAtTime(
Math.max(0.1, Math.min(20, q)),
this.ctx.currentTime,
0.01
);
band.Q = q;
}
// 重置所有频段
reset() {
this.bands.forEach((_, i) => this.setBandGain(i, 0));
}
// 应用预设
applyPreset(preset) {
// preset: [{ gain, freq?, Q? }, ...]
preset.forEach((p, i) => {
if (p.gain !== undefined) this.setBandGain(i, p.gain);
if (p.freq !== undefined) this.setBandFrequency(i, p.freq);
if (p.Q !== undefined) this.setBandQ(i, p.Q);
});
}
}EQ 预设库
const EQ_PRESETS = {
flat: [
{ gain: 0 }, { gain: 0 }, { gain: 0 }, { gain: 0 }, { gain: 0 }
],
bass_boost: [
{ gain: 8 }, { gain: 4 }, { gain: 0 }, { gain: 0 }, { gain: 0 }
],
vocal: [
{ gain: -2 }, { gain: 0 }, { gain: 5 }, { gain: 3 }, { gain: 1 }
],
acoustic: [
{ gain: 4 }, { gain: 2 }, { gain: 0 }, { gain: 2 }, { gain: 4 }
],
electronic: [
{ gain: 6 }, { gain: 2 }, { gain: -1 }, { gain: 2 }, { gain: 5 }
],
classical: [
{ gain: 3 }, { gain: 0 }, { gain: 0 }, { gain: 0 }, { gain: 3 }
],
loudness: [
{ gain: 6 }, { gain: 2 }, { gain: -1 }, { gain: 2 }, { gain: 6 }
],
};
// 使用
const eq = new ParametricEQ(audioCtx);
eq.applyPreset(EQ_PRESETS.bass_boost);频率响应曲线可视化
均衡器 UI 通常需要绘制频率响应曲线,BiquadFilterNode 提供了 getFrequencyResponse() 方法:
function drawEQCurve(bands, canvas) {
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const POINTS = 512;
// 生成对数分布的频率点(20Hz ~ 20kHz)
const freqArray = new Float32Array(POINTS);
for (let i = 0; i < POINTS; i++) {
freqArray[i] = 20 * Math.pow(1000, i / (POINTS - 1));
}
// 计算所有频段的综合频率响应
const magResponse = new Float32Array(POINTS).fill(1);
const phaseResponse = new Float32Array(POINTS);
const tempMag = new Float32Array(POINTS);
const tempPhase = new Float32Array(POINTS);
for (const band of bands) {
band.node.getFrequencyResponse(freqArray, tempMag, tempPhase);
for (let i = 0; i < POINTS; i++) {
magResponse[i] *= tempMag[i]; // 各频段响应相乘(dB 相加)
}
}
// 绘制
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = '#667eea';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i < POINTS; i++) {
const x = (i / POINTS) * W;
// 将幅度比转换为 dB,映射到画布 Y 轴(±24dB 范围)
const db = 20 * Math.log10(magResponse[i]);
const y = H / 2 - (db / 24) * (H / 2 - 10);
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.stroke();
// 绘制 0dB 参考线
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath();
ctx.moveTo(0, H / 2);
ctx.lineTo(W, H / 2);
ctx.stroke();
ctx.setLineDash([]);
}三、动态压缩器(Dynamics Compressor)
压缩器是混音中最重要的动态处理工具,它压缩音频的动态范围——让响的部分变小,配合 Makeup Gain 整体提升,使声音更饱满、更有力度。
压缩器五参数深度解析
class Compressor {
constructor(audioCtx) {
this.ctx = audioCtx;
this.node = audioCtx.createDynamicsCompressor();
this.makeupGain = audioCtx.createGain();
// 压缩器参数详解:
// threshold(阈值):超过这个电平才触发压缩,单位 dBFS,范围 -100~0
this.node.threshold.value = -24;
// knee(拐点):软拐点宽度,值越大过渡越平滑,范围 0~40dB
this.node.knee.value = 6;
// ratio(压缩比):超出阈值部分的压缩比例,4:1 意味着超出 4dB 只输出 1dB
this.node.ratio.value = 4;
// attack(起音):信号超过阈值后,压缩器开始工作的时间,单位秒
// 值越小响应越快(会影响瞬态),值越大保留更多瞬态感
this.node.attack.value = 0.003;
// release(释音):信号降到阈值以下后,压缩器停止工作的时间,单位秒
// 值越小恢复越快(可能产生"泵浦"感),值越大恢复越平滑
this.node.release.value = 0.25;
// Makeup Gain:压缩后补偿增益,通常设为压缩量的一半左右
this.makeupGain.gain.value = dbToLinear(6);
// 连接:输入 → 压缩器 → makeup gain → 输出
this.node.connect(this.makeupGain);
this.input = this.node;
this.output = this.makeupGain;
}
// 获取当前增益压缩量(dB,负值)
getReduction() {
return this.node.reduction; // 只读属性,实时反映压缩量
}
// 应用压缩预设
applyPreset(preset) {
const t = this.ctx.currentTime;
const p = preset;
this.node.threshold.setTargetAtTime(p.threshold, t, 0.01);
this.node.knee.setTargetAtTime(p.knee, t, 0.01);
this.node.ratio.setTargetAtTime(p.ratio, t, 0.01);
this.node.attack.setTargetAtTime(p.attack, t, 0.01);
this.node.release.setTargetAtTime(p.release, t, 0.01);
if (p.makeupGain !== undefined) {
this.makeupGain.gain.setTargetAtTime(
dbToLinear(p.makeupGain), t, 0.01
);
}
}
}
// 压缩预设
const COMPRESSOR_PRESETS = {
// 轻压缩:保留动态,轻微整形
gentle: {
threshold: -18, knee: 12, ratio: 2,
attack: 0.01, release: 0.3, makeupGain: 2,
},
// 人声压缩:控制动态,突出存在感
vocal: {
threshold: -20, knee: 6, ratio: 4,
attack: 0.005, release: 0.15, makeupGain: 4,
},
// 鼓组压缩:保留瞬态,压缩尾音
drums: {
threshold: -12, knee: 3, ratio: 6,
attack: 0.001, release: 0.05, makeupGain: 3,
},
// 母带压缩:整体动态控制
mastering: {
threshold: -6, knee: 6, ratio: 2,
attack: 0.01, release: 0.2, makeupGain: 1,
},
// 重度压缩(Limiter 效果)
limiter: {
threshold: -3, knee: 0, ratio: 20,
attack: 0.001, release: 0.1, makeupGain: 0,
},
};压缩量表(GR Meter)
专业压缩器 UI 都有一个增益压缩量表,实时显示压缩器正在压缩多少 dB:
class GRMeter {
constructor(compressorNode, canvas) {
this.compressor = compressorNode;
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.animId = null;
}
start() {
const draw = () => {
this.animId = requestAnimationFrame(draw);
this._draw();
};
draw();
}
stop() { cancelAnimationFrame(this.animId); }
_draw() {
const reduction = Math.abs(this.compressor.reduction); // 0 ~ 40dB
const { ctx, canvas } = this;
const W = canvas.width, H = canvas.height;
ctx.clearRect(0, 0, W, H);
// 背景
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, W, H);
// 压缩量条(从右往左,越长表示压缩越多)
const maxDb = 20;
const ratio = Math.min(reduction / maxDb, 1);
const barW = ratio * (W - 20);
// 颜色:轻压缩绿色,重压缩红色
const hue = 120 - ratio * 120; // 绿→黄→红
ctx.fillStyle = `hsl(, 90%, 50%)`;
ctx.fillRect(W - 10 - barW, H * 0.25, barW, H * 0.5);
// 刻度线
ctx.fillStyle = 'rgba(255,255,255,0.5)';
ctx.font = '10px monospace';
[0, 3, 6, 10, 20].forEach(db => {
const x = W - 10 - (db / maxDb) * (W - 20);
ctx.fillRect(x, 0, 1, H * 0.2);
ctx.fillText(`-`, x - 8, H - 4);
});
// 当前压缩量数字
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px monospace';
ctx.fillText(`GR: -dB`, 8, H / 2 + 4);
}
}四、混响(Reverb)
混响是最能改变空间感的效果器。Web Audio 提供两种混响实现方式:卷积混响(ConvolverNode)和算法混响(用延迟网络模拟)。
卷积混响(Convolution Reverb)
卷积混响通过与真实空间录制的**脉冲响应(IR)**做卷积运算,能完美重现真实空间的声学特性:
class ConvolutionReverb {
constructor(audioCtx) {
this.ctx = audioCtx;
this.convolver = audioCtx.createConvolver();
this.preDelay = audioCtx.createDelay(0.1); // 预延迟,增加空间感
this.preDelay.delayTime.value = 0.02; // 20ms 预延迟
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
const { dryGain, wetGain, setMix } = this._buildDryWet();
this.setMix = setMix;
// 信号流:输入 → 预延迟 → 卷积器 → 湿增益 → 输出
this.input.connect(dryGain);
this.input.connect(this.preDelay);
this.preDelay.connect(this.convolver);
this.convolver.connect(wetGain);
dryGain.connect(this.output);
wetGain.connect(this.output);
}
_buildDryWet() {
const dryGain = this.ctx.createGain();
const wetGain = this.ctx.createGain();
dryGain.gain.value = 0.7;
wetGain.gain.value = 0.3;
const setMix = (wet) => {
const t = this.ctx.currentTime;
dryGain.gain.setTargetAtTime(1 - wet, t, 0.02);
wetGain.gain.setTargetAtTime(wet, t, 0.02);
};
return { dryGain, wetGain, setMix };
}
// 从 URL 加载 IR 文件
async loadIR(url) {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
this.convolver.buffer = await this.ctx.decodeAudioData(arrayBuffer);
console.log('IR 加载完成:', url);
}
// 合成简单的 IR(无需外部文件)
synthesizeIR(duration = 2, decay = 3.0, reverse = false) {
const sampleRate = this.ctx.sampleRate;
const length = sampleRate * duration;
const buffer = this.ctx.createBuffer(2, length, sampleRate);
for (let ch = 0; ch < 2; ch++) {
const data = buffer.getChannelData(ch);
for (let i = 0; i < length; i++) {
// 指数衰减的白噪声 = 简单的混响 IR
const t = i / sampleRate;
data[i] = (Math.random() * 2 - 1) * Math.pow(Math.E, -decay * t);
}
if (reverse) data.reverse(); // 反向混响效果
}
this.convolver.buffer = buffer;
}
setPreDelay(ms) {
this.preDelay.delayTime.setTargetAtTime(
ms / 1000,
this.ctx.currentTime,
0.01
);
}
}算法混响(Schroeder 混响网络)
用纯算法模拟混响,不需要 IR 文件,资源占用更低,但音色不如卷积混响自然:
class AlgorithmicReverb {
constructor(audioCtx) {
this.ctx = audioCtx;
// Schroeder 混响:4 个并联梳状滤波器 + 2 个串联全通滤波器
// 梳状滤波器延迟时间(毫秒)—— 互质,避免产生明显的重复回声
const combDelays = [29.7, 37.1, 41.1, 43.7];
const allpassDelays = [5.0, 1.7];
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
// 并联梳状滤波器
const combOutputs = combDelays.map(delayMs => {
return this._createCombFilter(delayMs / 1000, 0.84);
});
// 合并梳状滤波器输出
const combMerge = audioCtx.createGain();
combMerge.gain.value = 0.25; // 4 路平均
combOutputs.forEach(comb => {
this.input.connect(comb.input);
comb.output.connect(combMerge);
});
// 串联全通滤波器
const allpass1 = this._createAllpassFilter(allpassDelays[0] / 1000, 0.7);
const allpass2 = this._createAllpassFilter(allpassDelays[1] / 1000, 0.7);
combMerge
.connect(allpass1.input);
allpass1.output
.connect(allpass2.input);
allpass2.output
.connect(this.output);
}
// 梳状滤波器 = 延迟 + 反馈
_createCombFilter(delayTime, feedback) {
const delay = this.ctx.createDelay(delayTime + 0.01);
const feedGain = this.ctx.createGain();
const input = this.ctx.createGain();
const output = this.ctx.createGain();
delay.delayTime.value = delayTime;
feedGain.gain.value = feedback;
input.connect(delay);
delay.connect(output);
delay.connect(feedGain);
feedGain.connect(delay); // 反馈回路
return { input, output };
}
// 全通滤波器(保持频率响应平坦,只改变相位)
_createAllpassFilter(delayTime, feedback) {
const delay = this.ctx.createDelay(delayTime + 0.01);
const feedGain = this.ctx.createGain();
const forwardGain = this.ctx.createGain();
const input = this.ctx.createGain();
const output = this.ctx.createGain();
delay.delayTime.value = delayTime;
feedGain.gain.value = -feedback;
forwardGain.gain.value = feedback;
input.connect(delay);
input.connect(forwardGain);
delay.connect(feedGain);
feedGain.connect(input);
delay.connect(output);
forwardGain.connect(output);
return { input, output };
}
}五、延迟效果器(Delay)
延迟效果器将信号延迟一段时间后叠加回原信号,产生回声效果。通过调节延迟时间、反馈量和混合比,可以得到从单次回声到无限循环的各种效果。
class DelayEffect {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
this.delay = audioCtx.createDelay(5.0); // 最大延迟 5 秒
this.feedback = audioCtx.createGain(); // 反馈增益
this.wetGain = audioCtx.createGain();
this.dryGain = audioCtx.createGain();
// 低通滤波器(让回声逐渐变暗,更自然)
this.toneFilter = audioCtx.createBiquadFilter();
this.toneFilter.type = 'lowpass';
this.toneFilter.frequency.value = 4000;
// 默认参数
this.delay.delayTime.value = 0.375; // 375ms(对应 160BPM 的八分音符)
this.feedback.gain.value = 0.4; // 40% 反馈
this.dryGain.gain.value = 0.7;
this.wetGain.gain.value = 0.3;
// 信号路由
// 干路:输入 → 干增益 → 输出
this.input.connect(this.dryGain);
this.dryGain.connect(this.output);
// 湿路:输入 → 延迟 → 色调滤波 → 反馈回路 + 湿增益
this.input.connect(this.delay);
this.delay.connect(this.toneFilter);
this.toneFilter.connect(this.feedback);
this.toneFilter.connect(this.wetGain);
this.feedback.connect(this.delay); // 反馈回路
this.wetGain.connect(this.output);
}
// 按 BPM 同步延迟时间
syncToBPM(bpm, noteValue = '8n') {
const beatDuration = 60 / bpm; // 一拍的时长(秒)
const noteMap = {
'1n': beatDuration * 4, // 全音符
'2n': beatDuration * 2, // 二分音符
'4n': beatDuration, // 四分音符
'8n': beatDuration / 2, // 八分音符
'16n': beatDuration / 4, // 十六分音符
'4t': beatDuration / 1.5, // 四分三连音
'8t': beatDuration / 3, // 八分三连音
};
const delayTime = noteMap[noteValue] || beatDuration / 2;
this.delay.delayTime.setTargetAtTime(
Math.min(delayTime, 5.0),
this.ctx.currentTime,
0.01
);
return delayTime;
}
setFeedback(value) {
// 反馈不能 >= 1,否则产生无限增益(啸叫)
const safeValue = Math.max(0, Math.min(0.95, value));
this.feedback.gain.setTargetAtTime(safeValue, this.ctx.currentTime, 0.01);
}
setDelayTime(seconds) {
this.delay.delayTime.setTargetAtTime(
Math.max(0.001, Math.min(5.0, seconds)),
this.ctx.currentTime,
0.01
);
}
setMix(wet) {
const t = this.ctx.currentTime;
this.dryGain.gain.setTargetAtTime(1 - wet, t, 0.01);
this.wetGain.gain.setTargetAtTime(wet, t, 0.01);
}
setTone(hz) {
this.toneFilter.frequency.setTargetAtTime(hz, this.ctx.currentTime, 0.01);
}
}Ping-Pong 延迟
Ping-Pong 延迟是延迟效果的经典变体——回声在左右声道之间交替出现,产生空间感极强的立体声效果:
class PingPongDelay {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
// 两个延迟节点,分别对应左右声道
this.delayL = audioCtx.createDelay(2.0);
this.delayR = audioCtx.createDelay(2.0);
this.feedbackL = audioCtx.createGain();
this.feedbackR = audioCtx.createGain();
// 声道分离器和合并器
this.splitter = audioCtx.createChannelSplitter(2);
this.merger = audioCtx.createChannelMerger(2);
const delayTime = 0.25; // 250ms
this.delayL.delayTime.value = delayTime;
this.delayR.delayTime.value = delayTime;
this.feedbackL.gain.value = 0.5;
this.feedbackR.gain.value = 0.5;
// Ping-Pong 路由:左延迟的输出反馈到右延迟,右延迟反馈到左延迟
this.input.connect(this.delayL);
this.delayL.connect(this.feedbackL);
this.feedbackL.connect(this.delayR); // L → R
this.delayR.connect(this.feedbackR);
this.feedbackR.connect(this.delayL); // R → L(形成 Ping-Pong)
// 左右声道分别输出到 merger
this.delayL.connect(this.merger, 0, 0); // L → 左声道
this.delayR.connect(this.merger, 0, 1); // R → 右声道
this.merger.connect(this.output);
// 干信号直通
this.input.connect(this.output);
}
setDelayTime(seconds) {
const t = this.ctx.currentTime;
this.delayL.delayTime.setTargetAtTime(seconds, t, 0.01);
this.delayR.delayTime.setTargetAtTime(seconds, t, 0.01);
}
setFeedback(value) {
const safe = Math.max(0, Math.min(0.92, value));
const t = this.ctx.currentTime;
this.feedbackL.gain.setTargetAtTime(safe, t, 0.01);
this.feedbackR.gain.setTargetAtTime(safe, t, 0.01);
}
}六、失真效果器(Distortion)
失真通过对信号进行非线性变换(削波),产生丰富的谐波,是电吉他音色的灵魂,也广泛用于电子音乐制作。
波形整形曲线(Waveshaper Curves)
不同的传递函数曲线产生不同风格的失真:
class Distortion {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
this.waveshaper = audioCtx.createWaveShaper();
this.preGain = audioCtx.createGain(); // 前置增益(驱动量)
this.postGain = audioCtx.createGain(); // 后置增益(音量补偿)
// 高通滤波器:去除直流偏移,让失真更干净
this.dcFilter = audioCtx.createBiquadFilter();
this.dcFilter.type = 'highpass';
this.dcFilter.frequency.value = 10;
// 色调滤波器:控制失真音色的亮度
this.toneFilter = audioCtx.createBiquadFilter();
this.toneFilter.type = 'lowpass';
this.toneFilter.frequency.value = 3500;
this.preGain.gain.value = 3.0; // 驱动量
this.postGain.gain.value = 0.4; // 补偿失真后的音量增加
this.waveshaper.oversample = '4x'; // 过采样减少混叠
// 路由:输入 → 前置增益 → DC滤波 → 波形整形 → 色调 → 后置增益 → 输出
this.input.connect(this.preGain);
this.preGain.connect(this.dcFilter);
this.dcFilter.connect(this.waveshaper);
this.waveshaper.connect(this.toneFilter);
this.toneFilter.connect(this.postGain);
this.postGain.connect(this.output);
// 默认使用软削波
this.setType('soft', 100);
}
// 软削波(Soft Clipping)—— 温暖、自然,类似电子管过载
_softClipCurve(amount) {
const n = 256;
const curve = new Float32Array(n);
const k = amount;
for (let i = 0; i < n; i++) {
const x = (i * 2) / n - 1;
curve[i] = ((Math.PI + k) * x) / (Math.PI + k * Math.abs(x));
}
return curve;
}
// 硬削波(Hard Clipping)—— 刺激、攻击性强,类似晶体管失真
_hardClipCurve(amount) {
const n = 256;
const curve = new Float32Array(n);
const threshold = 1 - amount / 1000;
for (let i = 0; i < n; i++) {
const x = (i * 2) / n - 1;
curve[i] = Math.max(-threshold, Math.min(threshold, x * (1 / threshold));
}
return curve;
}
// 折叠失真(Foldback)—— 数字感强,产生复杂谐波
_foldbackCurve(amount) {
const n = 256;
const curve = new Float32Array(n);
const threshold = 0.5 + (1 - amount / 200) * 0.5;
for (let i = 0; i < n; i++) {
let x = (i * 2) / n - 1;
// 超过阈值时折叠回来
while (Math.abs(x) > threshold) {
if (x > threshold) x = 2 * threshold - x;
if (x < -threshold) x = -2 * threshold - x;
}
curve[i] = x;
}
return curve;
}
// 比特压缩(Bit Crusher 近似)—— 复古数字音色
_bitCrushCurve(bits = 4) {
const n = 256;
const curve = new Float32Array(n);
const steps = Math.pow(2, bits);
for (let i = 0; i < n; i++) {
const x = (i * 2) / n - 1;
curve[i] = Math.round(x * steps) / steps;
}
return curve;
}
setType(type, amount = 100) {
const curves = {
soft: () => this._softClipCurve(amount),
hard: () => this._hardClipCurve(amount),
foldback: () => this._foldbackCurve(amount),
bitcrush: () => this._bitCrushCurve(Math.round(8 - amount / 14)),
};
this.waveshaper.curve = (curves[type] || curves.soft)();
}
setDrive(value) {
// value: 1 ~ 10
const t = this.ctx.currentTime;
this.preGain.gain.setTargetAtTime(value, t, 0.01);
// 驱动增大时降低后置增益补偿
this.postGain.gain.setTargetAtTime(0.4 / Math.sqrt(value), t, 0.01);
}
setTone(hz) {
this.toneFilter.frequency.setTargetAtTime(hz, this.ctx.currentTime, 0.01);
}
}七、合唱效果器(Chorus)
合唱效果通过将信号轻微延迟并叠加,模拟多个演奏者同时演奏的效果,让声音更丰满、有厚度。
class ChorusEffect {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
// 合唱核心:LFO 调制的延迟
// 使用 3 个声部,相位各差 120°,产生更丰满的效果
this.voices = [0, 1, 2].map(i => this._createVoice(i * (Math.PI * 2 / 3)));
const dryGain = audioCtx.createGain();
dryGain.gain.value = 0.6;
this.input.connect(dryGain);
dryGain.connect(this.output);
this.voices.forEach(voice => {
this.input.connect(voice.input);
voice.output.connect(this.output);
});
}
_createVoice(lfoPhase) {
const delay = this.ctx.createDelay(0.05);
const lfo = this.ctx.createOscillator();
const lfoGain = this.ctx.createGain();
const voiceGain = this.ctx.createGain();
// 基础延迟:15ms(合唱的典型延迟范围 10~25ms)
delay.delayTime.value = 0.015;
// LFO 调制延迟时间,产生音高微颤
lfo.type = 'sine';
lfo.frequency.value = 0.8; // 0.8Hz 调制速率
lfoGain.gain.value = 0.003; // 调制深度:±3ms
// 设置 LFO 初始相位(通过 start 时间偏移模拟)
lfo.connect(lfoGain);
lfoGain.connect(delay.delayTime);
lfo.start(this.ctx.currentTime - lfoPhase / (Math.PI * 2 * 0.8));
voiceGain.gain.value = 0.33; // 3 个声部平均分配
delay.connect(voiceGain);
return { input: delay, output: voiceGain, lfo, lfoGain };
}
// 调制速率(Hz)
setRate(hz) {
this.voices.forEach(v => {
v.lfo.frequency.setTargetAtTime(hz, this.ctx.currentTime, 0.01);
});
}
// 调制深度(0~1)
setDepth(value) {
const depthMs = value * 0.006; // 最大 ±6ms
this.voices.forEach(v => {
v.lfoGain.gain.setTargetAtTime(depthMs, this.ctx.currentTime, 0.01);
});
}
}八、镶边效果器(Flanger)
镶边与合唱原理相同,但延迟时间更短(0.5~10ms),反馈更强,产生独特的"金属扫频"音色:
class FlangerEffect {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
this.delay = audioCtx.createDelay(0.02);
this.lfo = audioCtx.createOscillator();
this.lfoGain = audioCtx.createGain();
this.feedback = audioCtx.createGain();
this.wetGain = audioCtx.createGain();
this.dryGain = audioCtx.createGain();
// 镶边参数:比合唱更短的延迟,更强的反馈
this.delay.delayTime.value = 0.003; // 3ms 基础延迟
this.lfo.frequency.value = 0.3; // 0.3Hz 调制速率
this.lfoGain.gain.value = 0.002; // ±2ms 调制深度
this.feedback.gain.value = 0.6; // 60% 反馈(镶边的关键)
this.dryGain.gain.value = 0.7;
this.wetGain.gain.value = 0.3;
// LFO → 延迟时间
this.lfo.connect(this.lfoGain);
this.lfoGain.connect(this.delay.delayTime);
this.lfo.start();
// 信号路由(含反馈回路)
this.input.connect(this.dryGain);
this.input.connect(this.delay);
this.delay.connect(this.feedback);
this.feedback.connect(this.delay); // 反馈回路
this.delay.connect(this.wetGain);
this.dryGain.connect(this.output);
this.wetGain.connect(this.output);
}
setRate(hz) {
this.lfo.frequency.setTargetAtTime(hz, this.ctx.currentTime, 0.01);
}
setDepth(value) {
this.lfoGain.gain.setTargetAtTime(value * 0.004, this.ctx.currentTime, 0.01);
}
setFeedback(value) {
// 镶边反馈可以是负值(产生不同音色)
const safe = Math.max(-0.95, Math.min(0.95, value));
this.feedback.gain.setTargetAtTime(safe, this.ctx.currentTime, 0.01);
}
}九、音高移调(Pitch Shift)
Web Audio API 没有内置音高移调节点,需要用 AudioWorklet 结合**相位声码器(Phase Vocoder)**算法实现:
// pitch-shift-processor.js(AudioWorklet)
class PitchShiftProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{
name: 'pitchFactor',
defaultValue: 1.0, // 1.0 = 原始音高,2.0 = 高八度,0.5 = 低八度
minValue: 0.25,
maxValue: 4.0,
automationRate: 'k-rate',
}];
}
constructor() {
super();
this.FRAME_SIZE = 2048;
this.HOP_SIZE = 512;
this.OVERLAP = this.FRAME_SIZE / this.HOP_SIZE;
// 输入/输出缓冲区
this.inputBuffer = new Float32Array(this.FRAME_SIZE);
this.outputBuffer = new Float32Array(this.FRAME_SIZE);
this.inputPtr = 0;
this.outputPtr = 0;
// 相位累积器
this.lastPhase = new Float32Array(this.FRAME_SIZE / 2 + 1);
this.sumPhase = new Float32Array(this.FRAME_SIZE / 2 + 1);
this.outputAccum = new Float32Array(this.FRAME_SIZE * 2);
// 汉宁窗(减少频谱泄漏)
this.window = new Float32Array(this.FRAME_SIZE);
for (let i = 0; i < this.FRAME_SIZE; i++) {
this.window[i] = 0.5 * (1 - Math.cos(2 * Math.PI * i / this.FRAME_SIZE));
}
}
process(inputs, outputs, parameters) {
const input = inputs[0][0];
const output = outputs[0][0];
const pitchFactor = parameters['pitchFactor'][0];
if (!input) return true;
// 将输入数据写入缓冲区
for (let i = 0; i < input.length; i++) {
this.inputBuffer[this.inputPtr++] = input[i];
// 当积累了足够的数据(一帧)时,处理这一帧
if (this.inputPtr >= this.HOP_SIZE) {
this.inputPtr = 0;
this._processFrame(pitchFactor);
}
}
// 从输出缓冲区读取数据
for (let i = 0; i < output.length; i++) {
output[i] = this.outputBuffer[this.outputPtr++];
if (this.outputPtr >= this.outputBuffer.length) {
this.outputPtr = 0;
}
}
return true;
}
_processFrame(pitchFactor) {
// 简化版相位声码器:
// 完整实现需要 FFT/IFFT,这里展示核心思路
// 实际项目建议使用 soundtouch.js 或 rubberband-wasm 等成熟库
const expFactor = pitchFactor;
for (let i = 0; i < this.FRAME_SIZE; i++) {
// 时域重采样(近似音高移调,有音质损失)
const srcIdx = Math.floor(i / expFactor);
if (srcIdx < this.inputBuffer.length) {
this.outputBuffer[i] = this.inputBuffer[srcIdx] * this.window[i];
}
}
}
}
registerProcessor('pitch-shift-processor', PitchShiftProcessor);实际项目建议:完整的高质量音高移调算法(相位声码器)实现复杂,建议直接使用 soundtouch-js 或将 RubberBand 编译为 WebAssembly 使用,音质远优于简单的时域重采样。
十、整合:专业级效果链
把本章所有效果器整合成一个完整的信号处理链,模拟专业 DAW 的效果器插槽:
class EffectChain {
constructor(audioCtx) {
this.ctx = audioCtx;
this.input = audioCtx.createGain();
this.output = audioCtx.createGain();
// 实例化所有效果器
this.eq = new ParametricEQ(audioCtx);
this.compressor = new Compressor(audioCtx);
this.reverb = new ConvolutionReverb(audioCtx);
this.delay = new DelayEffect(audioCtx);
this.chorus = new ChorusEffect(audioCtx);
this.distortion = new Distortion(audioCtx);
// 效果器槽位(有序,可启用/禁用)
this.slots = [
{ name: 'distortion', effect: this.distortion, enabled: false },
{ name: 'eq', effect: this.eq, enabled: true },
{ name: 'compressor', effect: this.compressor, enabled: true },
{ name: 'chorus', effect: this.chorus, enabled: false },
{ name: 'delay', effect: this.delay, enabled: false },
{ name: 'reverb', effect: this.reverb, enabled: false },
];
this._rebuildChain();
}
// 重新构建效果链(启用/禁用效果器后调用)
_rebuildChain() {
// 断开所有现有连接
this.input.disconnect();
this.slots.forEach(slot => slot.effect.output.disconnect());
const activeSlots = this.slots.filter(s => s.enabled);
if (activeSlots.length === 0) {
// 没有启用的效果器,直通
this.input.connect(this.output);
return;
}
// 串联所有启用的效果器
this.input.connect(activeSlots[0].effect.input);
for (let i = 0; i < activeSlots.length - 1; i++) {
activeSlots[i].effect.output.connect(activeSlots[i + 1].effect.input);
}
activeSlots[activeSlots.length - 1].effect.output.connect(this.output);
}
// 启用/禁用某个效果器
toggleEffect(name, enabled) {
const slot = this.slots.find(s => s.name === name);
if (slot) {
slot.enabled = enabled;
this._rebuildChain();
}
}
// 应用完整音效预设
async applyPreset(preset) {
// 启用/禁用效果器
Object.entries(preset.effects || {}).forEach(([name, enabled]) => {
this.toggleEffect(name, enabled);
});
// 设置 EQ
if (preset.eq) {
this.eq.applyPreset(preset.eq);
}
// 设置压缩器
if (preset.compressor) {
this.compressor.applyPreset(preset.compressor);
}
// 加载混响 IR
if (preset.reverbIR) {
await this.reverb.loadIR(preset.reverbIR);
this.reverb.setMix(preset.reverbMix || 0.3);
} else if (preset.reverbSynth) {
this.reverb.synthesizeIR(
preset.reverbSynth.duration,
preset.reverbSynth.decay
);
this.reverb.setMix(preset.reverbMix || 0.3);
}
// 设置延迟
if (preset.delay) {
this.delay.setDelayTime(preset.delay.time || 0.375);
this.delay.setFeedback(preset.delay.feedback || 0.4);
this.delay.setMix(preset.delay.mix || 0.3);
}
console.log('预设已应用:', preset.name);
}
// 连接音频元素
connectSource(audioEl) {
const src = this.ctx.createMediaElementSource(audioEl);
src.connect(this.input);
return src;
}
}
// ── 完整预设库 ─────────────────────────────────────────────
const EFFECT_PRESETS = {
clean: {
name: '清晰直通',
effects: {
distortion: false, eq: true, compressor: true,
chorus: false, delay: false, reverb: false,
},
eq: EQ_PRESETS.flat,
compressor: COMPRESSOR_PRESETS.gentle,
},
concert_hall: {
name: '音乐厅',
effects: {
distortion: false, eq: true, compressor: true,
chorus: false, delay: false, reverb: true,
},
eq: EQ_PRESETS.classical,
compressor: COMPRESSOR_PRESETS.mastering,
reverbSynth: { duration: 3.5, decay: 1.5 },
reverbMix: 0.45,
},
pop_vocal: {
name: '流行人声',
effects: {
distortion: false, eq: true, compressor: true,
chorus: true, delay: true, reverb: true,
},
eq: EQ_PRESETS.vocal,
compressor: COMPRESSOR_PRESETS.vocal,
reverbSynth: { duration: 1.5, decay: 2.5 },
reverbMix: 0.25,
delay: { time: 0.25, feedback: 0.3, mix: 0.2 },
},
lo_fi: {
name: 'Lo-Fi',
effects: {
distortion: true, eq: true, compressor: true,
chorus: true, delay: false, reverb: true,
},
eq: [
{ gain: 3 }, { gain: -2 }, { gain: 1 },
{ gain: -4 }, { gain: -6 },
],
compressor: COMPRESSOR_PRESETS.mastering,
reverbSynth: { duration: 1.0, decay: 3.0 },
reverbMix: 0.3,
},
rock_guitar: {
name: '摇滚吉他',
effects: {
distortion: true, eq: true, compressor: true,
chorus: false, delay: true, reverb: true,
},
eq: [
{ gain: 4 }, { gain: -2 }, { gain: 3 },
{ gain: 4 }, { gain: 2 },
],
compressor: COMPRESSOR_PRESETS.drums,
reverbSynth: { duration: 1.2, decay: 2.0 },
reverbMix: 0.2,
delay: { time: 0.375, feedback: 0.35, mix: 0.25 },
},
};使用方式
const audio = document.getElementById('audio');
const chain = new EffectChain(audioCtx);
chain.connectSource(audio);
chain.output.connect(audioCtx.destination);
// 应用预设
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const presetName = btn.dataset.preset;
await chain.applyPreset(EFFECT_PRESETS[presetName]);
});
});
// 实时调节 EQ
document.querySelectorAll('.eq-slider').forEach((slider, i) => {
slider.addEventListener('input', () => {
chain.eq.setBandGain(i, parseFloat(slider.value));
});
});
// 开关混响
document.getElementById('reverbToggle').addEventListener('change', (e) => {
chain.toggleEffect('reverb', e.target.checked);
});十一、本章知识图谱
小结
音频效果器的本质是信号处理算法——均衡器是频域滤波,压缩器是动态范围控制,混响是卷积或延迟网络,失真是非线性变换,调制效果是参数随时间变化的延迟。理解了这些底层逻辑,你就不再是"调参数的人",而是真正能根据听感判断"该用什么效果、调哪个参数、往哪个方向调"的工程师。
Web Audio API 提供的节点已经足以构建专业级的效果链。BiquadFilterNode 的七种滤波器类型覆盖了绝大多数 EQ 需求;DynamicsCompressorNode 的五个参数对应真实压缩器的核心控制;ConvolverNode 配合高质量 IR 文件,混响效果甚至可以媲美专业插件;WaveShaperNode 的自定义曲线让失真风格完全可编程。更复杂的需求,AudioWorklet 可以在独立线程中运行任意 DSP 算法。