Tone.js 完全停止所有播放声音

问题描述 投票:0回答:5

简而言之,按下按钮后,我想使用

PolySynth
Sequence
弹奏一些音符。如果用户反复按下按钮,我希望停止正在播放的内容,然后重新开始。

问题:无论我尝试什么,我都无法完全取消/静音之前播放的音符,以防序列再次启动(再次单击按钮)。这很可能是因为包络的衰减/维持。

我的合成器:

import { PolySynth } from 'tone'

const synth = new PolySynth(Synth, {
  oscillator: {
    type: 'sine4',
    volume: -6,
  },
  envelope: {
    attack: 0.01,
    decay: 0.5,
    sustain: 0.1,
    release: 1,
  },
}).toDestination()
synth.maxPolyphony = 4 // max notes playing at a time, not sure if necessary

我的顺序:

import { Sequence } from 'tone'

// Play the 2 notes individually then play them together
const notes = [
  { note: 'C4', duration: '8n' },
  { note: 'G4', duration: '8n' },
  { note: ['C4', 'G4'], duration: '4n' }
]

// The sequence that should play the notes after one another
const sequence = new Sequence({
  subdivision: '8n',
  loop: false,
  events: notes,
  callback: (time, note) => synth.triggerAttackRelease(note.note, note.duration, time),
})

我的玩法是,这是一个事件处理程序:

import { start, Transport } from 'tone'

// Event handler simply attached to a button's onClick
function onButtonClicked() {
  // Call whatever this start is, doc says it can only happen in an event handler
  start()
  
  // Try everything to kill current sound
  Transport.cancel()
  Transport.stop()

  // Start it again
  Transport.start()
  sequence.start()
}

如何在开始播放之前完全消除所有声音(如果有的话)?

javascript typescript audio tone.js
5个回答
3
投票

简短回答

仔细思考一下,如果我理解正确的话,这实际上是有意的行为。 您正在 Synth(基本上是一个 AudioWorkletNode)上触发一个音符。因此,一旦音符触发合成器,该音符就会消失。阻止该音符播放的唯一方法是将合成器本身静音。

长答案

在您所说的评论中,您可能在概念上遗漏了一些东西,我认为您的方向是正确的。

让我们考虑一下如何用 MIDI 生成声音。

  1. 您正在将 Synth(它获取 MIDI 音符并生成声音)连接到输出
  2. 您正在传输上安排一些 MIDI 音符
  3. 您开始运输
  4. 一旦走带达到音符的预定时间,该 MIDI 值将被发送到合成器。
  5. 由于 Synth 基本上是一个带有包络生成器的 AudioWorkletNode,因此 Synth 会获取 MIDI 音符并触发内部声音生成(通过包络)。因此,特定时间点的 MIDI 音符会触发特定长度的声音生成(这将是 ADS 部分)。即使在您的示例中 MIDI 音符持续时间仅为 1 毫秒,声音生成也将持续至少 1.001 秒(释放加上 1 毫秒 MIDI 持续时间)。让我们进一步分解一下:
    • MIDI 音符在假想的传输时间线上有一个起点和终点。
    • Start 触发 Envelope 的 ADS 部分。
    • End 触发包络的 R 部分。
    • 一旦您的 MIDI 音符触发包络,就会生成声音。

那么当你停止运输或序列本身时,会发生什么? 如果 MIDI 音符已触发包络,则包络将接收 MIDI 结束触发器并触发释放包络。

因此,您的合成器总会有拖尾声音,因为 MIDI 音符并不能确定您的合成器的起点和终点,而是触发您的包络的部分内容。所以实际上你的合成器创造了声音,它既不依赖于传输,也不可能依赖于传输。

希望这个解释对您有所帮助。如果我误解了你,我很乐意纠正。


1
投票

音符会继续播放,因为走带在音符触发之后、释放之前停止。因此,解决方案是在按下停止按钮时触发所有音符的释放。我在使用 tambien/piano(基于 Tonejs)时遇到了类似的问题。

    Tone.Transport.toggle()
    if (Tone.Transport.state === 'stopped') {
      for (let i=9; i<97; i++) {
        piano.keyUp({midi: i}, '+0')
      }
    }

1
投票

该解决方案可能很难在较长的序列中实现,但在您的情况下它应该可行。我挑战过类似的问题并且成功了。

polySynth 的问题是你只能添加演奏的音符。但使用普通合成器,可以通过用空音符覆盖播放的音符来“消除声音”。

// It would create continuously playing note.
synth = synth || new Tone.Synth().toMaster();
synth.triggerAttackRelease(noteToPlay);

// It would mute the sound.
synth = synth || new Tone.Synth().toMaster();
synth.triggerAttackRelease();

可以同时播放许多单个合成器,因此您可以手动创建复音合成器。

synth1 = synth1 || new Tone.Synth().toMaster();
synth1.triggerAttackRelease(noteToPlay);
synth2 = synth2 || new Tone.Synth().toMaster();
synth2.triggerAttackRelease(noteToPlay2);
synth3 = synth3 || new Tone.Synth().toMaster();
synth3.triggerAttackRelease(noteToPlay3);

构建序列会更复杂,但是您可以通过在播放函数的开头添加“soundKiller”来使播放序列静音。重要的是:不要声明序列,然后通过调用“Transport”来播放它,而只需在事件处理程序中播放音符(因为 - 正如另一个人已经写的 - 调用它后无法停止传输)。


0
投票

只需调用 PolySynth 对象上的releaseAll()方法即可:

polySynth.releaseAll()

这将停止播放所有当前触发的音符。


-2
投票

使用Tone.Transport.pause()

如果您有“暂停-播放”类别的播放/暂停按钮,请参阅下面的示例:

$( '.pause-play' ).on('click', function(e) {

    if (Tone.Transport.state === "paused" ) {  
          Tone.Transport.start("+0.1") })
    else {
          Tone.Transport.pause()  }
    }
)
© www.soinside.com 2019 - 2024. All rights reserved.