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

Tone.js音频序列首次播放后时间间隔不一致问题排查

问题分析与解决方案

核心问题

  1. 定时器未被正确清除:sequenceInterval是play()函数内的局部常量,playSequence()无法访问它,导致第一次播放结束后定时器没有被清除。后续点击按钮会创建新的定时器,多个定时器同时触发播放,造成节奏混乱。
  2. 浏览器定时器精度不足:setInterval依赖浏览器的事件循环,精度有限,无法保证音频播放的精准时间间隔,偶尔首次播放也会出现偏差。
  3. 全局变量冲突:全局变量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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 13:32:36