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

使用ToneJS开发React鼓机时播放音量波动问题排查

问题

在React应用中使用ToneJS开发鼓机/采样器,已录制样本的起音和释音时间并存储在sampleData状态对象中,但播放时触发的样本音量会随times数组中对象数量异常变化:单个实例音量正常,两个实例时第一个播放音量翻倍、第二个减半,且该现象随实例增加愈发严重。

播放调度代码如下:

useEffect(() => {
  if (isPlaying && sampleData.times.length > 0) {
    sampleData.times.forEach(({ startTime, duration }) => {
      if (duration) {
        Tone.Transport.schedule((time) => {
          sampler.current?.triggerAttackRelease("C4", duration, time, 1);
        }, startTime);
      }
    });
  }
}, [isPlaying, sampler, sampleData]);

已确认每次播放的sampler实例参数(如力度、音量)未变化,但仍出现音量波动。

问题成因及解决思路

核心成因

问题出在Tone.Transport的调度任务未被清理,导致重复注册:

  • 你的useEffect依赖包含isPlaying、sampler、sampleData,每当这些状态更新时,useEffect会重新执行,进而重复遍历sampleData.times,为同一个startTime多次注册相同的播放调度。
  • 同一个时间点的调度任务被多次触发时,triggerAttackRelease会重复播放同一个样本,多个音频实例叠加会直接导致音量翻倍;后续出现的音量减半,是音频输出达到系统上限后触发的自动削峰/压缩,或是多次调度的时序冲突引发的音频叠加抵消。
  • 举个例子:第一次isPlaying变为true时注册了一轮调度,当新增时间项导致sampleData更新,useEffect会再次执行,把旧的时间项和新的时间项重新注册一遍,旧时间点的播放任务就会被多次执行,音量叠加。

解决方法

在useEffect的清理函数中,取消所有已注册的调度任务,避免重复触发:

useEffect(() => {
  const scheduledTasks = [];
  if (isPlaying && sampleData.times.length > 0) {
    sampleData.times.forEach(({ startTime, duration }) => {
      if (duration) {
        // 保存调度任务ID
        const taskId = Tone.Transport.schedule((time) => {
          sampler.current?.triggerAttackRelease("C4", duration, time, 1);
        }, startTime);
        scheduledTasks.push(taskId);
      }
    });
  }

  // 组件卸载或依赖更新时,清除所有注册的调度任务
  return () => {
    scheduledTasks.forEach(taskId => Tone.Transport.clear(taskId));
  };
}, [isPlaying, sampler, sampleData]);

内容的提问来源于stack exchange,提问作者Ben Doggett

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:35:58