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

Tone.js序列暂停后重放出现时序错乱、音量异常问题求助

问题:Tone.js音序器暂停后重播出现爆音、时序错乱

我用Tone.js构建音序器,点击播放按钮可触发简单序列播放,首次播放正常,但暂停后再次播放时,音符会随机出现爆音、时序错乱或音量降低的情况。此前已解决重复创建多个synth导致同音符叠加播放的问题,但当前序列播放仍无法保持稳定。问题出在sequencer/src/components/controls/Controls.js文件,播放按钮同时兼具播放和暂停功能。

相关代码:

const Controls = () => {
  const [started, setIsStarted] = useState(false)
  const [isPlaying, setIsPlaying] = useState(false)
  const [tempo, setTempo] = useTempoHook('100')
  const [subDivision, setSubDivision] = useSubDivisionHook('1/4')
  const [sequence, setSequence] = useState(false)
  
  useEffect(() => {
    console.log('Is Playing: ', isPlaying)
    console.log('Tempo: ', tempo)
    console.log('SubDivision: ', subDivision)
  })

  Tone.Transport.bpm.value = tempo

  const createSequence = () => {
    const synth = new Tone.Synth().toDestination()
    const sequence = new Tone.Sequence((time, note) => {
      synth.triggerAttackRelease(note, 0.1, time)
    }, ['C3', 'D3', 'E3', 'F3'], '4n')
    sequence.start(0)
    return sequence
  }

  const startTone = async () => {
    await Tone.start()
    setIsStarted(true)
  }

  const stopSequence = () => {
    Tone.Transport.stop()
    sequence.stop()
    sequence.dispose()
    setIsPlaying(false)
  }
  const startSequence = () => {
    Tone.Transport.start()
    setSequence(createSequence())
    setIsPlaying(true)
  }

  const start = () => {
    if (Tone.Transport.state === 'started') {
      stopSequence()
    } else {
      startSequence()
    }
    console.log(Tone.Transport.state)

  }

  const stop = () => {
    // setIsPlaying(false)
  }
  
  return (
    <div>
      { !started && <ButtonControl icon={'Start'} color={'blue'} handler={startTone} /> }
      <AudioPlayBackManager playAudio={start} stopAudio={stop} />
      <TempoSliderControl tempo={tempo} setTempo={setTempo} />
      <SubDivisionControl setSubDivision={setSubDivision} />
    </div>
  )
}

问题根源

  1. 每次重播重复创建实例:startSequence中每次调用createSequence都会新建Synth和Sequence,旧实例未彻底清理,引发音频资源冲突。
  2. Transport与Sequence控制逻辑脱节:暂停时先停止Transport再处理Sequence,时序混乱;重播时重新创建Sequence并启动Transport,易导致不同步。
  3. React状态异步更新隐患:用useState保存Sequence实例,状态更新的异步特性可能导致操作时拿不到最新实例,销毁不彻底。

修复方案

修改后的Controls.js代码

import { useState, useEffect, useRef } from 'react';

const Controls = () => {
  const [started, setIsStarted] = useState(false);
  const [isPlaying, setIsPlaying] = useState(false);
  const [tempo, setTempo] = useTempoHook('100');
  const [subDivision, setSubDivision] = useSubDivisionHook('1/4');
  
  // 用ref保存实例,规避React状态异步问题
  const synthRef = useRef(null);
  const sequenceRef = useRef(null);

  // 仅初始化一次Synth和Sequence
  useEffect(() => {
    synthRef.current = new Tone.Synth().toDestination();
    sequenceRef.current = new Tone.Sequence((time, note) => {
      synthRef.current.triggerAttackRelease(note, 0.1, time);
    }, ['C3', 'D3', 'E3', 'F3'], '4n');

    // 组件卸载时清理资源
    return () => {
      sequenceRef.current?.dispose();
      synthRef.current?.dispose();
      Tone.Transport.stop();
    };
  }, []);

  // 监听tempo变化,实时更新Transport速度
  useEffect(() => {
    Tone.Transport.bpm.value = tempo;
  }, [tempo]);

  // 监听subDivision变化,更新序列间隔
  useEffect(() => {
    if (sequenceRef.current) {
      sequenceRef.current.interval = subDivision;
    }
  }, [subDivision]);

  const startTone = async () => {
    await Tone.start();
    setIsStarted(true);
  };

  const stopSequence = () => {
    sequenceRef.current.stop();
    Tone.Transport.stop();
    setIsPlaying(false);
  };

  const startSequence = () => {
    Tone.Transport.start();
    sequenceRef.current.start(0);
    setIsPlaying(true);
  };

  const togglePlay = () => {
    Tone.Transport.state === 'started' ? stopSequence() : startSequence();
  };

  return (
    <div>
      {!started && <ButtonControl icon={'Start'} color={'blue'} handler={startTone} />}
      <AudioPlayBackManager playAudio={togglePlay} stopAudio={stopSequence} />
      <TempoSliderControl tempo={tempo} setTempo={setTempo} />
      <SubDivisionControl setSubDivision={setSubDivision} />
    </div>
  );
};

export default Controls;

核心优化点

  • 复用实例:用useRef保存Synth和Sequence,避免每次重播重复创建,减少资源冲突。
  • 统一控制逻辑:暂停时先停Sequence再停Transport,重播时先启动Transport再启动Sequence,保证时序同步。
  • 实时响应配置变化:添加监听逻辑,tempo和subDivision修改时自动更新对应参数,无需重建序列。
  • 资源清理:组件卸载时销毁实例,释放音频资源,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 03:53:18