Tonejs / Tone.js

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

Clearing and Rescheduling Transport.Timeline with Players #1076

Open jamescqcampbell opened 2 years ago

jamescqcampbell commented 2 years ago

What are you trying to do?

The end goal is to use Transport.Timeline and multiple Player instances to schedule players to play different parts of clips at different times along the timeline. This includes players being reused to play earlier or later segments of their audio at different points on the timeline. I have used a very short clip in the codepen, but each player will be 5-10 minutes long in production.

E.g.

0:00 Player 1 0:10 Player 1 Stop 0:10 Player 2 Start 0:20 Player 2 Stop 0:20 Player 1 Start at 10s offset ...etc

Describe the bug

The bug occurs when synced players are scheduled with Tone.Transport.scheduleOnce() with the first argument of player.start() undefined. This is necessary as otherwise when the transport is paused and restarted, all players that have already been started play simultaneously.

If the timeline is cancelled (whether from 0 or from any point in the timeline) with Tone.Transport.cancel(0) and then new events are scheduled with Tone.Transport.scheduleOnce. The issue occurs when the Transport is restarted from 0 after this scheduling.

The error thrown is: Uncaught Error: The time must be greater than or equal to the last scheduled time". This error stems from StateTimeline.add (Tone.js line 310), but I can't work out why this happens as all events should have been cancelled before new events were added (so there shouldn't be any scheduled events).

I would consider disposing of the timeline but I worry that this might not be performant when the number of events to schedule reaches tens or hundreds (or more).

To Reproduce

Please see the codepen here: https://codepen.io/jamescqcampbell/pen/gOvbarK?editors=0010

To reproduce issue:

  1. Start audio context
  2. Press play (works fine)
  3. Press stop
  4. Click Change Timeline (just clears and reschedules the same audio timeline)
  5. Press play (should throw an error)
  6. If no errors press stop and change timeline again, reliably happens for me on the first or second try

Expected behavior I would expect the timeline to be completely cleared by Tone.Transport.cancel(0) and to be able to reschedule events from 0s with no issues.

What I've tried Previously I was only cancelling events on the timeline after the point at which edits were made. I switched to cancelling and rescheduling the entire timeline because of this issue but it didn't help.

I have looked into whether this issue was caused by not disposing of the Tone instance on component refresh but I don't think that this is the case.

Additional context I would like to be able to dynamically set and reset the timeline within react. This is a core part of what I would like to build so I am keen to help get this resolved.

All player events need to be synced to and controlled by the timeline such that when played back it sounds like a continuous piece of audio rather than a series of clips.

cordial commented 2 years ago

Did u manage to fix this?

jamescqcampbell commented 2 years ago

Sadly not, what I did instead was to:

  1. Create a ToneAudioBuffers with each of my audio sources.
  2. When scheduling, create a new player for each section of audio (with the source from the buffer). I.e. I didn't reuse the same player each time for this.
  3. Dispose of all of the players when rescheduling, but keep the ToneAudioBuffers and use that as the basis of the new set of players for the new schedule.

This works, though seeking can be a bit flaky (if you have any thoughts there please let me know!)

If anyone does solve this issue I'd be keen to know!