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("CR-78")));
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 << -6; // Cut 6 dB
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("CR-78"));
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

.vel(n) sets the velocity of a sample hit.

drums << [bd.vel(1.0) sd.vel(0.5) bd.vel(1.0) sd.vel(0.5)];
drums << [bd sd.vel(0.3) sd.vel(0.3) sd.vel(0.3) sd.vel(1.0)];

.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.vel(1.2).pan(-0.3) sd.vel(0.5).pitch(2)];
drums << [bd.vel(1.2).pitch(-2).pan(-0.5) sd.vel(1.0).pitch(1).pan(0.5)];

Track volume is set with the .volume property:

drums.volume << -6;
drums.volume << Sine(2).range(-12, 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("CR-78");
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, per-slot parameter 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
.set_note(name, note)String, Number/StringSamplerRemap a sample to a new note
.choke_group(id, names)Number, ArraySamplerAssign samples to a choke group
.name()StringKit name
.param(path)StringParamRefGet a parameter reference with .get(), .set(), .set_norm()
.param_get(path)StringNumberRead a parameter value
.param_set(path, value)String, NumberSamplerSet a parameter value
.param_set_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("CR-78"));
drums.samples(); // ["bd", "rs", "sd", "cp", ...]
drums.sample_info(); // [[bd, 36, "samples/BD_1.wav"], ...]
drums.note_for("bd"); // 36
drums.name(); // "CR-78"

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

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

.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("CR-78"));
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.

The sampler also exposes the generic instrument parameter interface for direct path-based access:

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

For per-sample parameter editing, prefer the .slot() API described below.

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("CR-78"));
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).param("gain") << 0.7;
}
MethodArgsReturnsDescription
.param(name)StringParamRefGet a parameter reference
.param_get(name)StringNumberRead a parameter value
.param_set(name, value)String, NumberDrumSampleRefSet a parameter value
.params()ArrayList available parameter names
.params(filter)StringArrayList parameter names matching filter
.envelope(a, d, s, r)4 NumbersDrumSampleRefSet ADSR envelope
.name()StringSample name
.note()NumberMIDI note number
NameDescription
gainSample gain
panStereo position
attackAttack time (ms)
decayDecay time (ms)
sustainSustain level
releaseRelease time (ms)
slice_startSample start point (0.0—1.0)
slice_endSample end point (0.0—1.0)
reverseReverse playback (0 or 1)

Use .param(name) to get a parameter reference, then << to set or modulate it:

drums.slot("bd").param("gain") << 0.8;
drums.slot("bd").param("pan") << -0.3;
drums.slot("sd").param("attack") << 10;
drums.slot("sd").param("release") << 200;
drums.slot("hh").param("pan") << Sine(0.5).range(-1, 1); // Auto-pan hi-hat
drums.slot("bd").param("gain") << Sine(2).range(0.6, 1.0); // Pulsing kick
drums.slot("bd").envelope(5, 50, 0.8, 100); // attack, decay, sustain, release

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)StringParamRefGet a parameter reference with .get(), .set(), .set_norm()
.param_get(path)StringNumberRead a parameter value
.param_set(path, value)String, NumberSamplerSet a parameter value
.param_set_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 << -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:

~/.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("CR-78")));
// 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.