You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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但所有音符同时播放,需要实现循环加等待依次播放的效果。

问题根源

  1. 浏览器无原生wait()函数,同步while循环会瞬间执行完所有操作,导致所有音符的start/stop几乎同时触发,只能听到单个音符
  2. 同一个振荡器(Oscillator)实例调用stop()后无法重新启动,必须每次创建新实例
  3. 直接用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:40:06