Signals & Time
A signal is a value that moves. Anything you set with << — an effect parameter, a track’s
volume or pan, a send level — can take a signal instead of a number, and the parameter follows it.
In DAW terms, one operator covers both the modulation matrix and the automation lane. This page is
about how a signal keeps time, because that decides what you hear when you re-run a line, pause, or
seek.
A signal runs at audio rate
Section titled “A signal runs at audio rate”The engine evaluates a bound signal as the graph runs, not once per event, so a parameter glides
instead of stepping. Signal arithmetic — Sine(1) + Tri(3) * 0.5, -Sine(1) — runs sample by
sample with no allocation, so layering and nesting modulators costs little.
A signal counts in its timebase
Section titled “A signal counts in its timebase”A signal’s numbers are counted in a unit: its timebase. By default the unit is one cycle, so
Sine(4) sweeps four times per cycle and an LFO stays locked to the tempo when the tempo changes. A
change mid-play bends its speed and never jumps its phase.
.set_timebase() sets another unit, given as a duration. Musical durations follow the tempo:
Sine(1).set_timebase(note(1, 8).dotted()) sweeps once per dotted eighth, Saw(1).set_timebase(cycles(2))
once every two bars. Seconds do not: Sine(2).set_timebase(seconds(1)) runs at 2 Hz at any tempo,
like an LFO with its sync switched off. The same unit applies to everything the signal counts, so an
automation curve’s breakpoint times and a signal() pattern’s steps are in its timebase too.
The unit belongs to the signal, not to the number. That is the difference from converting: a rate computed from the tempo of the moment goes stale when the tempo changes, and a timebase never does.
A signal owns its clock
Section titled “A signal owns its clock”A signal has one clock, and it belongs to the signal — not to any particular binding. Its local time
starts at zero on the next cycle boundary after you bind it, and binding it to the same parameter
again restarts it.
That’s what you want for one-shots: re-running a signal_ramp(0, 1, 4) line restarts the ramp
cleanly.
A restart lands on the grid, so it can be up to a cycle away — and until it lands, the signal keeps running exactly as it was. Nothing drops out or jumps to the bottom of its range while it waits, the way a launch-quantized clip keeps playing until the downbeat. A signal you have just written, with no phase to carry yet, waits at its start value.
Because the clock lives on the signal, everything reading that signal sees the same phase — every
parameter it drives, and scope() too. Point one LFO at two parameters and they stay locked
together. Binding a running signal to one more parameter doesn’t restart it; rebinding it where it is
already bound does, and every parameter it drives restarts with it. When you want two modulators that
drift independently, make two signals.
Two settings on the signal change this. .continuous() puts it on the global clock instead, so you
can change its speed without its phase jumping. .set_retrigger() resets its phase every period you
give it: .set_retrigger(cycles(1)) on every cycle, .set_retrigger(beats(1)) on every beat,
.set_retrigger(note(1, 8).dotted()) every dotted eighth. Without one, a signal never resets. Like
.set_timebase(), both configure the signal itself, so they hold wherever it is used and reach it
through .range() and other wrappers in any order.
The transport moves signal time, binding restarts it
Section titled “The transport moves signal time, binding restarts it”A restart is a binding gesture — << is the note-on. The transport does not retrigger anything:
PAUSE freezes signal time along with the playhead, and the PLAY after it picks every signal up on
exactly the phase it froze on.
Moving the playhead is different. Signal time is the playhead, so SEEK takes it along — every
signal restarts at the position you seeked to, right there rather than a cycle later. STOP is a
seek to 0 and behaves the same way, including while the transport is stopped: SEEK 8 then PLAY
starts at cycle 8 with every signal already positioned there.
The upshot is that phase depends only on where the playhead is, never on how it got there. Seek to the same place twice and you hear the same thing — which is also what makes a bounce reproducible.
A modulator loops, an automation lane is placed
Section titled “A modulator loops, an automation lane is placed”automation(#[0, 0], #[4, 1]) bound with << is a signal like any other: its breakpoint times are
measured from the moment its clock starts. It is a shape you attach to a parameter.
An automation curve written into a section
lane is placed instead: song[drums].volume.at(8, 8) << curve sits at cycles 8–16 of the song, in
absolute cycles, exactly like a clip on an arrangement track. It is bound the same way whether the
song is playing or stopped, it masks the curve beneath it, and when the song reaches cycle 8 the
swell is there. That is the difference between a modulator you perform and a lane you arrange.
Some settings are not automatable
Section titled “Some settings are not automatable”A few parameters take a plain number only, because changing them is not a change of level but a
change to the graph. track.set_delay(ms) — the calibration for outboard gear — re-plans the graph’s
latency compensation when it is set, so it cannot be driven by a signal.
Related
Section titled “Related”- Sweep a Parameter with an LFO — LFOs in practice
- Draw an Automation Curve — ramps and breakpoint curves
- Signals — every signal constructor and method
- The Arrangement Model — how placed automation masks and resumes