Skip to content

Signals

Signals are continuous control sources for modulating parameters. Bind one to an effect, track, or send parameter with <<. Import constructors from std/signals:

use "std/signals" { Sine, hz_to_periods };
ConstructorOutputMeaning
Sine(freq?) / Sine2(freq?)0–1 / -1–1Sine wave
Saw(freq?) / Saw2(freq?)0–1 / -1–1Sawtooth — ramps up, then resets
Tri(freq?) / Tri2(freq?)0–1 / -1–1Triangle wave
Square(freq?) / Square2(freq?)0 or 1 / -1 or 1Square wave, 50% duty cycle
Rand(freq?)0–1Sample-and-hold random (deterministic)
Perlin(freq?)0–1Smooth, continuous Perlin noise
automation(...breakpoints)breakpoint valuesBreakpoint envelope with curves
signal(pattern)pattern valuesStepped signal from a pattern
signal_ramp(start, end, duration?)start–endLinear ramp, clamps at endpoints
Cc(cc) / Cc(channel, cc)0–1MIDI CC input
Cc_learn()0–1Bind to the next CC knob you move
MethodReturnsMeaning
range(min, max)SignalMap output linearly to [min, max]
range_exp(min, max)SignalMap output exponentially (for frequencies)
retrigger(mode)SignalPhase reset: "cycle", "beat", or "free"
continuous()SignalKeep global clock phase across rebinds
smooth(time_ms)SignalOne-pole lowpass smoothing
at(time, value [, curve])SignalAdd a breakpoint (automation signals only)
in_seconds()SignalSwitch automation time base to seconds
set_param(name, value)SignalSet a DSP signal parameter

All oscillators take one optional frequency argument in periods per cycle (default 1 — one full wave per cycle). Pass a number for tempo-relative speed, or convert from absolute time with hz_to_periods() / sec_to_periods():

use "std/signals" { Sine, Saw, Tri2, Rand, hz_to_periods, sec_to_periods };
let once = Sine(); // one period per cycle
let wobble = Saw(4); // four periods per cycle
let sweep = Tri2(0.25); // one period across four cycles
let fixed = Sine(hz_to_periods(2)); // 2 Hz, independent of tempo
let slow = Rand(sec_to_periods(3)); // one new value every 3 seconds

Each waveform comes in a unipolar form (0 to 1, for parameter modulation) and a bipolar form with a 2 suffix (-1 to 1, for audio-style modulation and signal algebra).

use "std/signals" { Sine, Sine2, Square2, Perlin };
use "std/effects" { Delay };
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("CR-78")));
drums << [bd sd bd sd];
let d = Delay(0.25, 0.5);
drums.load_effect(d);
d.param("Time") << Sine(2).range(0.1, 0.4);
d.param("Feedback") << Perlin(1).range(0.2, 0.6);

Breakpoint envelope. Each breakpoint is #[time, value] or #[time, value, curve]; time is in cycles by default.

