Skip to content

Package Management

A Resonon package is a directory — usually a git repository — that bundles reusable code (a src/lib.non file) and/or sample kits (a kits/ directory). There’s no registry and no publish step: packages live on GitHub and install with a gh:user/repo shorthand that maps straight to a clone URL.

This guide covers scaffolding projects, the resonon.toml manifest, installing and publishing packages, and the kit.toml sample-kit format. For the runtime CLI (running files, the REPL, the server), see the CLI Reference.

resonon init <name> scaffolds a project in the current directory; resonon new <name> creates a new directory first. Both accept flags that choose the project type:

FlagProject type
(none)Plain project with src/main.non.
--libLibrary with src/lib.non (importable code).
--kitSample-kit project with a kits/ directory.
--nativeNative Rust extension (implies --lib).

Flags combine:

Terminal window
resonon new my-pkg --lib --kit # library plus sample kits
resonon new my-ext --lib --native # library backed by a native extension

Every scaffold writes a resonon.toml manifest, a README.md, an MIT LICENSE, a .gitignore, and initializes a git repository (unless you’re already inside one). A plain project looks like this:

my-project/
resonon.toml
src/
main.non # entry point — run with `resonon run`
README.md
LICENSE
.gitignore

resonon run executes src/main.non from the project root. The generated .gitignore excludes resonon.lock and dependencies/.

The scaffolded src/main.non sets project metadata with builtins that double as your project’s configuration:

project_title("My Project");
project_artist("Your Name");
project_bpm(120);
BuiltinDescription
project_title(string)Set the project title.
project_artist(string)Set the artist name.
project_bpm(bpm, beats_per_cycle?)Set the project tempo in BPM. beats_per_cycle defaults to 4.

project_bpm() establishes the tempo at startup. To change tempo live during a session, use setbpm(bpm, beats_per_cycle?) instead — it takes effect immediately and is ignored while Resonon is following an external MIDI clock. See Clock Sync.

resonon.toml declares package metadata and dependencies. It’s created automatically by resonon init / resonon new:

[package]
name = "my-project"
version = "0.1.0"
authors = ["Your Name <email>"]
resonon = "0.8.0"
[dependencies]
drums = { source = "gh:resonon/drum-kits", version = "v1.0" }
synths = { source = "gh:user/synths" }
FieldRequiredDescription
nameYesPackage name.
versionNoPackage version.
authorsNoList of authors.
resononNoResonon version the project targets.

Each entry maps a package name to its source. The key is the local package name (the directory it installs into).

FieldRequiredDescription
sourceYesPackage source (gh:user/repo).
versionNoGit tag to pin to (e.g. "v1.0").

Present only for packages with a native Rust extension. See Native Extensions.

FieldRequiredDescription
crateYesPath to the native Rust crate (relative to the project root).

The pkg subcommands have top-level aliases for the common cases:

Full commandAlias
resonon pkg install <source>resonon install <source>
resonon pkg update [name]resonon update [name]
resonon install (no args — install all from resonon.toml)
resonon remove <name>

Install from GitHub with the gh:user/repo shorthand, optionally pinning a tag with @:

Terminal window
resonon install gh:resonon/drum-kits
resonon install gh:resonon/drum-kits@v1.0

Where packages install: inside a project (where resonon.toml exists) packages go to dependencies/ relative to the project root and are recorded in resonon.toml and resonon.lock. Outside a project they install to the global library path, ~/.resonon/lib/. Resonon searches both locations (and the current directory) when resolving use imports and Sampler() paths.

After install, Resonon reports the package type and any kits:

Installed 'drum-kits' [kits] to ~/.resonon/lib/drum-kits
Kits:
808 — Sampler(Kit("drum-kits/808"))
vinyl — Sampler(Kit("drum-kits/vinyl"))

Run resonon install with no arguments to install every dependency listed in resonon.toml.

resonon pkg list shows installed packages and their types:

Installed packages (dependencies/):
drum-kits [kits]
kit: 808 — Sampler(Kit("drum-kits/808"))
kit: vinyl — Sampler(Kit("drum-kits/vinyl"))
my-lib [code]
studio-tools [code+kits]
kit: piano — Sampler(Kit("studio-tools/piano"))
TypeMeaning
[code]Has src/lib.non (importable code).
[kits]Has a kits/ directory.
[code+kits]Has both.

resonon pkg inspect <name> prints a package’s documentation, code exports with signatures, and kit details:

Package: my-lib [code+kits]
A collection of utility functions and drum kits.
Exports:
fn scaled(value, factor)
let default_bpm = <number>
Kits:
lo-fi — oneshot mode, 12 samples
Usage: Sampler(Kit("my-lib/lo-fi"))

resonon update [name] re-clones dependencies at their pinned tag (or the latest commit when no version is set), rebuilds any native extensions, and regenerates resonon.lock:

Terminal window
resonon update # update all dependencies
resonon update drum-kits # update one

resonon remove <name> (project only) drops a dependency from resonon.toml and resonon.lock and deletes its installed directory:

Terminal window
resonon remove drum-kits

A package needs at least one of: a src/lib.non (code) or a kits/ directory (samples). Both can coexist in one repository.

Write src/lib.non with the functions and values you want to share. All top-level fn, let, and class definitions are exported by default; mark items private to keep them internal. /// doc comments appear in resonon pkg inspect, and the first doc-comment block becomes the package description.

