Tonejs / Tone.js

A Web Audio framework for making interactive music in the browser.
https://tonejs.github.io
MIT License
13.27k stars 965 forks source link

Disposing Synth before one of its notes' release throws an error Uncaught Error #1174

Closed bryceAebi closed 2 months ago

bryceAebi commented 1 year ago

Description Here is the error:

Debug.ts:8 Uncaught Error: Synth was already disposed
    at assert (Debug.ts:8:1)
    at PolySynth._scheduleEvent (PolySynth.ts:241:1)
    at Object.callback (PolySynth.ts:253:1)
    at Context._timeoutLoop (Context.ts:560:1)
    at Context.emit (Emitter.ts:99:1)

And actually, this error gets thrown in an infinite loop.

To Reproduce To reproduce, play a long-running note with PolySynth, then dispose of the synth before the note's scheduled release. Upon the note's scheduled release, the above error will be thrown because indeed the Synth was disposed.

Expected behavior Desired behavior would be for all trigger and release events to be properly disposed of along with the Synth.

What I've tried I tried Tone.getTransport().stop() and Tone.getTransport().clear() and droneSynth.releaseAll() to no avail.

Additional Context My application does need to play very long synth notes (of indeterminate lengths). They set a tonal context for piano notes playing simultaneously. I also need my web app to be a single page app for some niche reasons related to iOS safari, so I need to be able to clean up after various instruments I spin up during usage of the web page.

bryceAebi commented 1 year ago

As a workaround, I also tried synth.sync() after its creation. This avoids causing the error to be thrown above because I can stop and clear the Transport before disposing of the synth.

But after I dispose of the synth and create a new one, there doesn't appear to be an easy way to play a new Tone.Part as the same audio context is still being used. Tone.Part passes the audio context time to the callback, but the synth is synced with the Transport time, so it does not play the notes at the right time.

// droneSynth is created and then I call droneSynth.sync() before starting any audio playback

  new Tone.Part((time, value) => { // time is the audiocontext time
    droneSynth?.triggerAttackRelease(
      value.registeredNote,
      value.duration,
      time, // time is interpreted as the TransportTime here though
    )
  }, notes).start(0) // this start value refers to the TransportTime

Ideally in the code snippet above, I could put +0 for the time (as in "play from the current TransportTime") but it appears that (there may be another bug here?) the + notation causes the synth to revert to using the AudioContext time, not the Transport time

tambien commented 2 months ago

From the original post, i think the error is the desired behavior. Instances aren't meant to be interacted with after they've been disposed, so the fact that it throws an error seems like what it should do. Maybe instead of disposing the synth you can just call releaseAll()? If you need the synth to be entirely silent when the Transport is started, you could also try running it through a gain node and silencing the gain node when the transport is stopped which will squash any release sound that your synth might still be making.