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

Tone.js completely stop all playing sounds

简而言之,我想使用 PolySynthSequence 来演奏几个音符。如果用户反复按下按钮,我希望停止正在播放的内容,然后重新开始。这很可能是因为信封的 decay/sustain.

问题:无论我尝试什么,我都无法完全cancel/silence之前演奏的音符,以防序列再次开始(再次单击按钮)。

我的合成器:

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()
}

我怎样才能在开始播放之前完全消除所有声音(如果有的话)?

简答

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

长答案

在你的评论中,你可能在概念上遗漏了一些东西,我认为你在正确的轨道上。

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

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

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

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

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

使用Tone.Transport.pause()

如果您有一个 class 为 'pause-play' 的 play/pause 按钮,请参见下面的示例:

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

    if (Tone.Transport.state === "paused" ) {  
          Tone.Transport.start("+0.1") })
    else {
          Tone.Transport.pause()  }
    }
)

音符一直在播放,因为传输在音符被触发后和释放前停止。因此,一种解决方案是在您按下停止按钮时触发所有音符的释放。我在使用 tambien/piano(基于 Tonejs)时遇到了类似的问题。

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