使用Tone.PolySynth结合Sequence时如何避免音频消失?
Tone.js PolySynth步进音序器声音失真/静音问题
背景
我基于Tone.js文档中的官方步进音序器示例搭建项目,没有用Players方法播放MP3文件,而是采用PolySynth来实现多样音色探索。参考了多款鼓机、步进音序器的实现方案后,完成了一个基础可用的步进音序器,支持切换音符开关播放单音、按设定速度循环序列、切换播放/暂停模式等功能。
问题
音序器基础功能正常,但PolySynth的声音在循环几次后会开始失真,很快完全静音。音频消失后,Sequence调用似乎仍在继续,其他功能也能正常操作,但程序会出现明显卡顿、延迟。我怀疑是Sequence的处理方式有误,导致内存意外过载,但控制台没有输出明显错误信息。
已尝试方案
- 使用带回调的
Transport.scheduleRepeat:仍出现卡顿、静音问题 - 用async调用处理Sequence播放切换:问题依旧
- 用
Array.forEach或for循环手动循环序列:无法保持Tone的时序同步 - 用ref跟踪Sequence并持续销毁前一个节拍实例:仍存在卡顿、静音问题(当前代码采用此方案)
代码片段
项目基于React和TypeScript开发,以下是主App组件的核心代码:
function App() { type Loop = { dispose: any; }; const [isPlaying, setIsPlaying] = React.useState(false); const [currentBeat, setCurrentBeat] = React.useState(0); const [currentBPM, setCurrentBPM] = React.useState(120); const [noteMatrix, setNoteMatrix] = React.useState(defaultNoteMatrix); const [currentScale, setCurrentScale] = React.useState(SCALES.major); const synth = new Tone.PolySynth().toDestination(); const loop = React.useRef<Loop | null>(null); const noteMap = noteMatrix.map((beat) => beat .map((note, i) => { return { note: currentScale[i], on: note }; }) .filter((note) => note.on) .map((item) => item.note) ); const playSingleNote = (note = "") => synth.triggerAttackRelease(note, "16n"); const tick = (e: React.MouseEvent<HTMLButtonElement>): void => { const currentBeat = parseFloat(e.currentTarget.dataset.beat!); const selectedNote = parseFloat(e.currentTarget.dataset.index!); const isOn = e.currentTarget.dataset.on === "1" ? 0 : 1; const updatedMatrix = [...noteMatrix]; playSingleNote(currentScale[selectedNote]); updatedMatrix[currentBeat][selectedNote] = isOn; setNoteMatrix(updatedMatrix); }; const reset = useCallback(() => { setNoteMatrix([ [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat], [...emptyBeat] ]); setIsPlaying(false); Tone.Transport.stop(); }, []); React.useEffect(() => { Tone.Transport.loop = false; Tone.Transport.on("stop", () => setCurrentBeat(0)); }); React.useEffect(() => { Tone.Transport.bpm.value = currentBPM; }, [currentBPM]); React.useEffect(() => { if (loop.current) { loop.current.dispose(); } loop.current = new Tone.Sequence( (time, beat) => { setCurrentBeat(beat); synth.triggerAttackRelease(noteMap[beat], "16n", time); }, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "16n" ).start(0); }, [noteMap, isPlaying]); const togglePlay = React.useCallback(() => { Tone.context.resume(); Tone.Transport.toggle(); setIsPlaying((isPlaying) => !isPlaying); }, []); return ( <> <GridStyle> <div className="beat"> {currentScale.map((note, i) => ( <div className="head" key={i}> {note} </div> ))} </div> {noteMatrix.map((beat, i) => ( <div className="beat" key={i} data-active={currentBeat === i ? 1 : 0}> {currentScale.map((note, j) => ( <button className="note" key={j} onClick={tick} data-note={note} data-on={beat[j]} data-beat={i} data-index={j} /> ))} </div> ))} </GridStyle> <button onClick={togglePlay}>{isPlaying ? "Stop" : "Play"}</button> <button onClick={reset}>Clear</button> Scale <select name="scale" id="scale" defaultValue="major" onChange={(e) => setCurrentScale(SCALES[e.currentTarget.value])} > {["major", "minor", "suspended"].map((scale) => ( <option value={scale} key={scale}> {scale} </option> ))} </select> BPM <select name="bpm" id="bpm" onChange={(e) => setCurrentBPM(parseFloat(e.currentTarget.value))} defaultValue={currentBPM} > {[80, 100, 120, 140, 160, 180, 200].map((bpm) => ( <option value={bpm} key={bpm}> {bpm} </option> ))} </select> </> ); } export default App;
我是Tone.js新手,但熟悉React,希望能得到相关指导和解决思路。我知道应用的数据处理、DOM处理部分不够优雅,但希望这些不是问题根源。
内容的提问来源于stack exchange,提问作者Bumhan Yu
相关产品推荐
相关产品推荐

