Tonejs / Tone.js

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

changing bpm effects the Transport scheduling #962

Closed cordial closed 6 months ago

cordial commented 3 years ago

Should the bpm of transport effect the timings of schedule events? It currently seems like it does.

To Reproduce

Schedule an event like- Transport.schedule((time) => { currentName = narrativeEmotion.name; handleAudioChange(narrativeEmotion.name, results); // setIntro(false); // setCurName(narrativeEmotion.name); }, 20);

If you however change the transport bpm like

Transport.bpm.value = 120

This seems to effect when the schedule is hit - I would have expected this to be independent? Is that not the case? Is there a way to change bpm but not affect transport scheduling? Or does that not make any sense? Expected behavior Event happens after 20 seconds.

cordial commented 3 years ago

Sorry, to provide some context - what I am trying to achieve is the following - i) a metronome that you can dynamically adjust the bpm for (it currently plays using scheduleRepeat every 4n using a 'player' object). ii) A timeline that triggers events, as above, after x seconds that doesn't respond / change if i edit transport.bpm.value (or if better another way to edit the speed of the metronome rather than editing transport.bpm.value)

I was kinda expecting if you triggered events in seconds, that it would ignore the bpm...

tambien commented 3 years ago

I've had similar questions to this in the past, even if you schedule something using seconds instead of a tempo-relative value it will be converted into ticks which will mean that it will stay tempo-relative.

Currently there's no mechanism to do what you're looking for in Tone.js. sorry!

Maybe a new method like scheduleAtTime or something like that would be useful for what you're doing.

cordial commented 3 years ago

Thanks for your quick reply. When you say nothing is available, do you mean i'll have to adjust the timings myself? Does that mean if the bpm is 60 that transport schedule methods would happen at that actual time specified, and if it was 120bpm I would need to double the timings, if it was 30bpm id halve them? Or maybe something more complicated is needed to adjust?

Or do you mean it just won't work?

scheduleAtTime sounds good - is this library still actively being updated with new versions?

dirkk0 commented 3 years ago

Changing tempo and even time signature might not be straight forward but it is possible.

Here's an example that I did some time ago, and finally put it on CopePen: https://codepen.io/dirkk0/pen/mdMVpzE?editors=1010

It changes the signature and the bpm twice before ramping the tempo down.

cordial commented 3 years ago

Changing tempo and even time signature might not be straight forward but it is possible.

Here's an example that I did some time ago, and finally put it on CopePen: https://codepen.io/dirkk0/pen/mdMVpzE?editors=1010

It changes the signature and the bpm twice before ramping the tempo down.

Thanks for the comment, but I'm finding that a touch hard to follow. If I have a metronome that is scheduleRepeating every 4n and starts at time X and ends in time Y, how do i adjust X and Y to compensate for changing the bpm (so that the X and Y values represent the time in seconds that I have entered) i.e. so no matter the BPM value I change x and y so that they actually occur at the times specified and remain absolute rather than relative?

dirkk0 commented 3 years ago

Yes, I understand, this example does much more than just the changes.

Could you provide a minimal codepen with an example?

tambien commented 3 years ago

Maybe one workaround would be to schedule and keep track of a setTimeout on the Transport's "start" event.

Tone.Transport.on('start', () => {
  setTimeout(callback, 30 * 1000)
})

This would give you a callback 30 seconds after you click start.

cordial commented 3 years ago

Thanks both. I think I'll try @tambien workaround, that makes sense.

codepen - its all mired up in react/next.js and firestore calls so its not easy to reproduce. If the above doesn't work i will do though - thanks!

cordial commented 3 years ago

hmm actually i dont think that will work - here is my code below -

The schedule repeat is playing a metronome with sound X for time period Y and at each timeStamp inside narrativeJourney the bpm and the sound file might be changed.

initialiseMetroSounds(narrativeJourney, results).then((metroSounds) => {
                    narrativeJourney.forEach((narrativeEmotion, index) => {
                        let sectionTimeInSeconds = 0;
                        if (index == narrativeJourney.length - 1) {
                            sectionTimeInSeconds =
                                narrativeTimeInSeconds - cumNarrativeStartTime;
                        } else {
                            sectionTimeInSeconds =
                                (narrativeJourney[index + 1].time / 100) *
                                narrativeTimeInSeconds;
                        }
                        cumNarrativeStartTime +=
                            (narrativeEmotion.time / 100) * narrativeTimeInSeconds;

                        player.volume.value =
                            results[narrativeEmotion.name].metronome.volume;
                        Transport.scheduleRepeat(
                            (time) => {
                                player
                                    .load(String(metroSounds[index]))
                                    .then(() => {
                                        player.start();
                                    })
                                    .catch((err) => {
                                        console.error("error loading firebaseURL", err);
                                    });
                            },
                            "4n",
                            cumNarrativeStartTime,
                            sectionTimeInSeconds
                        );

                        Transport.schedule((time) => {
                            currentName = narrativeEmotion.name;
                            Transport.bpm.value =
                                results[narrativeEmotion.name].metronome.tempo;
                            handleAudioChange(narrativeEmotion.name, results);
                            setIntro(false);
                            setCurName(narrativeEmotion.name);
                        }, cumNarrativeStartTime);