Tone.js icon indicating copy to clipboard operation
Tone.js copied to clipboard

Differences between `Tone.context` and `Tone.getContext()`. How to properly instantiate and dispose the entire Tone instance?

Open satelllte opened this issue 2 years ago • 7 comments

Describe the bug

Hi 👋🏻 I'm trying to build some wrapper in my application over the Tone.js for the abstraction. One of the problems I'd like to resolve is to have an ability to instantiate & dispose (i.e. destroy) the core instance of Tone.js.

The weird thing I noticed about Tone.js is the difference between calling Tone.context VS calling Tone.getContext() to get the reference to the Context object. In case I'm using Tone.getContext() I never hear the audio.

To Reproduce

The basic demo in the sandbox: https://stackblitz.com/edit/tone-context-issue (see the private get _context() method of CoreAudio.ts file, if you edit it to return Tone.getContext() instead of Tone.context you will not hear audio anymore)

Expected behavior

  • IMO, Tone.getContext() looks like an opposite of Tone.setContext() which should get me the current context reference and it should be playable.

What I've tried

See the sandbox for details. Maybe I did something wrong in my CoreAudio wrapper?

satelllte avatar Oct 06 '22 16:10 satelllte

Have you learned anything new regarding the global context? I'm trying to set latencyHint to playback without blowing up the whole app's modularity/treeshaking by importing * as Tone from 'tone. Everything I try throws an error.

braebo avatar Oct 27 '22 01:10 braebo

Have you learned anything new regarding the global context? I'm trying to set latencyHint to playback without blowing up the whole app's modularity/treeshaking by importing * as Tone from 'tone. Everything I try throws an error.

@FractalHQ no, I didn't have this use-case, maybe you should create a separate issue for this

satelllte avatar Oct 27 '22 07:10 satelllte

Hello - I am not very familiar with React so admittedly I don't follow the code in your demo very well (specifically - the order in which code is being executed across the different files). The error is happening because some of the code is referencing an already-closed older context (or audio nodes created with the older context). Some things to note about contexts in Tone.js that should help you work around the problem:

  • Tone automatically creates it's own context as soon as it loads. It is a special context wrapped in standardized-audio-context to maximize compatibility. If you plan to use your own new contexts you should dispose of the built-in one first to free resources.
  • Using a new context with Tone is a bit of an edge use case that brings some subtle pitfalls. If you will be creating multiple contexts for use in Tone it's good practice to keep your own global references to those contexts, and always explicitly pass in those references when creating Tone objects (ex: new Tone.Synth({context: myCustomContext1})). That way you never have to worry about which context is referred to by Tone.context and Tone.getContext(), and it's easier to debug.
  • Likewise, if you're using multiple contexts, you'll want to avoid using toDestination() and getTransport() which assumes use of a single context (instead: synth.connect(myCustomContext1.destination), myCustomContext1.transport.start()).
  • If you create a new context using native new AudioContext() then you lose out on the x-browser compatibility benefits of standardized-audio-context, and you're deeper in edge case territory because Tone expects a standards-conforming audio context which standardized-audio-context guarantees but not all browsers provide. There are some situations where using a native context might be preferable. But if yours is not one of those, then you should use new Tone.Context() to instantiate a context wrapped by Tone.js and its bundled standardized-audio-context.

marcelblum avatar Nov 03 '22 13:11 marcelblum

Hi @satelllte, This is an issue with <StrictMode /> in React and the only solution is not opting in. If you remove the strict mode wrapper in index.tsx your issue is solved. From version 18 the effects are fired twice on load. This is the reason why your context wasn't working, basically it was disposed right after you initialised it.

More Info: StrictMode: Ensuring reusable state

andeeplus avatar Dec 11 '22 22:12 andeeplus

Hi @satelllte, This is an issue with <StrictMode /> in React and the only solution is not opting in. If you remove the strict mode wrapper in index.tsx your issue is solved. From version 18 the effects are fired twice on load. This is the reason why your context wasn't working, basically it was disposed right after you initialised it.

More Info: StrictMode: Ensuring reusable state

Hi @andeeplus, you're right! When I turn off the React strict mode it works fine. I've also tried to not dispose the context on unmount, which fixes the issue when strict mode is on.


But my main concern is about the difference between Tone.context and Tone.getContext(), why the behaviour is different?

UPD: I see, probably because of this, like @marcelblum described:

Using a new context with Tone is a bit of an edge use case that brings some subtle pitfalls. If you will be creating multiple contexts for use in Tone it's good practice to keep your own global references to those contexts, and always explicitly pass in those references when creating Tone objects (ex: new Tone.Synth({context: myCustomContext1})). That way you never have to worry about which context is referred to by Tone.context and Tone.getContext(), and it's easier to debug.

satelllte avatar Dec 12 '22 08:12 satelllte

@satelllte Yes, I think what @marcelblum said is the reason about the difference. I've personally run Tone in a large application and I must admit I never had the need for a different context from the main. Anyway has a practice I always access to the destination and the context using getDestination() and getContext().

About strict mode, this is a bit of a pain in react for those kind of situations. Also if you plan to work with other libraries that had issue with strict mode (i.e. wavesurferjs). I didn't find any other solution than didn't opt in the strict mode for the moment (the alternative would be manually opt in all the components that are not using the library that fails).

andeeplus avatar Dec 14 '22 17:12 andeeplus

Got it, @andeeplus.

Thanks for sharing more about your experience with Tone. And, for sure, that's a good point to use only one way of accessing something at the same project.

Regarding scrict mode, I personally prefer to have it on, so this was one of the reasons to open this issue. But maybe it's really a pain in case the project depends on libraries like Tone, which normally should be instantiated once per session.

satelllte avatar Dec 14 '22 19:12 satelllte