/// my-lib — utility helpers for generative rhythms.
/// The first doc-comment block becomes the package description.
private let lib_name = "my-lib";
/// Scale a value and round to the nearest integer.
fn scaled(value, factor) {
round(value * factor)
}
/// Default tempo for performance templates.
let default_bpm = 120;
/// Print a greeting. Internal — not exported.
private fn greet() {
PRINT f"Hello from {lib_name}!";
}

Organize WAV files under kits/<kit-name>/. A kit is one subdirectory of kits/:

my-drums/
kits/
808/
bd.wav
sd.wav
hh.wav

If you name files after GM drum abbreviations (bd, sd, hh, …), Resonon auto-detects notes and you can skip kit.toml entirely. For explicit control, add a kit.toml (see Kit Configuration).

Import a package’s code with use "<name>" — the bare package name resolves to its src/lib.non:

use "studio-tools";
let pattern = studio-tools.swing(42, 0.6);

Load its kits with Sampler(Kit("<name>/<kit>")):

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

See Modules for the full import system and Samplers for sampler details.

A kit.toml controls how a kit’s samples are mapped and played. Every section is optional — with no kit.toml, Resonon auto-detects WAV files in the directory.

FieldTypeDefaultDescription
nameStringDirectory nameDisplay name for the kit.
authorStringKit author.
modeString"oneshot"Playback mode: "oneshot" or "melodic".

Oneshot plays each sample at a fixed pitch — standard for drums. Melodic repitches samples relative to each sample’s root note — use it for tonal instruments.

Map sample names to notes in one place instead of repeating note in every entry. Values are note names ("C2", "F#3") or MIDI numbers (36, 42):

[defaults]
bd = "C2"
sd = "D2"
hh = "F#2"

Each key defines a sample:

FieldTypeDefaultDescription
filesArray of stringsWAV paths relative to the kit directory. Multiple files enable round-robin.
noteString or numberAuto-assignedMIDI note that triggers this sample ("C2" or 36).
rootString or numberRoot pitch for melodic mode ("C4" or 60).
choke_groupNumberSamples sharing a group cut each other off.
velocityNumber1.0Default velocity (0.01.0).
loop_startNumberLoop start, normalized (0.01.0).
loop_endNumberLoop end, normalized (0.01.0).

A drum kit with a choke group (the open and closed hats cut each other):

[kit]
name = "Lo-Fi Drums"
[samples]
bd = { files = ["kick.wav"] }
sd = { files = ["snare.wav"], note = "D2" }
hh = { files = ["hihat_closed.wav"], note = "F#2", choke_group = 1 }
hh_open = { files = ["hihat_open.wav"], note = "A#2", choke_group = 1 }

Multiple files per sample give automatic round-robin variation:

[samples]
sd = { files = ["snare_1.wav", "snare_2.wav", "snare_3.wav"], note = "D2" }

A melodic kit repitches relative to each root; use loop points for sustaining sounds:

[kit]
name = "Tape Piano"
mode = "melodic"
[samples]
piano = { files = ["piano_c4.wav"], root = "C4" }
pad = { files = ["pad_c3.wav"], root = "C3", loop_start = 0.1, loop_end = 0.95 }

When a sample has no explicit note, Resonon resolves it in order:

  1. Explicit note in the [samples] entry.
  2. A [defaults] mapping.
  3. The GM drum mapping below (by sample name).
  4. Sequential assignment from C2 (MIDI 36).
NameAliasesNote
bdkick, bassC2 (36)
sdsnareD2 (38)
rsrim, rimshotC#2 (37)
hhhihat, hat, chF#2 (42)
hh_openhihat_open, ohA#2 (46)
cpclapD#2 (39)
lttom, tom_lowA2 (45)
mttom_midB2 (47)
httom_high, tom_hiD3 (50)
crashcyC#3 (49)
rideD#3 (51)
cbcowbellG#3 (56)
mamaracasA#4 (70)
clclaveD#5 (75)
lcconga_lowE4 (64)
mcconga_midD#4 (63)
hcconga_highD4 (62)

With no kit.toml, each .wav becomes a sample named after its filename, notes come from the table above (or sequentially from C2), and the mode defaults to oneshot — so a minimal kit is just a folder of WAV files.

A --native project adds a Rust crate under native/, a built-dylib directory lib/, and a [native] section in resonon.toml. Build it with:

Terminal window
resonon build # build for the current platform
resonon build --dist # platform-named output for binary distribution

Writing native extensions — the resonon-ext crate, the #[ext_fn] macro, and calling into Rust from .non — is covered in Going Native.

Packages publish by pushing to GitHub — gh:user/repo maps directly to https://github.com/user/repo.git. There’s no registry or publish command:

Terminal window
cd my-package
git init
git add .
git commit -m "Initial commit"
gh repo create my-package --public --source=. --push

Others then install it:

Terminal window
resonon install gh:yourname/my-package

Versioning. Tag releases with git tags. Consumers pin a tag in resonon.toml:

[dependencies]
my-package = { source = "gh:yourname/my-package", version = "v1.0" }

…or at install time with resonon install gh:yourname/my-package@v1.0. Without a pin, install clones the latest commit on the default branch, and resonon.lock records the exact commit so the install is reproducible.

“Module not found” on use — Check the package is installed (resonon pkg list) and that you’re using the repository name, not the [kit].name from kit.toml. The bare name (use "drum-kits") resolves to the package’s src/lib.non.

“no lib.non or kits/ found” after install — The repository doesn’t follow the package layout. Ensure code is at src/lib.non or kit WAVs are under kits/<kit-name>/.

Samples not loading — Confirm the kit directory holds .wav files (not .mp3/.flac) and that the path has both parts: Sampler(Kit("package-name/kit-name")).