JavaScript音频播放问题:无法循环切换音符,仅发出单一音调
解决Web Audio API按频率列表依次播放音符的问题
问题描述
想实现基于频率列表依次播放音符,同时支持切换波形,但当前代码只能发出单一音符,无法循环播放所有音符。原代码如下:
function play_tune(frequencies, oscillator_types){ var context = new AudioContext(); if (typeof oscillator_types == 'undefined'){ oscillator_types = ['sine']; } if (typeof oscillator_types_index == 'undefined'){ oscillator_types_index = 0; } var oscillator = context.createOscillator(); frequencies_index = 0 while (frequencies_index != frequencies.length){ if (oscillator_types_index >= oscillator_types.length){ oscillator_types_index = 0; } oscillator.type = oscillator_types[oscillator_types_index]; oscillator.frequency.value = frequencies[frequencies_index]; oscillator.connect(context.destination); oscillator.start(); wait(1); oscillator.stop(); oscillator_types_index = oscillator_types_index + 1; frequencies_index = frequencies_index + 1; } } play_tune([250, 200, 500, 1000, 1500, 350, 460, 720, 2000])
试过调整代码顺序、查阅资料、将计时器设为0(无声音)、使用setTimeout但所有音符同时播放,需要实现循环加等待依次播放的效果。
问题根源
- 浏览器无原生
wait()函数,同步while循环会瞬间执行完所有操作,导致所有音符的start/stop几乎同时触发,只能听到单个音符 - 同一个振荡器(Oscillator)实例调用
stop()后无法重新启动,必须每次创建新实例 - 直接用
setTimeout不处理时间偏移,会导致所有定时器几乎同时触发,音符重叠
改进方案1:精准时间调度(推荐)
利用AudioContext的currentTime实现精准的时间控制,每个音符创建独立振荡器:
function play_tune(frequencies, oscillator_types = ['sine'], noteDuration = 1) { const context = new AudioContext(); let currentTime = context.currentTime; let oscTypeIndex = 0; frequencies.forEach(freq => { // 为每个音符创建新的振荡器 const oscillator = context.createOscillator(); // 循环切换波形 oscillator.type = oscillator_types[oscTypeIndex % oscillator_types.length]; oscillator.frequency.value = freq; oscillator.connect(context.destination); // 精准控制播放/停止时间 oscillator.start(currentTime); oscillator.stop(currentTime + noteDuration); // 更新时间轴和波形索引 currentTime += noteDuration; oscTypeIndex++; }); } // 调用示例:播放频率列表,用sine/square两种波形,每个音符播放1秒 play_tune([250, 200, 500, 1000, 1500, 350, 460, 720, 2000], ['sine', 'square']);
改进方案2:异步顺序播放(支持流程控制)
如果需要中途暂停/继续等交互,用async/await结合Promise实现:
async function play_tune_async(frequencies, oscillator_types = ['sine'], noteDuration = 1) { const context = new AudioContext(); let oscTypeIndex = 0; for (const freq of frequencies) { await new Promise(resolve => { const oscillator = context.createOscillator(); oscillator.type = oscillator_types[oscTypeIndex % oscillator_types.length]; oscillator.frequency.value = freq; oscillator.connect(context.destination); oscillator.start(); setTimeout(() => { oscillator.stop(); oscillator.disconnect(); // 清理资源 resolve(); }, noteDuration * 1000); oscTypeIndex++; }); } } // 调用示例 play_tune_async([250, 200, 500, 1000, 1500, 350, 460, 720, 2000], ['sine', 'square']);
关键优化点
- 每个音符使用独立的振荡器实例,解决旧实例无法重启的问题
- 用AudioContext的
currentTime或setTimeout时间偏移实现依次播放 - 取余操作
oscTypeIndex % oscillator_types.length实现波形循环切换 - 支持自定义音符时长和波形列表,调用更灵活
内容的提问来源于stack exchange,提问作者Huw Piggin
相关产品推荐
相关产品推荐

