Tonejs / Tone.js

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

Issue scheduling effects with Tone.Offline #1258

Closed l-irizarry closed 3 months ago

l-irizarry commented 3 months ago

Describe the bug

I'm trying to schedule effects with Tone.Offline, but the effects are being added to the whole track instead of at the scheduled event.

To Reproduce

My code looks like this:

const exportAudio = () => {
    Tone.Offline( async ({ transport, context }) => {

      const reverb = new Tone.Reverb(10);

      const player1 = new Tone.Player();
      await player1.load(tracks["vocals"]); // this points to an URL
      player1.toDestination();

      // Play the first 5 seconds without effects
      transport.schedule(time => {
        player1.start(time, 0, 5);
      }, 0);

      // Play the rest with reverb
      transport.schedule(time => {
        player1.disconnect();
        player1.connect(reverb);
        player1.start(time, 5);
      }, 5)

      transport.start();
} // .then() ...

Expected behavior I expect the file to play without reverb for the first 5 seconds, and with reverb after the first 5 seconds. But it plays with reverb from the beginning, even though the first schedule doesn't add any reverb.

What I've tried Some of the things I've tried:

  const osc = new Tone.Oscillator().toDestination();
  transport.schedule(time => {
    osc.start(time).stop(time +1);
   }, 5);

Additional context I have scheduled effects outside of Tone.Offline for the same track in a similar fashion, and effects are added and removed as expected at the given scheduled times. Does the Offline class work differently when it comes to scheduling?

Regardless, just wanted to say thank you for this amazing library! It's a game changer.

tambien commented 3 months ago

Thanks @synthetic-luis, Offline works slightly differently than the online rendering in that all changed attributes have to be scheduleable. Basically the entire audio graph is established at initialization and then it is stepped through during the offline rendering process. So changes to the audio graph like connect/disconnect won't appear in the final output, those have to be setup up front and then all the scheduleable events like start(time) are rendering in the offline rendering loop. Since this code your shared contains a scheduled disconnect which alters the audio graph it's not going to be possible within the offline rendering.

I would suggest you connect everything up from the start and use two Gain nodes to switch between reverb and no reverb at the scheduled time. The graph could look something like this:

image

And then you can set one gain to 0 and the other to 1 at the scheduled time in order to move the player signal from one part of the audio graph to the other.

const noReverbGain = new Gain(1);
const reverbGain = new Gain(0);
player.start(0);
// turn off one gain and turn on the other
const switchTime = 5;
reverbGain.setValueAtTime(1, switchTime);
noReverbGain.setValueAtTime(0, switchTime);
l-irizarry commented 3 months ago

@tambien thank you for the detailed explanation and the diagram! That makes sense for the case of just reverb.

In my specific case, I would like to add multiple effects in different parts of a track. For example:

0-10s [reverb, pitch shift] 10s-20s [reverb] 20-30s [distortion, reverb] 30s-40s [distortion, lower volume] etc, etc

In this case, do you recommend creating a Gain node for each effect combination? (e.g. "reverb + pitch shift" is one Gain node, "reverb" by itself is another, and so on). And then switching between them in the manner you explained?

tambien commented 3 months ago

@synthetic-luis yeah that's definitely one way to go about it. I think all of the effects that you mentioned also have a wet attribute which you can also schedule. The wet attribute is pretty much a mix know which interpolates between the dry bypass signal and the effected signal. (pretty similar to what i had diagramed, but internal to the node). So you could hook them up in series and then schedule the effects wet signal at the given times:

// turn off pitch shift at 10 seconds
pitchShift.wet.setValueAtTime(0, 10);
// turn on distortion at 10 seconds
distortion.wet.setValueAtTime(1, 20); 
l-irizarry commented 3 months ago

@tambien all the information you provided is super helpful. I now have a path forward. Thank you very much!