Share DSP Code
DSP code is shared in three ways. All three are inlined when the block is compiled, so a call costs nothing at audio rate. Pick the smallest one that does the job:
| You need… | Write |
|---|---|
| a helper for one block | a fn inside that block |
| the same pure function in several blocks | a dsp fn at the top of the file |
| a component with its own state, in several places | a dsp object at the top of the file |
Split a block into helpers
Section titled “Split a block into helpers”Any fn in a block besides process or render is a helper. It can read and write the block’s
param, state and buffer names, but only sees its own arguments — pass it what it needs:
use "std/instruments" { Sampler, Kit };
dsp effect SoftClipper { param drive: 2.0 range(1, 10); param mix: 0.5 range(0, 1);
fn soft_clip(x) -> out { return x / (1.0 + __native("abs", x)); }
fn blend(dry, wet) -> out { return dry * (1.0 - mix) + wet * mix; }
fn process(left, right) -> (out_l, out_r) { return (blend(left, soft_clip(left * drive)), blend(right, soft_clip(right * drive))); }}
let drums = AudioTrack("drums");drums.set_instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];drums.set_effects(#[SoftClipper("softclipper")]);
PLAY;Share a pure function
Section titled “Share a pure function”Move the helper out of the block and prefix it with dsp. Every dsp block in the file can call
it. A dsp fn has no param, state or buffer, and returns one value — call it once per
channel:
dsp fn softclip(x) -> out { return x / (1.0 + __native("abs", x));}
dsp effect Saturate { param drive: 2.0 range(1, 10);
fn process(left, right) -> (out_l, out_r) { return (softclip(left * drive), softclip(right * drive)); }}
dsp instrument ClippedSine { voice state phase: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { phase = __native("fract", phase + __native("mtof", note) * INV_SR); let sample = softclip(__native("sin", phase * TWOPI) * 4.0) * velocity * gate; return (sample, sample); }}Share a component with state
Section titled “Share a component with state”When the shared code must remember something — a filter’s last sample, a delay line — write a
dsp object. Give it state and buffer declarations and methods with any names. Each
let x = OnePole() in a block is a separate copy with its own state, so make one per channel:
use "std/instruments" { Sampler, Kit };
dsp object OnePole { state prev: 0.0;
fn lp(input, coeff) -> out { prev = prev + coeff * (input - prev); return prev; }}
dsp effect Smooth { param amount: 0.1 range(0.001, 1);
fn process(left, right) -> (out_l, out_r) { let lp_l = OnePole(); let lp_r = OnePole(); return (lp_l.lp(left, amount), lp_r.lp(right, amount)); }}
let drums = AudioTrack("drums");drums.set_instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];drums.set_effects(#[Smooth("smooth")]);
PLAY;An object has no param: its block’s params reach it as arguments, like amount above.
Import DSP code from another file
Section titled “Import DSP code from another file”Put the shared code in a module, and import it into another module with a selective use:
dsp fn softclip(x) -> out { return x / (1.0 + __native("abs", x));}
dsp object OnePole { state prev: 0.0; fn lp(input, coeff) -> out { prev = prev + coeff * (input - prev); return prev; }}use "./filters" { softclip, OnePole };
dsp effect Warm { param tone: 0.2 range(0, 1);
fn process(input) -> output { let lp = OnePole(); return lp.lp(softclip(input), tone); }}Only a module’s dsp blocks can call imported DSP code — not the blocks in the file you run or in
a selection you execute. To use softclip from your piece, import Warm, the block that calls it:
use "./warm" { Warm };
let bass = AudioTrack("bass");bass.set_effects(#[Warm("warm")]);Mark a definition private to keep it out of the module’s exports.
Related
Section titled “Related”- DSP Blocks — the rules for helpers,
dsp fnanddsp object - Modules —
use, selective imports andprivate