Time, scheduling and clocks#
Every timed object in a patch gets its timing from one of three sources. Knowing which one an object uses tells you how accurate it is, whether it keeps running while the engine is paused, and which thread its output runs on.
Block clock |
Millisecond timer |
Domain clock |
|
|---|---|---|---|
Unit |
milliseconds, rounded up to whole audio blocks |
whole milliseconds |
beats, at a tempo that can change |
Resolution |
one block (128 samples, about 2.9 ms at 44.1 kHz) |
about 1 ms, plus operating-system wake-up jitter |
one block (see How accurate a beat is) |
Runs while nothing renders |
no, pending messages wait |
yes |
no, the clock only advances with the audio callback |
Output runs on |
the audio thread, at the start of a block |
the engine’s timer thread |
the audio thread, at the start of a block |
Used by |
|
|
|
The rest of this page explains each source, lists the objects, and ends with the limits. Time has the full reference for every object named here.
The block clock#
Each patcher counts the audio blocks it renders. A block is 128 samples. Most
objects that wait, such as .delay, .pipe and .line, use this
counter through the patcher’s deferred-message scheduler. The object asks
for a message “N milliseconds from now”. The scheduler turns N into a block
count and delivers the message when the counter gets there.
How a wait becomes blocks#
The scheduler rounds the wait up to whole blocks, at the current sample rate:
blocks = ceil(ms × sampleRate / 1000 / 128), and at least 1
So a wait is never shorter than asked, and is at most one block longer. At 48 kHz, 100 ms is 37.5 blocks, which becomes 38 blocks (101.3 ms). At 44.1 kHz it becomes 35 blocks (101.6 ms).
A wait of 0 ms still takes one block. A deferred message is never
delivered inside the message that scheduled it. That is why a .delay 0
wired back into its own inlet makes a fast metronome (one bang per block)
instead of an endless loop.
When the message arrives#
At the start of every block, before any audio is computed, the patcher does these steps in order:
apply queued parameter changes (see Building and editing graphs),
deliver the values the host sent with
PassData/PassBang(see Talking to the host),deliver every deferred message that is due,
render the block.
So a deferred message runs on the audio thread, and whatever it changes is heard in the same block. Messages due in the same block arrive in the order they were scheduled. Each one is a new logical event (see Messages and dispatch): everything it sets off counts as one event.
Where the count starts depends on where the wait was requested:
From inside the patch, for example a
.delaybanged by a.ror by another deferred message, the wait starts at the current block. The message arrives exactly the rounded-up number of blocks later, in audio time.From outside a block, for example a
pHandlecall on the host’s thread, or a millisecond.metrobanging a.delay, the count starts at the last block rendered. The message can therefore arrive up to one block earlier than the rounded-up wait, measured from the moment of the request.
The audio device may ask for more than 128 samples per callback. The engine then renders several blocks back to back in one callback. Deferred messages stay accurate to the block in the audio you hear. Measured on a wall clock, they arrive in bursts, one burst per device callback.
What stops the block clock#
The counter only moves while the patcher renders. A patcher renders only when it is attached to a sound or an insert and the engine is running (see Patcher). Until then, every pending message waits. “100 ms from now” means 100 ms of rendered audio.
A pending message belongs to one object. If you delete that object, or
SetParams rebuilds it, the message is dropped and does not reach the new
object (see Building and editing graphs).
The millisecond timer#
.metro and .clocker with a millisecond interval do not use the block
clock. They use a real operating-system timer: one engine thread serves every
millisecond timer in the process.
Resolution. Intervals are whole milliseconds, 1 ms at the least. On Windows,
System::initsets the system timer resolution to 1 ms.No drift. Each tick is scheduled at the previous deadline plus the interval, not at the time the previous tick actually ran. A late tick does not push the ones after it back.
Jitter. A tick runs when the operating system wakes the timer thread, which can be a little late. The thread also runs callbacks one at a time. If one metro’s tick sets off a lot of work, other timers wait until it is done. When a tick is overdue by more than an interval, the missed ticks run straight away, one after another.
Keeps running. The timer does not depend on rendering. A millisecond
.metrokeeps banging while the engine is paused or the patcher is not attached.
Which thread#
A timer tick runs the object’s output on the timer thread. Everything downstream runs there too, in the same call. This matters in three places:
A
.sdriven by a millisecond.metroreaches the send callback on the timer thread (see Talking to the host).A bus publish from the timer thread is delivered at the next
System::update().A signal object gets the new value at the next block it renders. So a metro tick is audible at a block boundary. How far that is from the tick depends on the device buffer size.
If you need a pulse that stays in step with the audio, or that stops when
the engine stops, use the beat engine (below), or a .delay wired into
itself.
.metro in 3.0#
Changed in version 3.0: Live interval (issue #625). A number in
the right inlet changes the interval of a running metro without
restarting it. The next tick moves to previous tick + new interval. When
that time is already past, the tick fires straight away. A SetParams
change to period also reaches a running metro, one tick later.
Changed in version 3.0: Left inlet (issue #711). The left inlet now takes four messages:
a non-zero int or float starts the metro and 0 stops it. A float is compared with 0, not rounded, so
0.5starts it.bangstarts it. On a metro that is already running, a bang restarts it from now. One button wired to several metros brings them into step.stopstops it.
Every start sends a bang straight away.
Changed in version 3.0: Starting and stopping a millisecond metro from the audio thread is now real-time safe (issue #718). There the request is handed to a background thread, so the second tick may be one thread hop late, and a stop may let one more tick through. From any other thread, the start and stop happen before the call returns, and no tick comes after a stop.
Domain clocks and beat time#
A domain clock is a named musical clock owned by the engine. It counts beats at a tempo in BPM. The tempo can jump, glide over a number of seconds, drop to 0 (paused) or even go negative. The beat position is the running total of tempo over time, so a tempo change changes how fast beats go by and never makes the position jump.
The host creates clocks with System::createClock, setTempo and
beatPosition (yse_system_create_clock and friends in the C API).
Clips play notes on the same clocks. A patch refers to a clock by name, so the
host, the clips and the patch all share it. Clocks and clips
is the guide to clocks and clips on the host side.
Waiting on a clock#
An object on a clock waits for a beat position, not for a number of
milliseconds. It uses the same scheduler as the block clock. The scheduler
checks the clock at the start of every block and delivers the message once
the clock has reached the target beat. So the output runs on the audio thread,
like a .delay, and a beat wait:
gets shorter when the tempo rises, and longer when it falls;
follows a tempo glide smoothly;
never finishes while the tempo is 0.
Objects that repeat (.metro, .tempo and .clocker on a clock) do
not count their wake-ups. At every wake-up they read the clock and work out
how many grid points it has passed. Each wake-up is late by up to one block,
but that error never adds up, and a pulse faster than one block still gives
the right number of outputs. If the clock jumps a long way in one block, a
.metro sends at most 64 catch-up bangs and skips the rest.
Several objects on the same clock stay in exact step with each other and with every clip on that clock. Two millisecond metros cannot do that.
Tempo-relative time values#
Objects that accept beat time read a tempo-relative syntax. A quarter note is one beat.
Written |
Beats |
Meaning |
|---|---|---|
|
4, 2, 1, 0.5 … |
Note values. The number must be a power of two from 1 to 128. |
|
1.5 |
Dotted: the value × 3/2. |
|
1/3 |
Triplet: the value × 2/3. |
|
3 |
Ticks, 480 per beat. Use this for any other length. |
Any plain number means milliseconds and switches a
two-unit object back to its millisecond engine. bars.beats.units,
hh:mm:ss and quantize are not read: a domain clock counts beats but
has no bars and no time signature.
Binding a clock#
.metro, .delay, .clocker, .qlim and .speedlim take a beat
time in the right inlet. They also need a clock <name> message in the
left inlet to say which clock to count on. A bare clock returns the object
to milliseconds. .tempo, .timepoint, .when, .timer and
.translate take the clock name as a creation argument instead.
A beat time with no clock bound does nothing.
.delaydoes not fire and.metrodoes not start..speedlimand.qlimlet messages through, so a stream is never swallowed..metroand.clockerchoose their engine when they are started. Aclockmessage or a change of unit takes effect at the next start.A name may refer to a clock that does not exist yet. The object then waits. The patcher looks for the clock again every 64 blocks, and the wait starts counting when the clock appears.
A clock that is destroyed while objects are bound to it freezes. Objects on it stop and never fire. Creating a new clock with the same name does not reconnect them.
Only .transport and .setclock create clocks, and neither one ever
destroys a clock. All other objects only use a clock that already exists.
How accurate a beat is#
The engine advances every domain clock once per 128-sample block, just
before it renders that block, by the length of one block. The patcher checks
beat deadlines at the start of the same block, so a beat wait is accurate to
one block. This holds for any device buffer size: when the device asks for
512 samples per callback, the engine renders four blocks and the clock moves
between each of them. Offline rendering (renderOffline) behaves the same.
Clip transports advance with the clocks, one block at a time, so a clip and a patch on the same clock fire in the same block.
While no sound is playing, nothing renders. The clocks then keep moving once per device callback, by the length of that callback.
Changed in version 3.0: Clocks used to advance once per device callback, so with a device buffer larger than 128 samples every beat deadline in a callback came due in its first block, up to one buffer early (issue #944).
The objects#
Delays and ramps#
.delaySends a bang after a set time: 5 ms by default, or a beat time on a bound clock. It holds only one bang. A new bang restarts the wait, and
stopcancels it. A number in the right inlet sets the time for the next bang and leaves a bang already waiting alone. Block clock or domain clock..pipeDelays ints, floats and lists. Unlike
.delayit holds many values, and each value keeps its own deadline, so ten values in give ten values out in the same order.cleardrops everything waiting andflushsends it all now. Milliseconds only..lineRamps a number to a target over a set time, sending a value every grain milliseconds (20 by default) and banging its right outlet when it arrives. Each value is computed from the time elapsed, so a late step jumps straight to the right value. A grain shorter than one block gives one value per block. Milliseconds only.
.blineThe same ramp, but it moves one step each time it is banged instead of on a timer. It has no clock of its own. Drive it from a
.metroor a.tempoto make a ramp in beats.
Rate limiting and grouping#
.speedlimPasses a message only if the interval has gone by since the last one it passed, and drops the rest. Time is read from the block counter, so two messages in the same block are 0 ms apart. It never schedules anything. Takes a beat time, plus
thresholdandquantizemessages..qlimThe same limit, but it holds early messages and sends them when the interval is up. By default a new message replaces the one waiting;
usurp 0queues up to 32 instead. Takes a beat time..threshCollects values that arrive close together into one list. Every value restarts the wait, so the list goes out once the input has been quiet for the threshold (10 ms by default). Milliseconds only.
.quickthreshThe same grouping, but the window starts at the first value. The group goes out a fixed time after it began, which is what chord detection needs. Milliseconds only.
Pulses and measuring#
.metroSends a bang at a regular interval, 1000 ms by default. The millisecond engine is the timer thread; a beat interval with
clock <name>runs on the domain clock. See .metro in 3.0 for the 3.0 changes..clockerSends the time since it was started, every interval (5 ms by default). The elapsed milliseconds are measured with the system’s steady clock, not added up from ticks, so they stay right even when a tick is late. With a clock bound, a second outlet sends the beats that clock has moved since the start. The millisecond engine is the timer thread.
.timerA bang in the left inlet marks a start, and a bang in the right inlet sends the time since that start, measured with the steady clock. With a clock name as argument, a second outlet sends the same interval in beats. It has no timing of its own and runs on the thread of the bang.
Musical time#
.transportStarts and stops a named clock and sets its tempo:
.transport main 120. If no clock of that name exists yet, it creates one when it joins the patcher, stopped, so adding one never starts anything. Stopping sets the tempo to 0, which pauses the clock. Starting continues from the same beat, andtempo 140 2glides to 140 BPM over two seconds. A bang sends the beat position and the tempo..setclockCreates a named clock that is already running:
.setclock fast 240. The number it receives is the tempo, and 0 stops it. Use it to give part of a patch a tempo of its own..tempoCounts notes on a clock:
.tempo main 120 1 16sends 0, 1 … 15, 0 … in sixteenth notes. The interval is 4 × multiplier / division beats. Starting it writes its tempo to the clock. Stopping it leaves the clock alone, so the clips on that clock keep playing. Clock only..timepointBangs once when a clock passes a beat position:
.timepoint main 128. It fires on the way past, then waits until it is armed again. A target that is already behind the clock never fires, so loading a patch halfway through a piece does not fire every cue at once. Clock only..whenOn any input, sends the clock’s beat position and tempo. It only reads the clock and never creates one.
.translateConverts between
ms,hz,samples,beatsandticks, and accepts note values:.translate ms beats main. Conversions between milliseconds and beats use the clock’s tempo at that moment. A bang converts the last value again. With no clock, or a clock at tempo 0, it sends nothing.
.qlist, .seq, .mtr, .bondo, .makenote and .borax also
schedule messages through the block clock, and some of them can use a
domain clock. They are described with their own categories in
Patcher object reference.
Examples#
A beat-synced pattern. The host creates the clock. A .tempo counts
quarter notes on it, and PassBang starts the count at the next block:
YSE::System().createClock("main", 120.f);
YSE::patcher patch;
patch.create(2);
YSE::pHandle* run = patch.CreateObject(".r", "run");
YSE::pHandle* count = patch.CreateObject(".tempo", "main 120 1 4"); // quarter notes
YSE::pHandle* print = patch.CreateObject(".print", "beat");
patch.Connect(run, 0, count, 0);
patch.Connect(count, 0, print, 0);
// ... attach the patcher to a sound and play it ...
patch.PassBang("run"); // starts counting: 0 1 2 3 0 1 ...
YSE::System().setTempo("main", 90.f, 4.f); // glides to 90 BPM over 4 s
The same clock drives every object bound to it. A .metro sent
clock main in its left inlet and 8n in its right inlet would tick
eighth notes that stay locked to this count through the glide.
An echo on the block clock. Each value comes back 250 ms later, rounded up to whole blocks:
YSE::pHandle* in = patch.CreateObject(".r", "note");
YSE::pHandle* echo = patch.CreateObject(".pipe", "250");
patch.Connect(in, 0, echo, 0);
Limits#
128 pending deferred messages per patcher. This is shared by every object that uses the scheduler, whether it waits on blocks or on a clock. A
.pipeuses one slot per waiting value (and holds at most 64); a.qlimuses one slot however long its queue is. When the table is full, a new request is refused and counted, not sent early. List payloads must be shorter than 256 characters.8 clock names per patcher. Objects that name the same clock share one entry. A clock name can be up to 63 characters.
256 millisecond timers per process. Each
.metroand.clockertakes one when it is created. Past that, a new one has no millisecond timer (its beat engine still works).Nothing is logged when one of these limits is reached, because the refusal may happen on the audio thread.