Skip to content

Samplers

Resonon includes a built-in sample engine for drum machines and melodic instruments. Audio tracks host sampler instruments that respond to patterns.

use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("cr78")));
drums << [bd sd bd sd];
PLAY;

Audio tracks are channels for audio output. The name is used for identification and rendering.

let drums = AudioTrack("drums");
let bass = AudioTrack("bass");
let hats = AudioTrack("hats");

Each track needs an instrument loaded before it can produce sound.

drums.volume << 0.8; // Set volume to 80%
drums.pan << -0.5; // Pan left

Sampler() loads a drum kit where each sample is mapped to a name or MIDI note.

Sampler() // Empty sampler (no kit)
Sampler(Kit("name")) // With local kit
Sampler(Kit("package/kit")) // With package-qualified kit
ParameterTypeDescription
kitKitKit value (optional, creates empty sampler if omitted)
let kit = Sampler(Kit("cr78"));
drums.load_instrument(kit);
// Package-qualified name
let kit = Sampler(Kit("drumlib/808"));
drums.load_instrument(kit);

SamplerMelodic() is for pitched instruments (bass, keys, pads). Notes in patterns are mapped across the keyboard relative to a root note.

SamplerMelodic() // Empty sampler (no kit)
SamplerMelodic(Kit("name")) // With local kit
SamplerMelodic(Kit("package/kit")) // With package-qualified kit
ParameterTypeDescription
kitKitKit value (optional, creates empty sampler if omitted)

The workflow for melodic samplers is: create the sampler, load a sample with its root note, optionally set an envelope, then load onto a track.

let bass = SamplerMelodic(Kit("bass"));
bass.load_sample("sub_bass", "C1");
bass.envelope(10, 100, 1.0, 200);
let bass_track = AudioTrack("bass");
bass_track.load_instrument(bass);
bass_track << [C1 _ C1 _ | E1 _ E1 _ | G1 _ G1 _ | A1 _ A1 _];

The << operator sends a pattern to a track. Reassigning replaces the current pattern.

drums << [bd sd bd sd];
drums << [bd*4]; // Replaces the previous pattern

Layered patterns use commas for parallel voices:

drums << [bd _ bd _, _ sd _ sd, hh*8];

The built-in CR-78 drum machine kit includes the following samples:

