Your First Instrument
On this page you write a synthesizer in the resonon language. You start with a sine wave and end with a filtered synth that you change while it plays.
An effect changes audio that already exists. An instrument makes audio from notes: a pattern sends it notes, and it turns each one into sound.
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 Your First Effect. Open src/main.non,
evaluate RESET; on its own, then remove that line. Check that the earlier drum track is silent.
The synth below makes its own sound; it needs no samples or plugin.
Play a sine wave
Section titled “Play a sine wave”dsp instrument SineOsc { voice state phase: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = __native("mtof", note); phase = __native("fract", phase + freq * INV_SR); let sample = __native("sin", phase * TWOPI) * velocity * gate; return (sample, sample); }}
let synth = AudioTrack("synth");synth.set_instrument(SineOsc());synth << [c4 e4 g4 c5];
PLAY;You hear four notes, played by a synth that you wrote.
dsp instrument SineOsc { … }defines a new instrument calledSineOsc.fn render(note, velocity, gate)runs one time for each sample, and returns a left and a right sample.noteis the MIDI note number,velocityis how hard it was played (0to1), andgateis1while the note is held and0after it is released.__native("mtof", note)changes the note number into a frequency in Hz.phasegoes from0to1once for each period of that frequency, andsinturns it into a wave.SineOsc()makes one of these instruments, andset_instrumentputs it on the track.
Play a chord
Section titled “Play a chord”Send the track a chord:
synth << [c4:maj e4:min];You hear three notes at the same time. The instrument gives each note its own voice: its own
copy of render, with its own phase. That is what voice state means. Each voice starts again
from 0.0 when its note starts, so the notes don’t get in each other’s way.
Shape each note
Section titled “Shape each note”A sine that starts and stops at full level clicks. Give each voice an envelope, which fades the note in while it is held and out after it is released:
dsp instrument Synth { param attack: 0.002 range(0.0001, 0.1); param release: 0.001 range(0.0001, 0.1);
voice state phase: 0.0; voice state env: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = __native("mtof", note); phase = __native("fract", phase + freq * INV_SR); let osc = __native("sin", phase * TWOPI);
let target = gate * velocity; if gate > 0.0 { env = env + (target - env) * attack; } else { env = env + (target - env) * release; }
let sample = osc * env; return (sample, sample); }}
synth.set_instrument(Synth());synth << [c4 _ e4 _ g4 _ c5 _];The notes are softer at the start and ring out at the end.
env moves a small step toward target in each sample. While the note is held, target is the
velocity and the step is attack; after the release, target is 0 and the step is release.
A smaller step is a slower fade.
Add a filter
Section titled “Add a filter”Write a second instrument, FilteredSynth. It is the same as Synth, but its oscillator is a
sawtooth, which has a lot of high harmonics, and a lowpass filter follows it:
dsp instrument FilteredSynth { param attack: 0.002 range(0.0001, 0.1); param release: 0.001 range(0.0001, 0.1); param cutoff: 1200 range(20, 20000);
voice state phase: 0.0; voice state env: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = __native("mtof", note); phase = __native("fract", phase + freq * INV_SR); let osc = phase * 2.0 - 1.0;
let target = gate * velocity; if gate > 0.0 { env = env + (target - env) * attack; } else { env = env + (target - env) * release; }
let sample = __native("svf_lp", osc * env, cutoff, 1.5); return (sample, sample); }}
let lead = FilteredSynth();synth.set_instrument(lead);__native("svf_lp", input, cutoff, resonance) is a built-in lowpass filter. The
DSP builtins list every function you can call like this.
This time you kept the instrument in lead, so you can reach its knobs.
Turn the knobs
Section titled “Turn the knobs”Set a knob by name, or send it a signal:
use "std/signals" { Sine };
print(lead.parameters());lead.set_parameter("release", 0.0002);lead.parameter("cutoff") << Sine(2).range_exp(300, 6000);parameters() returns every knob with its range and value, and print shows them as a table. The notes now ring longer, and the filter
opens and closes twice every cycle.
The instrument must be on the track before you can read its knobs: FilteredSynth() only describes it,
and set_instrument builds it.
Change it while it plays
Section titled “Change it while it plays”Change the waveform line to a square wave, then select only the dsp instrument FilteredSynth { … }
block and run it:
dsp instrument FilteredSynth { param attack: 0.002 range(0.0001, 0.1); param release: 0.001 range(0.0001, 0.1); param cutoff: 1200 range(20, 20000);
voice state phase: 0.0; voice state env: 0.0;
fn render(note, velocity, gate) -> (out_l, out_r) { let freq = __native("mtof", note); phase = __native("fract", phase + freq * INV_SR); let osc = if phase < 0.5 { 1.0 } else { -1.0 };
let target = gate * velocity; if gate > 0.0 { env = env + (target - env) * attack; } else { env = env + (target - env) * release; }
let sample = __native("svf_lp", osc * env, cutoff, 1.5); return (sample, sample); }}The sound changes with no click, and notes that are playing fade into the new version. The filter sweep and the longer release are still there: an edit changes the code, not the knobs.
Next steps
Section titled “Next steps”You wrote a polyphonic synth with an envelope and a filter, and changed it while it played.
- Write a Modulation Source — write the signal that turns a knob
- Voices — how notes get voices, and what happens when they run out
- DSP Blocks — everything a
dsp instrumentcan declare