Tone.js
Tone.js copied to clipboard
Differences between `Tone.context` and `Tone.getContext()`. How to properly instantiate and dispose the entire Tone instance?
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 ofTone.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?
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.
Have you learned anything new regarding the global context? I'm trying to set
latencyHint
toplayback
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
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 byTone.context
andTone.getContext()
, and it's easier to debug. - Likewise, if you're using multiple contexts, you'll want to avoid using
toDestination()
andgetTransport()
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 ofstandardized-audio-context
, and you're deeper in edge case territory because Tone expects a standards-conforming audio context whichstandardized-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 usenew Tone.Context()
to instantiate a context wrapped by Tone.js and its bundledstandardized-audio-context
.
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 @satelllte, This is an issue with
<StrictMode />
in React and the only solution is not opting in. If you remove the strict mode wrapper inindex.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 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).
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.