use "std/signals" { automation };
use "std/effects" { Lowpass };
use "std/instruments" { Sampler, Kit };
let hats = AudioTrack("hats");
hats.load_instrument(Sampler(Kit("CR-78")));
hats << [hh*8];
let lpf = Lowpass(1000);
hats.load_effect(lpf);
lpf.param("Cutoff") << automation(#[0, 400], #[4, 4000, "exp"], #[8, 400]);

Call it empty and chain .at() for the builder form:

use "std/signals" { automation };
let env = automation().at(0, 400).at(4, 2000, "smooth").at(8, 400);
CurveShape
"linear"Straight line (default)
"step"Instant jump at the breakpoint
"exp"Exponential curve
"smooth"Smooth interpolation
"ease-in" / "ease-out" / "ease-in-out"Slow start / slow end / both
"ease"CSS-style ease
"bezier(x1,y1,x2,y2)"Custom cubic Bézier

Add a breakpoint to an automation signal (chainable; automation signals only). Curve defaults to "linear".

Switch an automation signal’s time base from cycles to seconds (automation signals only).

use "std/signals" { automation };
let env = automation(#[0, 400], #[1.5, 2000]).in_seconds();

Both constructors require an active MIDI input connection.

MIDI CC input as a 0–1 signal. One argument reads the CC from any channel; two arguments pin it to a channel (0–15). CC numbers are 0–127.

use "std/signals" { Cc };
let mod_wheel = Cc(1);
let ch1_cutoff = Cc(0, 74);

Blocks until you move a CC knob or fader (10-second timeout), then returns a signal bound to that exact channel and CC number.

use "std/signals" { Cc_learn };
let cutoff = Cc_learn(); // move a knob now

Stepped signal from a pattern — outputs the value of the active event (0 during rests).

use "std/signals" { signal };
use "std/effects" { Delay };
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("CR-78")));
drums << [bd sd bd sd];
let d = Delay(0.25, 0.5);
drums.load_effect(d);
d.param("Time") << signal(#[0, 50, 100, 50]).range(0.1, 0.5);

Linear ramp from start to end over duration cycles (default 1), holding at end afterwards.

use "std/signals" { signal_ramp };
use "std/effects" { Lowpass };
use "std/instruments" { Sampler, Kit };
let hats = AudioTrack("hats");
hats.load_instrument(Sampler(Kit("CR-78")));
hats << [hh*8];
let lpf = Lowpass(400);
hats.load_effect(lpf);
lpf.param("Cutoff") << signal_ramp(400, 4000, 4); // 4-cycle sweep

Signals combine with each other and with numbers using arithmetic operators; the result is a new signal evaluated sample-wise.

OperatorExampleMeaning
+Sine(1) + Saw(3)Addition
-Sine(1) - Tri(2)Subtraction
*Sine(1) * Sine(7)Multiplication (ring mod)
/Sine(1) / 2Division
- (unary)-Sine(1)Negation
use "std/signals" { Sine, Saw };
let scaled = Sine(4) * 0.5; // halve the amplitude
let offset = Sine(2) + 0.3; // DC offset
let complex = (Sine(1) + Saw(3) * 0.5).range_exp(200, 8000).smooth(5);

Map the signal’s output linearly to [min, max].

use "std/signals" { Sine };
let lfo = Sine(4).range(0.2, 0.8);

Map exponentially — equal output movement covers equal pitch intervals. Use this for frequency parameters.

use "std/signals" { Sine };
let cutoff_lfo = Sine(1).range_exp(200, 4000);

Set the phase-reset mode: "cycle" resets phase every cycle, "beat" every beat, "free" never.

use "std/signals" { Sine };
let locked = Sine(4).retrigger("beat");

Opt out of bind-time phase reset. By default a signal restarts its local clock at the next cycle boundary after binding; .continuous() keeps it on the global playback clock, so re-evaluating a line tweaks the LFO without resetting its phase.

use "std/signals" { Sine, hz_to_periods };
use "std/effects" { Lowpass };
use "std/instruments" { Sampler, Kit };
let hats = AudioTrack("hats");
hats.load_instrument(Sampler(Kit("CR-78")));
hats << [hh*8];
let lpf = Lowpass(1000);
hats.load_effect(lpf);
lpf.param("Cutoff") << Sine(hz_to_periods(2)).range(200, 4000).continuous();

One-pole lowpass smoothing — removes zipper noise from stepped sources like MIDI CC.

use "std/signals" { Cc };
let smoothed = Cc(74).smooth(50).range(200, 4000);

Set a named parameter on a DSP signal instance (chainable). DSP signals also support set_buffer(name, data) and get_buffer(name) for buffer access.

Register a signal for scope visualization in the editor. The label defaults to a description of the signal.

use "std/signals" { Sine };
scope(Sine(2).range(0.2, 0.8), "lfo");

Define custom compiled signals with the dsp signal block — params, state, buffers, and per-sample code running in the audio engine. Instances are ordinary signals: all methods above apply, and they bind with << like any other signal. See DSP Signals, Functions & Objects for the block syntax and DSP Reference for the built-in functions available inside.