AbbreviationSampleMIDI Note
bdBass drum36 (C2)
sdSnare38 (D2)
hhHi-hat42 (F#2)
cyCymbal46 (A#2)
cpClap39 (D#2)
rsRimshot37 (C#2)
cbCowbell56
clClave75
maMaracas70
tbTambourine54
guGuiro73
hbHigh bongo60
lbLow bongo61
lcLow conga64
meMetal beat80

You can use MIDI note numbers instead of names, or mix both:

drums << [36 42 38 42]; // MIDI numbers
drums << [bd 42 sd 42]; // Mixed
MethodVelocityDescription
.ghost0.3Subtle ghost note
.soft0.5Half velocity
.loud1.0Full velocity
.accent1.0Emphasis
.vel(n)CustomExplicit 0.0—1.0
drums << [bd.loud sd.soft bd.loud sd.soft];
drums << [bd sd.ghost sd.ghost sd.ghost sd.accent];

.pitch(semitones) shifts sample pitch. Positive values go higher, negative go lower.

drums << [bd bd.pitch(5) bd bd.pitch(-5)];
drums << [bd bd.pitch(12) bd.pitch(-12)]; // Octave shifts

.pan(position) sets stereo position from -1 (full left) to 1 (full right).

drums << [bd.pan(-1) sd.pan(1) bd.pan(-1) sd.pan(1)];
drums << [hh.pan(-0.3) hh.pan(0.3) hh.pan(-0.3) hh.pan(0.3)];

Combine multiple methods on a single sample:

drums << [bd.loud.pan(-0.3) sd.soft.pitch(2)];
drums << [bd.loud.pitch(-2).pan(-0.5) sd.accent.pitch(1).pan(0.5)];

Track volume is set with the .volume property:

drums.volume << 0.8;
drums.volume << Sine(2).range(0.6, 1.0); // Tremolo effect

Kit() loads kit metadata without creating a sampler plugin. Use it to inspect a kit before loading.

MethodArgsReturnsDescription
.name()StringKit display name
.mode()String"oneshot" or "melodic"
.author()StringKit author (empty string if not set)
.samples()ArrayList of sample names
.sample_info()Array[name, note, file] entries sorted by note
.info()NulPrint formatted kit table
let k = Kit("cr78");
show(k); // Formatted sample table
k.name(); // "CR-78"
k.mode(); // "oneshot"
k.author(); // ""
k.samples(); // ["bd", "rs", "sd", "cp", ...]
k.sample_info(); // [[bd, 36, "samples/BD_1.wav"], ...]
k.info(); // Print formatted table to console

Once a drum sampler is loaded, it exposes methods for inspection, sample selection, envelope editing, note remapping, and parameter access.

MethodArgsReturnsDescription
.slot(name)String or NumberDrumSampleRefGet a reference to a sample slot
.samples()ArrayList of sample names
.sample_info()Array[name, note, file] entries
.note_for(name)StringNumberMIDI note for a sample name
.get(name)StringSampleExtract sample as standalone value
.select(name_or_note)String or NumberSamplerSelect a slot for editing
.selected()NumberCurrently selected note
.set_note(name, note)String, Number/StringSamplerRemap a sample to a new note
.choke_group(id, names)Number, ArraySamplerAssign samples to a choke group
.envelope(...)4 or 5 argsSamplerSet ADSR envelope
.name()StringKit name
.param(path)StringNumberRead a parameter value
.set_param(path, value)String, NumberSamplerSet a parameter value
.set_param_norm(path, value)String, NumberSamplerSet a parameter using normalized 0–1 value
.params()NulPrint all parameters
.params(filter)StringNulPrint parameters matching filter
let drums = Sampler(Kit("cr78"));
drums.samples(); // ["bd", "rs", "sd", "cp", ...]
drums.sample_info(); // [[bd, 36, "samples/BD_1.wav"], ...]
drums.note_for("bd"); // 36
drums.name(); // "cr78"

.get(name) extracts a sample as a standalone Sample value:

let kick = drums.get("bd");

There are two ways to set an envelope on a drum sample:

5-argument form — specify the sample name directly:

drums.envelope("bd", 5, 50, 0.8, 100);

Select-then-4-argument form — select a slot first, then apply:

drums.select("bd");
drums.envelope(5, 50, 0.8, 100);
drums.selected(); // 36 (the MIDI note of the selected slot)
ParameterTypeRangeDefaultDescription
attackNumber0—5000 msAttack time in milliseconds
decayNumber0—5000 msDecay time in milliseconds
sustainNumber0.0—1.0Sustain level (amplitude)
releaseNumber0—5000 msRelease time in milliseconds

.set_note(name, note) reassigns a sample to a different MIDI note. The note can be a number or a note name:

drums.set_note("bd", 48); // Remap bass drum to C3
drums.set_note("bd", "C3"); // Same, using note name

.choke_group(id, names) assigns samples to a choke group from code. Samples in the same group cut each other off (e.g., open hi-hat chokes closed hi-hat). The group id must be 1—255.

let drums = Sampler(Kit("cr78"));
drums.choke_group(1, ["hh", "cy"]); // Group 1: hh and cy choke each other
drums.choke_group(2, ["hb", "lb"]); // Group 2: bongos — group 1 is preserved

Multiple calls accumulate — each call adds to the existing overrides without removing previous ones. This is useful for prototyping choke configurations before committing them to kit.toml.

Use .params() to list all available parameters, and .param() / .set_param() to read and write them:

drums.params(); // Print all parameters
drums.params("bd"); // Print parameters for bass drum only
drums.param("slot_0/gain"); // Read a parameter
drums.set_param("slot_0/gain", 0.5); // Set a parameter

Parameters are addressed by path. After selecting a slot, legacy names like "Gain", "Pan", "Attack", etc. are also accepted:

drums.select("bd");
drums.set_param("Gain", 0.8);

The .slot() method on a drum sampler returns a DrumSampleRef, a reference to a specific sample slot. This is the most ergonomic way to edit individual drum samples.

let drums = Sampler(Kit("cr78"));
drums.load_instrument(drums);
let kick = drums.slot("bd"); // DrumSampleRef for bass drum
let snare = drums.slot("sd"); // DrumSampleRef for snare

Pass any sample name as a string, or a MIDI note number:

drums.slot("hh") // hi-hat (by name)
drums.slot("cp") // clap
drums.slot(36) // kick (by MIDI note)

Because names are strings, you can use variables:

for s in drums.samples() {
drums.slot(s).gain << 0.7;
}
MethodArgsReturnsDescription
.envelope(a, d, s, r)4 NumbersDrumSampleRefSet ADSR envelope
.name()StringSample name
.note()NumberMIDI note number
drums.slot("bd").name(); // "bd"
drums.slot("bd").note(); // 36
drums.slot("bd").envelope(5, 50, 0.8, 100);

DrumSampleRef properties return automatable parameter references. You can assign values directly with <<, or modulate them with signals.

PropertyDescription
.gainSample gain
.panStereo position
.attackAttack time (ms)
.decayDecay time (ms)
.sustainSustain level
.releaseRelease time (ms)
.slice_startSample start point
.slice_endSample end point
.reverseReverse playback
drums.slot("bd").gain << 0.8;
drums.slot("bd").pan << -0.3;
drums.slot("sd").attack << 10;
drums.slot("sd").release << 200;
drums.slot("hh").pan << Sine(0.5).range(-1, 1); // Auto-pan hi-hat
drums.slot("bd").gain << Sine(2).range(0.6, 1.0); // Pulsing kick

Melodic samplers provide methods for loading samples, setting envelopes, loop control, and inspection.

MethodArgsReturnsDescription
.load_sample(name)StringSamplerLoad sample, root from kit.toml
.load_sample(name, root)String, String/NumberSamplerLoad sample with explicit root
.envelope(a, d, s, r)4 NumbersSamplerSet ADSR envelope
.root(note)String or NumberSamplerOverride root note
.set_loop(start, end)2 NumbersSamplerSet loop points in seconds
.set_loop_normalized(start, end)2 NumbersSamplerSet loop points (0.0—1.0)
.samples()ArrayList of sample names in the kit
.sample_info()Array[name, root_or_nil, file] entries
.root_for(name)StringNumber or NulRoot note for a sample
.get(name)StringSampleExtract sample as standalone value
.name()StringPlugin name
.param(path)StringNumberRead a parameter value
.set_param(path, value)String, NumberSamplerSet a parameter value
.set_param_norm(path, value)String, NumberSamplerSet a parameter using normalized 0–1 value
.params()NulPrint all parameters
.params(filter)StringNulPrint parameters matching filter

.load_sample() loads a sample from the kit and prepares it for pitched playback.

1-argument form — root note comes from kit.toml:

let keys = SamplerMelodic(Kit("keys"));
keys.load_sample("sound1"); // Root C4 from kit.toml

2-argument form — explicit root note:

keys.load_sample("sound1", "C4");
keys.load_sample("sound1", 60); // Same, using MIDI number

.root(note) changes the root note after loading, without reloading the sample:

keys.load_sample("sound1", "C4");
keys.root("C3"); // Shift root down an octave
keys.root(48); // Same, using MIDI number

.envelope(attack, decay, sustain, release) sets the ADSR envelope.

ParameterTypeRangeDefaultDescription
attackNumber0—5000 msAttack time in milliseconds
decayNumber0—5000 msDecay time in milliseconds
sustainNumber0.0—1.0Sustain level (amplitude)
releaseNumber0—5000 msRelease time in milliseconds
keys.envelope(10, 100, 0.8, 200); // 10ms attack, 100ms decay, 0.8 sustain, 200ms release

.set_loop(start_secs, end_secs) sets loop points in seconds. The values are converted to normalized positions using the loaded sample’s duration.

keys.set_loop(0.5, 2.0); // Loop from 0.5s to 2.0s

.set_loop_normalized(start, end) sets loop points directly as normalized positions (0.0—1.0):

keys.set_loop_normalized(0.3, 0.9); // Loop over the middle 60% of the sample

When start > end, reverse (ping-pong) looping is enabled:

keys.set_loop_normalized(0.9, 0.3); // Reverse loop
let keys = SamplerMelodic(Kit("keys"));
keys.samples(); // ["sound1", "sound2"]
keys.sample_info(); // [["sound1", 60, "/path/to/sound1.wav"], ...]
keys.root_for("sound1"); // 60 (C4)
keys.name(); // Plugin name
keys.params(); // Print all parameters
let pad = SamplerMelodic(Kit("keys"));
pad.load_sample("sound1", "C4");
pad.envelope(200, 300, 0.7, 500);
pad.set_loop_normalized(0.2, 0.8);
let pad_track = AudioTrack("pad");
pad_track.load_instrument(pad);
pad_track.volume << 0.6;
pad_track << [C3 _ E3 _ | G3 _ C4 _ | E4 _ G4 _ | C5 _ _ _];

Place your own samples in a kits/ folder:

kits/mykit/
kit.toml (optional metadata)
kick.wav
snare.wav
hihat.wav

A kit.toml file has three sections: [kit] for metadata, [defaults] for default note mappings, and [samples] for sample definitions.

[kit]
name = "My Kit"
author = "Your Name"
mode = "oneshot" # "oneshot" (drum) or "melodic"
FieldTypeDescription
nameStringDisplay name (falls back to directory name)
authorStringKit author
modeString"oneshot" for drums, "melodic" for pitched instruments

Default note mappings for sample names. Values can be note names or MIDI numbers:

[defaults]
bd = "C2"
sd = "D2"
hh = 42

Each sample has a key (the name used in patterns) and a configuration table:

FieldTypeDescription
filesArrayWAV file paths (relative to kit directory). Multiple files enable round-robin.
fileStringSingle file path (backward compatibility, prefer files)
noteNumber or StringMIDI note that triggers this sample
rootNumber or StringRoot note for melodic samples (original pitch of the recording)
choke_groupNumberSamples in the same group cut each other off
velocityNumberDefault velocity (0.0—1.0), defaults to 1.0
loop_startNumberLoop start position (0.0—1.0, normalized)
loop_endNumberLoop end position (0.0—1.0, normalized)
[kit]
name = "CR-78"
mode = "oneshot"
[samples]
bd = { files = ["samples/BD_1.wav", "samples/BD_2.wav"], note = "C2" }
sd = { files = ["samples/SD_1.wav", "samples/SD_2.wav"], note = "D2" }
hh = { files = ["samples/HH_1.wav"], note = "F#2", choke_group = 1 }
hh_open = { files = ["samples/HH_open.wav"], note = "A#2", choke_group = 1 }
[kit]
name = "keys"
mode = "melodic"
[samples]
sound1 = { files = ["samples/sound1.wav"], root = "C4" }
sound2 = { files = ["samples/sound2.wav"], root = "C3", loop_start = 0.3, loop_end = 0.9 }

When a sample’s MIDI note is not explicitly set, it is resolved in this order:

  1. Explicit note field in the sample config
  2. Default from the [defaults] section
  3. GM drum mapping based on the sample name (e.g., bd maps to C2)
  4. Sequential assignment starting from C2 (36)

If kit.toml is omitted, Resonon auto-detects WAV files in the kit directory. Each file becomes a sample named after the filename (without extension), with notes assigned sequentially.

let custom = Sampler(Kit("mykit"));

Kits from installed packages use the "package/kit" syntax:

let drums = Sampler(Kit("drumlib/808"));
let pad = SamplerMelodic(Kit("synthpack/pads"));

The search path for package kits is:

~/.config/resonon/lib/{package}/kits/{kit}/

See Package Management for details on installing and managing packages.

External CLAP instrument plugins can also be loaded onto audio tracks. See Plugins for details on loading and controlling CLAP instruments.

let synth = Instrument("Surge XT");
let lead = AudioTrack("lead");
lead.load_instrument(synth);
lead << [C4 E4 G4 C5];

Use get_instrument() to retrieve the instrument loaded on a track. This returns the original Sampler, SamplerMelodic, or Instrument value — a shared reference, so any parameter changes affect the live instrument.

let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("cr78")));
// Later, retrieve and tweak parameters
let kit = drums.get_instrument();
kit.slot("bd").envelope(2, 80, 0.3, 60);
kit.slot("hh").release << Sine(0.25).range(40, 180);

This is especially useful in multi-file projects where tracks are created in one module and used in another:

main.non
let drums = tracks.drums();
let kit = drums.get_instrument();
kit.slot("bd").gain << 0.8;

Returns NUL if no instrument has been loaded.