The event scheduler is an interface exposed in web synth for the purpose of scheduling events to take place in the future and synchronizing time/beats between all parts of the application. Under the hood, it manages a global rhythm counter that runs on the audio thread based on the high-granularity audio context clock. It maintains a global beat counter with a dynamically adjustable BPM that allows for all modules to be synchronized to the same rhythm.
It exposes this functionality to the main/render thread via event registration, similar to that of setTimeout
but based off of this global synchronized clock. Events can be schedule in terms of both beats as well as time, and it takes care of dealing with dynamic BPM changes under the hood. It is currently used by the [midi-editor] and [sequencer] to support playback.
There are some issues with this interface that can create problems for highly-accurate audio rendering and playback needs. The main issue is that web audio is only accessible from the main thread; you can't dynamically modify the [audio-graph] from the audio rendering thread; the only thing you can do is emit outputs via AudioWorkletProcessor
s.
To work around this, some nodes make use of [audio-thread-midi-scheduling]. This allows nodes that produce scheduled MIDI events to send them directly to the nodes that consume them without leaving the audio thread. This completely bypasses the problem of having to round-trip to the UI thread and mostly eliminates latency/desync problems.
For interactive events such as those originating from live user input, event callbacks still must be handled on the main thread and can suffer from contention for CPU resources from the UI and other things running in the application. If the application is busy rendering a complicated visualization or performing some heavy React rendering, it's possible for audio playback to get glitchy or events to lag.
This was originally quite bad, but switching to React concurrent mode helped greatly since it avoids the length of blocking periods of the event loop and reduces the average latency between when events are received over the message port and when they are actually processed on the event loop.