Tonejs / Tone.js

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

setContext not working? #1192

Closed cz-chrome closed 1 year ago

cz-chrome commented 1 year ago

Describe the bug

I've built a synthesizer web app and I'd like to adjust latency (to avoid unwanted audio crackling).

I'm trying to do this by following the example in the documentation here: https://tonejs.github.io/docs/14.8.36/Context#latencyHint

But the second line: "Tone.setContext(context);" appears to have no effect.

This block of code:

const context = new Tone.Context({ latencyHint : 0.01 }) Tone.setContext(context); console.log(Tone.context.latencyHint);

Logs the default value "Interactive" to the console (instead of the 10ms value I was aiming for). I tried a larger latency value to make sure, but it is not applied to the audio.

To Reproduce

My complete code is at: https://codepen.io/cz-chrome/pen/PoyPBML, this bit is on line 40-44 of the JS page.

Expected behavior

I would expect this code to change the context to the new context I created and therefore the value of 'latencyHint' to be updated to the one set in the new context.

What I've tried

I've tried many things to get rid of the audio crackling (it's very intermittent now). Adding a bit of latency seemed like a good idea, If there is another way of doing this I would be interested in that as well but based on the documentation this seemed to me to be the 'proper' way.

Additional context

The app I built is mostly working very well. The audio crackling that I still get is intermittent and doesn't happen on all devices. I also tried a lot of things that may reduce CPU load like reducing polyphony, removing effects, scheduling note ons with transport, et cetera. None of these things have had any effect. Also the crackling is not happening on some slower/older devices I tried but it is happening on my new and pretty fast pixel phone. The crackling/distortion I do still get sounds similar to what happens in a DAW with the buffer size set too small, (happening even when CPU load is not very high), that's what got me thinking about adding some latency.

marcelblum commented 1 year ago

Couple things, first off Tone.context will always refer to Tone's original default context that it creates when the library initializes (pretty sure it's an undocumented property for internal use). Tone.getContext() returns the most recently set context. You can also access the context of individual Tone audio nodes via their context property e.g. voice1.context.

Secondly, you're setting your new context after you create your audio nodes. This means all of your synths are being created using the original default context, which is why they reflect that default latencyHint. Setting a new context doesn't automatically change all previously created nodes. You would need to instantiate all of your Tone.js objects after setting the new context, then they will use the context with the desired latencyHint.

I'm not sure how well numeric latencyHint values are obeyed across browsers/platforms though so it might be best to stick with the string values. Within Tone.js you can also tweak the lookAhead.

cz-chrome commented 1 year ago

Thanks! I was able to reduce the crackling by adjusting context.lookAhead. I'm still trying to wrap my head around the concepts behind these contexts, but your reply definitely helps. I guess this is not a bug then so will close the issue. I do feel that the documentation I referenced (https://tonejs.github.io/docs/14.8.36/Context#latencyHint) is rather confusing in this regard, but that may well be due to my lack of understanding.

By the way, although I was able to reduce the crackling quite a bit by setting a 12ms lookAhead, it doesn't eliminate it entirely. If you have any other suggestions on how I could improve the performance, that would be highly appreciated!

marcelblum commented 1 year ago

Web audio supports using multiple contexts at once, for example to output to multiple devices simultaneously, or use a mix of online and offline contexts, and Tone.js supports use of multiple contexts, but it's up to the dev to manage this. latencyHint and sampleRate are the best variables to tweak for performance so I would encourage experimentation there, just make sure your Tone.js objects are instantiated using your new context not the default one. Lowering the sampleRate can do wonders for performance at the expense of quality, though it's not supported in all platforms.

cz-chrome commented 1 year ago

Great, thanks a lot, I will look into that!