Your First Effect
On this page you write your own audio effects in the resonon language: a gain stage, a tremolo, an echo and a drive. At the end, you change an effect while you hear it.
An effect changes the audio that goes through a track. In a DAW, it is an insert on a channel strip. Here, you write what it does to each sample.
Work in a project, as in Your First Sound.
Write each step into src/main.non, select it, and press Cmd+Enter. Every step adds to what the
previous one built.
Before you start
Section titled “Before you start”Finish Language for a Reusable Piece and
Signals and Modulation.
Open src/main.non, evaluate RESET; on its own, then remove that line.
The session should be silent before you create the drums and gain stage. No plugin is needed.
Write a gain stage
Section titled “Write a gain stage”The simplest effect changes the volume:
use "std/instruments" { Sampler, Kit };
dsp effect Gain { param level: 1.0 range(0, 2);
fn process(left, right) -> (out_l, out_r) { return (left * level, right * level); }}
let drums = AudioTrack("drums");drums.set_instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
let gain = Gain("gain");drums.set_effects(#[gain]);
PLAY;You hear the beat, and it goes through the effect that you wrote.
dsp effect Gain { … }defines a new effect calledGain.param level: 1.0 range(0, 2);is a knob. Its default is1.0, and it goes from0to2. In the effect, you read it like a variable.fn process(left, right) -> (out_l, out_r)runs one time for each sample — 48,000 times each second at 48 kHz. It gets the left and right input sample and returns the left and right output sample.Gain("gain")makes one of these effects, with the namegain.set_effectsputs it on the track, the same as a built-in effect.
Turn the knob
Section titled “Turn the knob”The knob is on the effect you kept in gain. Set it with <<:
gain.parameter("level") << 0.3;The beat is quieter. A knob also takes a signal, so it can move by itself:
use "std/signals" { Sine };
gain.parameter("level") << Sine(1).range(0.2, 1.0);The volume now swells up and down once in each cycle. You did not write that into the effect:
any knob that you declare with param can be set and modulated from outside.
Remember a value between samples
Section titled “Remember a value between samples”process forgets everything between two samples. A state variable keeps its value. A tremolo
needs one, to know where its wobble is:
dsp effect Tremolo { param speed: 4.0 range(0.1, 20); param depth: 0.5 range(0, 1); state phase: 0.0;
fn process(left, right) -> (out_l, out_r) { phase = __native("fract", phase + speed * INV_SR); let wobble = 1.0 - depth * (0.5 + 0.5 * __native("sin", phase * TWOPI)); return (left * wobble, right * wobble); }}
drums.set_effects(#[gain, Tremolo("tremolo")]);The beat pulses four times a second, after the gain stage.
phasegoes from0to1, and moves on byspeed * INV_SRin each sample.INV_SRis one divided by the sample rate, sophasegoes aroundspeedtimes each second.__native("fract", …)keeps it between0and1, and__native("sin", …)turns it into a wave.__nativecalls resonon’s built-in DSP functions. Hover a name in your editor to see what it does.
Delay the signal
Section titled “Delay the signal”An echo must keep a lot of samples. A buffer is a fixed-size array for that, and its index wraps
around by itself, so it works as a circular delay line:
dsp effect Echo { param time: 0.25 range(0.001, 1.0); param feedback: 0.5 range(0, 0.99); param mix: 0.4 range(0, 1); buffer buf_l(48000); buffer buf_r(48000); state wp: 0;
fn process(left, right) -> (out_l, out_r) { let rp = wp + 48000.0 - time * SR; let delayed_l = buf_l[rp]; let delayed_r = buf_r[rp]; buf_l[wp] = left + delayed_l * feedback; buf_r[wp] = right + delayed_r * feedback; wp = (wp + 1.0) % 48000.0; return (left + delayed_l * mix, right + delayed_r * mix); }}
drums.set_effects(#[gain, Echo("echo")]);Each hit now repeats a quarter of a second later, and fades out.
wp is where the effect writes, and rp is where it reads, time * SR samples back. SR is the
sample rate. The chain is now the gain stage and the echo: the tremolo is gone, because set_effects sets
the whole chain.
Split out a helper
Section titled “Split out a helper”A long process is hard to read. Put a part of it into its own function, in the same block:
dsp effect Drive { param drive: 4.0 range(1, 10); param tone: 3000 range(200, 12000);
fn soft_clip(x) -> out { return x / (1.0 + __native("abs", x)); }
fn process(left, right) -> (out_l, out_r) { let l = soft_clip(left * drive); let r = soft_clip(right * drive); return (__native("svf_lp", l, tone, 0.7), __native("svf_lp", r, tone, 0.7)); }}
let drive = Drive("drive");drums.set_effects(#[drive, Echo("echo")]);The drums are louder and rougher, and the echo is still there.
soft_clip pushes a loud signal smoothly toward -1 and 1. __native("svf_lp", input, cutoff, resonance) is a built-in lowpass filter that takes away the harsh top. The
DSP builtins list every function you can call like this.
Change it while it plays
Section titled “Change it while it plays”Turn the tone down first:
drive.parameter("tone") << 800;Now edit the effect itself. Change the soft_clip line to a harder clip, then select only the
dsp effect Drive { … } block and run it:
dsp effect Drive { param drive: 4.0 range(1, 10); param tone: 3000 range(200, 12000);
fn soft_clip(x) -> out { return __native("clamp", x, -0.5, 0.5); }
fn process(left, right) -> (out_l, out_r) { let l = soft_clip(left * drive); let r = soft_clip(right * drive); return (__native("svf_lp", l, tone, 0.7), __native("svf_lp", r, tone, 0.7)); }}The sound changes with no click, and the beat does not stop. The tone is still at 800: an edit
changes the code, not the knobs you have turned. If you add or remove a param, the knobs no
longer match — run the set_effects line again to build the effect again from its defaults.
Next steps
Section titled “Next steps”You wrote an effect, gave it knobs, state and a buffer, and changed it while it played.
- Your First Instrument — make sound from notes
- Sidechain a Custom Effect — give an effect a second input
- Share DSP Code — use one helper in many blocks
- DSP Blocks — everything a
dsp effectcan declare