Signals & Automation
Signals are continuous value streams used to modulate effect parameters, track volume, panning, and send levels. Resonon provides built-in oscillators, stepped sequences, custom functions, and breakpoint automation.
LFO Oscillators
Section titled “LFO Oscillators”Unipolar (0—1)
Section titled “Unipolar (0—1)”| Constructor | Waveform |
|---|---|
Sine() | Sine wave |
Saw() | Sawtooth |
Tri() | Triangle |
Square() | Square wave |
Rand() | Random (S&H) |
Perlin() | Perlin noise |
Bipolar (-1—1)
Section titled “Bipolar (-1—1)”| Constructor | Waveform |
|---|---|
Sine2() | Sine wave |
Saw2() | Sawtooth |
Tri2() | Triangle |
Square2() | Square wave |
Waveform Shapes
Section titled “Waveform Shapes”| Oscillator | Shape |
|---|---|
Sine() / Sine2() | Smooth sinusoidal. Starts at midpoint (0.5 / 0.0), peaks at 1/4 cycle, troughs at 3/4 cycle |
Saw() / Saw2() | Ramp up from min to max over one cycle, then jumps back. Saw(): 0→1. Saw2(): -1→1 |
Tri() / Tri2() | Symmetric triangle. Tri(): starts at 0, peaks at 1.0 at mid-cycle. Tri2(): starts at 1.0, troughs at -1.0 at mid-cycle |
Square() / Square2() | High for first half, low for second half. 50% duty cycle |
Rand() | Deterministic sample-and-hold. Same time + frequency always produces the same value (reproducible) |
Perlin() | Smooth continuous 1D gradient noise. Organic, slowly-varying modulation |
LFO speed is controlled by the argument:
- No argument or
1— 1 complete cycle per pattern cycle (tempo-synced) - Number
n—ncomplete cycles per pattern cycle hz(freq)— absolute frequency in Hertz (tempo-independent)
Sine() // 1 cycle per pattern cycle (default)Sine(4) // 4 cycles per pattern cycle (fast)Sine(0.25) // 1 cycle over 4 pattern cycles (slow)Sine(hz(2)) // Always 2 Hz regardless of tempoAt 120 BPM, Sine(4) completes 4 cycles in 2 seconds. With hz(), Sine(hz(1)) always completes 1 cycle per second regardless of tempo.
Bind-Time Epoch
Section titled “Bind-Time Epoch”By default, every signal binding (<<) gets its own local clock that starts at 0 on the next cycle boundary after the bind. This means one-shot signals like signal_ramp(0, 1, 4) always start from the beginning when you (re)bind them — no manual phase reset needed.
filter.Cutoff << signal_ramp(300, 4000, 4);// Re-evaluating the line restarts the ramp at 300 Hz on the next cycle..continuous() — Opt Out
Section titled “.continuous() — Opt Out”For LFOs where you want phase continuity across rebinds (so tweaking frequency doesn’t jump phase), use .continuous():
filter.Cutoff << Sine(hz(2)).range(200, 4000).continuous();// Stays on the global playback clock; rebinding doesn't reset phase..continuous() composes with other wrappers in any order:
Sine(2).continuous().range(0.1, 0.9)Sine(4).retrigger("cycle").continuous()Retrigger
Section titled “Retrigger”Controls when the LFO phase resets within its local clock:
lfo.retrigger("cycle") // Reset phase at every cycle startlfo.retrigger("beat") // Reset phase at every beatlfo.retrigger("free") // Free-running, never resets (default)delay.Feedback << Sine(4).retrigger("beat").range(0.2, 0.7);Range Mapping
Section titled “Range Mapping”.range(min, max) — Linear
Section titled “.range(min, max) — Linear”Maps signal output linearly from min to max.
filter.Cutoff << Sine(1).range(200, 4000);// LFO=0 -> 200, LFO=0.5 -> 2100 (arithmetic midpoint), LFO=1 -> 4000.range_exp(min, max) — Exponential
Section titled “.range_exp(min, max) — Exponential”Maps signal output exponentially. The midpoint is the geometric mean, making sweeps sound perceptually even. Ideal for frequencies.
filter.Cutoff << Sine(1).range_exp(200, 4000);// LFO=0 -> 200, LFO=0.5 -> 894 (geometric midpoint), LFO=1 -> 4000Exponential mapping spends equal time in each octave, while linear mapping rushes through lows and lingers in highs.
Signal Algebra
Section titled “Signal Algebra”Signals support arithmetic operators for combining and scaling:
| Expression | Result |
|---|---|
signal + signal | Sample-wise sum |
signal - signal | Sample-wise difference |
signal * signal | Sample-wise product (ring modulation) |
signal / signal | Sample-wise division |
signal + number | DC offset |
signal * number | Amplitude scaling |
number - signal | Inverted offset |
-signal | Negation (flips sign) |
// Mix two LFOsfilter.Cutoff << (Sine(1) + Tri(3)) * 0.5;
// Scale a signaldelay.Time << Sine(2) * 0.3;
// DC offsetdelay.Feedback << Sine(4) * 0.2 + 0.5;
// Ring modulationfilter.Cutoff << (Sine(1) * Sine(7)).range_exp(200, 4000);
// Negatefilter.Cutoff << (-Sine(1)).range(200, 4000);Arithmetic composes freely with .range(), .smooth(), and other signal methods:
let lfo = (Sine(1) + Saw(3) * 0.5).range_exp(200, 8000).smooth(5);filter.Cutoff << lfo;Stepped Signals
Section titled “Stepped Signals”signal(#[values])
Section titled “signal(#[values])”Steps through an array of values, one per beat:
delay.Time << signal(#[0.1, 0.2, 0.35, 0.15]);signal(pattern)
Section titled “signal(pattern)”Converts a pattern into stepped signal values:
delay.Feedback << signal([C4 E4 G4 C5]).range(0.1, 0.7);delay.Time << signal([0.1 0.25 0.4 0.15]);Patterns auto-convert to signals when used with <<, so the signal() wrapper is optional unless you need chaining:
delay.Feedback << [0.1 0.3 0.5 0.3]; // Auto-convertsdrums.volume << [0.5 1.0 0.5 1.0]; // Works for track params toosignal_ramp(start, end)
Section titled “signal_ramp(start, end)”Linear ramp that loops every cycle:
filter.Cutoff << signal_ramp(0, 1).range_exp(200, 4000); // Sawtooth rampfilter.Cutoff << signal_ramp(1, 0).range_exp(200, 4000); // Inverted rampsignal_ramp(start, end, duration)
Section titled “signal_ramp(start, end, duration)”Linear ramp over a custom number of cycles:
filter.Cutoff << signal_ramp(0, 1, 4).range_exp(200, 4000); // Slow sweep over 4 cyclesCustom Signals
Section titled “Custom Signals”Custom signals are built with dsp signal blocks — see the DSP Signals section below for the full reference. They compile to per-sample graphs and replace the old signal_full() wrapper.
DSP Signals
Section titled “DSP Signals”dsp signal blocks define compiled modulation sources using the same DSP engine as effects and instruments. They run at audio sample rate and have access to state, parameters, buffers, helper functions, and all DSP builtins.
Basic Syntax
Section titled “Basic Syntax”dsp signal Name { fn process() -> out { // return a value per sample }}The process function returns a single f64 value per sample. Runtime builtins available inside the body:
| Builtin | Description |
|---|---|
TIME | Elapsed time in seconds since the signal’s epoch |
CPS | Cycles per second (tempo-derived) |
CYCLE | Current cycle number (integer) |
BPC | Beats per cycle (default 4, configurable via setbpm) |
SR | Sample rate in Hz |
INV_SR | Inverse sample rate (1 / SR) |
CYCLE_PHASE | Fractional position within the current cycle (0→1) |
BEAT_PHASE | Fractional position within the current beat (0→1) |
CYCLE_EDGE | 1.0 on the sample at a cycle boundary, 0.0 otherwise |
BEAT_EDGE | 1.0 on the sample at a beat boundary, 0.0 otherwise |
All DSP builtins are available: sin, cos, abs, noise(), delay(), filters, etc.
State and Parameters
Section titled “State and Parameters”dsp signal Wobble { param speed: 4.0 range(0.1, 40); param depth: 0.5 range(0, 1); state phase: 0.0;
fn process() -> out { phase = fract(phase + speed * CPS * INV_SR); return 0.5 + depth * 0.5 * sin(phase * TWOPI); }}paramdeclares configurable values with defaults and rangesstatedeclares mutable per-sample variables that persist across samplesbufferdeclares fixed-size arrays (for delay lines, etc.)
Helper Functions
Section titled “Helper Functions”Local fn declarations inside a dsp signal are helper functions. They can read and write the signal’s state.
dsp signal SmoothNoise { state prev: 0.0; param smoothing: 0.001 range(0.0001, 0.1);
fn smooth(input) -> out { prev = prev + smoothing * (input - prev); return prev; }
fn process() -> out { return smooth(noise()); }}Using dsp fn and dsp object
Section titled “Using dsp fn and dsp object”dsp signal can call module-level dsp fn helpers and instantiate dsp object components:
dsp object OnePole { state prev: 0.0; fn lp(input, cutoff) -> out { prev = prev + cutoff * (input - prev); return prev; }}
dsp signal FilteredNoise { param cutoff: 0.01 range(0.001, 0.5);
fn process() -> out { let filt = OnePole(); return filt.lp(noise(), cutoff); }}Upstream Signal Inputs
Section titled “Upstream Signal Inputs”A dsp signal process function can accept other signals as per-sample inputs. Declare them as parameters in the fn process(...) signature:
dsp signal MyRange { param lo: 0; param hi: 1; fn process(source) -> out { return lo + source * (hi - lo); }}Pass signals at instantiation — positionally or by name:
let ranged = MyRange(Sine(2)); // positionallet ranged = MyRange(source: Sine(2)); // namedlet half = MyRange(0.5); // numbers auto-wrap as constant signalsMultiple inputs work the same way:
dsp signal CrossFade { param mix: 0.5; fn process(a, b) -> out { return a * (1.0 - mix) + b * mix; }}let blended = CrossFade(Wobble(), SmoothNoise());Upstream signals are composable — one dsp signal can feed into another:
let chain = MyRange(Wobble()).range_exp(200, 8000);Signal-Valued Parameters
Section titled “Signal-Valued Parameters”Parameters can be driven by signals instead of static values. Pass a signal as a param argument at instantiation — it will be processed per-sample, modulating the parameter in real time:
// Named: lo bound modulated by a slow sinelet dynamic = MyRange(Sine(2), lo: Sine(0.5), hi: 0.9);
// Positional: args fill upstream inputs first, then params in orderlet dynamic = MyRange(Sine(2), Sine(0.5), 0.9);When there are no upstream inputs, positional args fill params directly:
let fast_wobble = Wobble(8.0, 0.8); // speed=8, depth=0.8let modulated = Wobble(Sine(0.5), 1); // speed modulated by a sineSignal-driven params bypass range() clamping and smoothing — the signal provides continuous values directly.
Instantiation
Section titled “Instantiation”Call the signal name with () to create an instance. The result is a regular signal — use it with <<, .range(), .smooth(), etc. Signals with upstream inputs require the matching signal arguments.
filter.Cutoff << FilteredNoise().range_exp(200, 4000);delay.Feedback << Wobble().range(0.1, 0.6);When to Use DSP Signals
Section titled “When to Use DSP Signals”| Use case | Recommended approach |
|---|---|
| Simple oscillator | Built-in Sine(), Saw(), etc. |
| Stepped values | signal(#[...]) or signal([pattern]) |
| Breakpoint envelopes | automation() |
| Complex per-sample logic with state | dsp signal |
| Noise + filtering + nonlinear processing | dsp signal |
| Reusing DSP components as modulation | dsp signal |
Automation
Section titled “Automation”Creating Breakpoint Envelopes
Section titled “Creating Breakpoint Envelopes”automation() takes breakpoints as #[time, value] arrays. Time is in cycles (tempo-relative).
let sweep = automation(#[0, 0], #[4, 1]);filter.Cutoff << sweep.range_exp(200, 4000);// Linear ramp from 200 Hz to 4000 Hz over 4 cycles.at() Builder
Section titled “.at() Builder”Chain breakpoints one at a time:
let sweep = automation().at(0, 0).at(4, 1);filter.Cutoff << sweep.range_exp(200, 4000);Curve Types
Section titled “Curve Types”Each breakpoint accepts an optional third element specifying the interpolation curve for the segment after that point:
| Curve | Description |
|---|---|
"linear" | Straight line interpolation (default) |
"step" | Hold value until next breakpoint |
"exp" | Exponential interpolation |
"smooth" | Cosine interpolation (eases in and out) |
"ease-in" | Slow start, fast finish |
"ease-out" | Fast start, slow finish |
"ease-in-out" | Slow start and end, fast middle |
"ease" | CSS default ease timing |
"bezier(x1,y1,x2,y2)" | Custom cubic bezier (x in [0,1], y unrestricted) |
// Step curve: discrete changes, no interpolationlet steps = automation( #[0, 0.1, "step"], #[1, 0.25, "step"], #[2, 0.4, "step"], #[3, 0.15, "step"]);delay.Time << steps;
// Exponential sweepfilter.Cutoff << automation(#[0, 0.0, "exp"], #[4, 1.0]).range_exp(200, 4000);
// Mixed curves in one envelopelet varied = automation( #[0, 0.0, "smooth"], #[2, 1.0, "ease-out"], #[4, 0.3, "exp"], #[6, 0.3, "step"], #[8, 0.3, "linear"], #[10, 0.0]);With .at() chaining:
let envelope = automation() .at(0, 0.0, "ease-in") .at(3, 1.0, "ease-out") .at(5, 0.5) .at(8, 0.0);Custom Bezier Curves
Section titled “Custom Bezier Curves”Specify control points for precise curve shaping:
// S-curve: slow start and end, fast middleautomation(#[0, 0.0, "bezier(0.7, 0.0, 0.3, 1.0)"], #[4, 1.0]);
// Overshoot: y values outside [0,1] create elastic motionautomation().at(0, 0.0, "bezier(0.2, 1.5, 0.8, -0.5)").at(4, 1.0);.in_seconds()
Section titled “.in_seconds()”Convert from cycle-based to seconds-based timing:
let fade = automation(#[0, 1.0], #[5, 0.0]).in_seconds();// Breakpoint times are now in seconds, not cyclesHold Behavior
Section titled “Hold Behavior”- Before the first breakpoint, the first value is held
- After the last breakpoint, the last value is held
let delayed_start = automation(#[2, 0.0], #[4, 1.0]);delay.Feedback << delayed_start.range(0.1, 0.6);// Cycles 0-2: holds at 0.1 (first value)// Cycles 2-4: ramps to 0.6// Cycle 4+: holds at 0.6 (last value)Smoothing
Section titled “Smoothing”.smooth(time_ms)
Section titled “.smooth(time_ms)”Apply a one-pole lowpass filter to remove zipper noise from stepped or fast-changing signals:
delay.Time << signal(#[0.1, 0.2, 0.35, 0.15]).smooth(10);Modulation
Section titled “Modulation”Route signals to effect parameters with <<:
filter.Cutoff << Sine(2).range_exp(400, 4000); // Filter sweepdelay.Time << Sine(0.25).range(0.1, 0.4); // Slow delay modulationdelay.Feedback << Saw(4).range(0.1, 0.6); // Sawtooth feedbackdrums.volume << Sine(2).range(0.6, 1.0); // Tremolodrums.pan << Sine2(0.5); // AutopanCombine multiple modulation sources across a track’s effects:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.load_instrument(Sampler(Kit("cr78")));drums << [bd sd [bd bd] sd];
let filter = Lowpass(2000);let delay = Delay(0.25, 0.4);drums.load_effect(filter);drums.load_effect(delay);
// Stepped filter positions + slow ramping feedbackfilter.Cutoff << signal(#[0.2, 0.5, 0.8, 0.4]).range_exp(400, 4000);delay.Feedback << signal_ramp(0, 1, 2).range(0.1, 0.6);See Also
Section titled “See Also”- Signal Methods Reference - Constructor table and method signatures
- Effects Reference - Effect parameters and
.modulate() - MIDI CC - Controlling external parameters via MIDI CC