使用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
相关产品推荐
相关产品推荐

