javascript - 将 `Tone.PolySynth()` 与 `Sequence` 方法一起使用时,如何避免音频消失?

标签 javascript reactjs typescript web-audio-api tone.js

上下文

我一直在尝试构建类似于 official example 的步进音序器在 Tone.js 文档中。然而,我没有使用 Players 方法播放 MP3 文件,而是想实现 PolySynth 来探索各种声音,例如 Jon Oliver's example。 .

在查看了一些使用不同方法的演示之后——比如 this drum sequencer (使用带有 Players 的 MP3),this step sequencer (使用多个单声道 Synth)——我设法构建了一个基本的功能音序器。 My working demo in CodeSandbox here .

问题

虽然它按预期工作——即打开/关闭时播放单个音符,按定义的速度循环播放序列,切换播放/暂停模式等——复音合成器的声音在循环几次后开始有点失真,很快就完全静音 .

即使在音频消失后,Sequence 调用似乎仍在继续并且所有其他功能都按预期工作 - 但它变得明显卡顿和滞后。

我怀疑这是我处理 Sequence 调用并无意中使内存过载的方式,尽管我没有看到任何明显的控制台错误。

我尝试了什么

  • 使用带有回调的 Transport.scheduleRepeat:(类似于此 Medium article 导致相同的滞后、静音行为
  • 使用 async 调用来处理 Sequence 播放切换:(类似于 this CodeSandbox example 导致相同的延迟、静音行为
  • 使用 Array.forEachfor 循环手动循环序列:没有 Tone 时无法保持时间同步 时序控制
  • 使用 ref 跟踪 Sequence,同时连续处理先前的节拍:(在 this CodeSandbox example 上修改导致相同的滞后,静音行为。这是当前工作代码中的内容。

代码片段

它是用 ReactTypeScript 构建的。主要的 App 部分如下所示。 You can view the full working code at this CodeSandbox .

我是 Tone.js 的完全初学者,但相当精通 React。我们将不胜感激任何指导和见解。

我知道应用程序的数据处理、DOM 处理和其他方面也很笨拙,希望它们不会成为导致此问题的原因。

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;

最佳答案

synth 对象在每次渲染时创建。您可以将其移出 App(或 useRef),一切正常。

另一件事我注意到这里没有依赖列表:

React.useEffect(() => {
  Tone.Transport.loop = false;
  Tone.Transport.on("stop", () => setCurrentBeat(0));
}, []); // <--

附言多么好的项目!

关于javascript - 将 `Tone.PolySynth()` 与 `Sequence` 方法一起使用时,如何避免音频消失?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73439543/

相关文章:

JavaScript:仅当单词包含整数 0-9 时,如何从字符串中删除最后一个单词?

javascript - 如何删除子子文档中的元素mongoosejs

node.js - JSDoc:如何包含多个 .md 文件

javascript - ReactJS react 路由器 RoutingContext

javascript - react 如何检测状态/ Prop 的变化?

typescript - 无法在 typescript 中导入svg文件

angular - 如何使用 Jasmine 为私有(private)方法编写 Angular/TypeScript 的单元测试

javascript - 如何删除边框顶部和 div 之间的间距

javascript - 可以在本地站点上强制使用 cookie 吗?

javascript - 改变 ionic 清新器的位置