Plugins
Resonon’s built-in effects and samplers cover a lot of ground, but sometimes you want a specific third-party reverb, or a synth you already know. Resonon loads CLAP and VST3 plugins — as effects in a track’s chain, or as the instrument on a track — and lets you read, set, and modulate their parameters from code.
Two reverb plugins ship bundled with resonon and load instantly, with no setup. Name
one with Effect, and put it on a track:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
let reverb = Effect("reverb", "resonon-reverb"); // bundled — no scan neededdrums.fx(#[reverb]); // placing it is what loads it
reverb.param_set("Room", 0.8); // now the handle is live
PLAY;Effect("reverb", "resonon-reverb") names the plugin; fx is what loads it and sets it
as the track’s chain, like any built-in effect; param_set then dials the live instance
(A constructor is a description).
The other bundled plugin is "resonon-reverb-plate", a lusher plate reverb.
Setting Before Placement Seeds the Slot
Section titled “Setting Before Placement Seeds the Slot”Writing a parameter before placement is allowed, and does the obvious thing — it seeds
the slot, and the value lands on whatever instance the .fx() line builds. Reading one
before placement is not; there is nothing there to read yet.
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.instrument(Sampler(Kit("CR-78")));drums << [bd sd bd sd];
let reverb = Effect("reverb", "resonon-reverb");reverb.param_set("Room", 0.8); // recorded now...reverb.param_set("Mix", 0.4);drums.fx(#[reverb]); // ...and applied to the instance built here
show(reverb.param_get("Room")); // 0.8 — reading needs the placement above
PLAY;Finding Plugins
Section titled “Finding Plugins”resonon keeps a plugin database of what you have installed, and maintains it for you. Every run that starts the audio engine — running a file, the REPL, a server — sweeps the CLAP and VST3 folders in the background, and only opens bundles that are new or have changed since last time — so the sweep after installing one plugin costs one plugin, not your whole folder. Normally you never think about this.
The folders swept are the standard ones for your OS, CLAP_PATH and VST3_PATH, and every
[folders] plugins entry in ~/.resonon/preferences.toml. Those entries are read before the
sweep dispatches, so a folder of your own is included in it from the first run — which is the
concrete reason the folder list is data and not a function call. resonon config plugins add
writes one without opening the file, and reload_preferences() scans a folder you just added
without restarting.
You can still ask for a sweep explicitly, which is what you want right after installing something mid-session:
let plugins = plugin_scan();show(plugins.length()); // how many were foundprint(plugins); // formatted table
let reverbs = plugins.filter("reverb"); // narrow by name substringprint(reverbs);plugin_scan() returns a PluginList with .length(), .filter(name), .get(index),
and .data (the raw entries, each carrying name, id, vendor, and format). In
VSCode the same thing is on the palette as RESONON: Rescan Plugins, and from a
terminal it is resonon plugin scan.
To turn automatic sweeping off — scanning does run third-party code, and you may prefer
to say when — uncheck resonon.plugins.autoScan in VSCode, pass --plugin-auto-scan=false, or
set RESONON_PLUGIN_AUTO_SCAN=0 (Configuration). Plugins loaded by path or filename still
resolve without any sweep; only lookup by display name depends on one — and that now holds
for your own folders too, which it could not while they were named by a call nothing outside
a running session executed.
That on-demand resolve is not free the first time. A bundle the database has never seen — or one whose file has changed since it was recorded — is scanned right then, in a separate process, and your code waits for it: normally a moment, but up to 30 seconds for a plugin that is slow to load. The result is remembered, so it is a once-per-bundle cost.
That resolve happens on the constructor line, not at placement: Effect(...) looks the
name up straight away, so a typo — or a bundle the scanner skipped — is underlined where you
wrote it. What waits for .fx() / .instrument() is the expensive half: opening the binary,
activating it, enumerating its parameters. So a plugin that resolves but fails to load
reports on the placement line instead.
Loading Plugins
Section titled “Loading Plugins”Effect(slot, name) describes an effect; Instrument(name) describes an instrument. Neither
loads anything — but both resolve the name immediately, the same way, in order: a full
path ending in .clap/.vst3; a filename found in your plugin folders; or a display
name from the plugin database. If nothing matches, resonon lists the locations it searched
and the plugins it knows about, on the constructor line.
let shimmer = Effect("shimmer", "Valhalla Shimmer"); // by display namelet fx = Effect("fx", "/path/to/plugin.clap"); // by full pathlet synth = Instrument("Surge XT"); // an instrument pluginA track hosts a plugin instrument exactly like a built-in one:
let synth = Instrument("Surge XT");let lead = AudioTrack("lead");lead.instrument(synth);lead << [C4 E4 G4 C5];When a single bundle contains several plugins, pass the plugin ID as a second argument
to pick one — Effect("multi", "Multi-FX", "com.vendor.multi-fx.chorus"). With both a CLAP and a
VST3 build of the same plugin installed, CLAP is tried first; the plugin ID is how a piece says
which build it means, and it says so in the piece rather than in a machine-wide setting
(Configuration).
Parameters
Section titled “Parameters”Effect(...) is a description, not a loaded plugin
(A constructor is a description). Two
things happen at two different times: the plugin’s name is resolved on the constructor line,
and the plugin is loaded and activated when a track places it. So place it first with
.fx(), then inspect and dial it through the same handle. (Reading a parameter off an unplaced
effect is an error that says as much.)
.params() prints every parameter with its range, default, and current value; pass a
substring to filter a plugin with a long list:
let reverb = Effect("reverb", "resonon-reverb");master.fx(#[reverb]); // place it — now it's live
reverb.params(); // all of themreverb.params("room"); // just the matching onesRead a value with .param_get(name), and set one with .param_set(name, value), which
returns the plugin so calls chain:
let reverb = Effect("reverb", "resonon-reverb");master.fx(#[reverb]);
reverb.param_set("Room", 0.8) .param_set("Damp", 0.4) .param_set("Mix", 0.5);
show(reverb.param_get("Room")); // 0.8When you’d rather work in normalised terms, .param_set_norm(name, 0..1) maps a 0..1
value across the parameter’s real range — including any non-linear VST3 curve.
The << shorthand sets a parameter too, and it’s the one that also accepts a moving
signal — so the same syntax that sets a value can modulate it:
use "std/signals" { Sine };
let reverb = Effect("reverb", "resonon-reverb");master.fx(#[reverb]);
reverb.param("Mix") << 0.4; // set a valuereverb.param("Room") << Sine(0.2).range(0.3, 0.9); // ...or modulate itSee Signals & Automation for the full set of modulation sources.
Plugin GUI
Section titled “Plugin GUI”Many third-party plugins have a graphical editor. Check with .supports_gui(), then open
and close the window with .show_gui() and .hide_gui():
let lead = AudioTrack("lead");let synth = Instrument("Surge XT");lead.instrument(synth); // built here — the GUI methods need the instance
if synth.supports_gui() { synth.show_gui();}The bundled resonon reverbs have no GUI — .supports_gui() returns false — so you
drive them with .params() and param_set / <<. Most third-party plugins do.
Saving State
Section titled “Saving State”.save_state(path) writes a plugin’s full state — every parameter plus internal data —
to a file, and .load_state(path) reads it back into an instance. Both are chainable:
let lead = AudioTrack("lead");let synth = Instrument("Surge XT");lead.instrument(synth);
synth.save_state("presets/my_lead.preset"); // a read: needs the live plugin
let backing = AudioTrack("backing");let synth2 = Instrument("Surge XT");synth2.load_state("presets/my_lead.preset"); // a write: recorded, applied at placementbacking.instrument(synth2);This is the write/read asymmetry in miniature: save_state reads a running plugin, so it
needs the instrument on a track first, while load_state writes, so it can run before
placement and is replayed onto the instance the track builds.
Next Steps
Section titled “Next Steps”You can scan, load, and control external instruments and effects. Next, bring external sound into resonon.
- Recording & Input — live audio input and capturing your output
- Effects — the native DSP effects that need no plugin
- Signals & Automation — modulate plugin parameters with LFOs and envelopes