Tone.js音频序列首次播放后时间间隔不一致问题排查
问题分析与解决方案
核心问题
- 定时器未被正确清除:
sequenceInterval是play()函数内的局部常量,playSequence()无法访问它,导致第一次播放结束后定时器没有被清除。后续点击按钮会创建新的定时器,多个定时器同时触发播放,造成节奏混乱。 - 浏览器定时器精度不足:
setInterval依赖浏览器的事件循环,精度有限,无法保证音频播放的精准时间间隔,偶尔首次播放也会出现偏差。 - 全局变量冲突:全局变量
i会被多个并发的定时器函数修改,导致音符索引错乱,播放顺序异常。
修复方案一:修正定时器逻辑
将sequenceInterval改为全局变量,且每次调用play()时先清除之前的定时器,避免叠加:
const synth = new Tone.Synth().toDestination(); let duration = "8n"; let sequence = ["C3", "A3", "B3"]; let i = 0; let sequenceInterval; // 改为全局变量 function playSequence() { if (Tone.context.state !== "running") { Tone.start(); } synth.triggerAttackRelease(sequence[i], duration); i++; if (i >= sequence.length) { clearInterval(sequenceInterval); // 现在能正确访问并清除定时器 } } function play() { i = 0; clearInterval(sequenceInterval); // 先清除之前的定时器,避免叠加 sequenceInterval = setInterval(playSequence, 600); }
修复方案二:使用Tone.js原生调度(推荐)
Tone.js内置了Tone.Sequence专门用于音频序列播放,基于音频上下文时间调度,精度远高于浏览器定时器,且无需手动管理索引和定时器:
const synth = new Tone.Synth().toDestination(); let duration = "8n"; let sequence = ["C3", "A3", "B3"]; // 创建序列,第一个参数是每个音符的回调,第二个是时间间隔(这里用"8n"对应八分音符,也可以用毫秒) const noteSequence = new Tone.Sequence((time, note) => { synth.triggerAttackRelease(note, duration, time); }, sequence, "8n"); function play() { if (Tone.context.state !== "running") { Tone.start(); } noteSequence.stop(); // 先停止之前的播放 noteSequence.start(0); // 从开头重新播放 }
补充说明
- 使用Tone.js原生调度时,时间间隔可以用音符时值(如"4n"四分音符、"8n"八分音符),会自动跟随Tone.js的全局BPM(默认120),比固定毫秒更符合音乐逻辑。
- 每次播放前调用
stop()可以确保之前的序列播放被终止,避免叠加。
内容的提问来源于stack exchange,提问作者darkplasma
相关产品推荐
相关产品推荐

