如何使用Web Audio API生成钢琴、长笛等乐器类音色?
如何使用Web Audio API生成钢琴、长笛等乐器类音色?
嘿,你现在用的正弦波是最纯净的「基础款」声音,但钢琴、长笛这类真实乐器的音色可复杂多啦——它们的差异主要来自泛音(谐波)结构、音量/频率包络,还有不同的滤波特性。我来一步步帮你改造代码,做出接近真实乐器的声音👇
先搞懂核心:乐器音色的3个关键
- 泛音(谐波):除了你设置的基频
freq,真实乐器会同时发出多个比基频高的谐波(比如2倍、3倍基频),不同乐器的谐波比例不一样,音色就天差地别 - ADSR包络:音量随时间的变化曲线,分四个阶段:
- Attack:从静音到最大音量的时间(钢琴快,长笛慢)
- Decay:从最大音量降到持续音量的时间
- Sustain:持续阶段的音量比例(钢琴Sustain低,长笛Sustain高)
- Release:松开按键后音量降到静音的时间
- 滤波:模拟乐器发声后的高频衰减(比如钢琴的高频泛音会随时间慢慢消失)
1. 实现钢琴音色
钢琴的特点:起音极快,然后快速衰减到一个较低的持续音量,泛音非常丰富(基频+多倍谐波)。我们可以叠加多个振荡器模拟泛音,再用ADSR包络控制音量:
// 先确保你已经初始化了AudioContext const ctx = new (window.AudioContext || window.webkitAudioContext)(); function playPianoTone(freq, duration, delay = 0) { const startTime = ctx.currentTime + delay; const endTime = startTime + duration; // 钢琴的泛音组合:基频为主,叠加2/3/4倍泛音(音量依次降低) const harmonics = [ { freqRatio: 1, gain: 0.3 }, // 基频,音量最大 { freqRatio: 2, gain: 0.15 }, // 2倍泛音 { freqRatio: 3, gain: 0.08 }, // 3倍泛音 { freqRatio: 4, gain: 0.03 }, // 4倍泛音 ]; // 创建主增益节点,统一控制整体ADSR包络 const masterGain = ctx.createGain(); masterGain.connect(ctx.destination); // 叠加每个泛音的振荡器 harmonics.forEach(harmonic => { const osc = ctx.createOscillator(); osc.type = "sine"; // 用正弦波叠加泛音,更接近钢琴的柔和质感 osc.frequency.value = freq * harmonic.freqRatio; const oscGain = ctx.createGain(); oscGain.gain.setValueAtTime(harmonic.gain, startTime); osc.connect(oscGain).connect(masterGain); osc.start(startTime); osc.stop(endTime); }); // 钢琴的ADSR包络: Attack极短,快速衰减后保持低音量,释音平缓 masterGain.gain.setValueAtTime(0, startTime); masterGain.gain.linearRampToValueAtTime(1, startTime + 0.01); // 0.01秒快速起音 masterGain.gain.linearRampToValueAtTime(0.1, startTime + 0.01 + 0.2); // 0.2秒衰减到低音量 masterGain.gain.setValueAtTime(0.1, endTime); masterGain.gain.linearRampToValueAtTime(0, endTime + 0.3); // 0.3秒释音 } // 调用示例:弹一个440Hz的A音,持续1秒 playPianoTone(440, 1);
2. 实现长笛音色
长笛是木管乐器,特点是起音平缓(不会突然炸响),持续阶段音量稳定,泛音以奇次谐波为主(1、3、5倍频),音色更通透。我们可以用少量泛音+缓慢的ADSR包络,再加个带通滤波模拟管乐的共鸣:
function playFluteTone(freq, duration, delay = 0) { const startTime = ctx.currentTime + delay; const endTime = startTime + duration; // 长笛泛音:基频为主,叠加3/5倍奇次泛音 const harmonics = [ { freqRatio: 1, gain: 0.4 }, { freqRatio: 3, gain: 0.12 }, { freqRatio: 5, gain: 0.05 }, ]; const masterGain = ctx.createGain(); masterGain.connect(ctx.destination); // 加一个带通滤波,模拟长笛的共鸣特性,让音色更集中 const bandpassFilter = ctx.createBiquadFilter(); bandpassFilter.type = "bandpass"; bandpassFilter.frequency.value = freq; bandpassFilter.Q.value = 1; // Q值越小,滤波带宽越宽 bandpassFilter.connect(masterGain); harmonics.forEach(harmonic => { const osc = ctx.createOscillator(); osc.type = "sine"; osc.frequency.value = freq * harmonic.freqRatio; const oscGain = ctx.createGain(); oscGain.gain.setValueAtTime(harmonic.gain, startTime); osc.connect(oscGain).connect(bandpassFilter); osc.start(startTime); osc.stop(endTime); }); // 长笛的ADSR包络:平缓起音,稳定持续,缓慢释音 masterGain.gain.setValueAtTime(0, startTime); masterGain.gain.linearRampToValueAtTime(1, startTime + 0.3); // 0.3秒平缓起音 masterGain.gain.linearRampToValueAtTime(0.8, startTime + 0.3 + 0.1); // 轻微衰减到稳定音量 masterGain.gain.setValueAtTime(0.8, endTime); masterGain.gain.linearRampToValueAtTime(0, endTime + 0.5); // 0.5秒缓慢释音 } // 调用示例:弹一个349Hz的F音,持续2秒 playFluteTone(349, 2);
进阶小技巧
- 要是觉得叠加多个振荡器太麻烦,可以直接用
osc.type = "sawtooth"或者"square",这些波形本身就包含丰富泛音,再用滤波和ADSR包络调整,也能快速模拟不同乐器 - 钢琴的释音阶段可以加一点「延音」效果:在stop之后,让增益缓慢下降而不是立刻切断
- 可以把ADSR包络封装成一个单独的函数,比如
createADSRGain(ctx, startTime, endTime, attack, decay, sustainLevel, release),这样不同乐器可以复用参数
怎么样,你可以先试试这些代码,调整泛音的比例、ADSR的时间参数,就能调出更接近你想要的音色啦!有问题再喊我~




