Ship a Native Extension
A native extension is a Rust library that resonon loads and calls like a module. Use one to compute
something — a wavetable, a scale, a pattern — that is too slow or impossible in resonon. It runs on
the interpreter thread, not the audio thread: audio processing belongs in a
dsp effect or dsp instrument. See
Native Extensions: Safety & Threading
before you ship one.
You need the Rust toolchain; check it with cargo --version.
Scaffold the project
Section titled “Scaffold the project”resonon new my-ext --nativecd my-extmy-ext/ resonon.toml # manifest, with a [native] section src/ lib.non # the resonon-facing API native/ # the Rust crate Cargo.toml src/lib.rs bin/ # built libraries go hereExpose functions and classes
Section titled “Expose functions and classes”In native/src/lib.rs, mark each function with #[ext_fn], and list it in resonon_extension!:
use resonon_ext::prelude::*;
#[ext_fn(doc = "Doubles a number")]fn double(x: f64) -> f64 { x * 2.0}
resonon_extension! { name: "my_ext", functions: [double]}For an object that keeps state, add a class, a constructor and methods. Every constructor and
method goes into functions, and the class into classes:
use resonon_ext::prelude::*;
/// A step sequencer that cycles through values.#[ext_class]struct Stepper { values: Vec<f64>, index: usize,}
#[ext_new(doc = "Create a stepper from an array of values")]fn stepper_new(values: Vec<f64>) -> Stepper { Stepper { values, index: 0 }}
#[ext_method(Stepper, doc = "Get the next value in the sequence")]fn stepper_next(s: &mut Stepper) -> f64 { if s.values.is_empty() { return 0.0; } let val = s.values[s.index % s.values.len()]; s.index += 1; val}
resonon_extension! { name: "my_ext", functions: [stepper_new, stepper_next], classes: [Stepper]}The macros and the types they accept are in the Native Extensions reference.
Wrap them in lib.non
Section titled “Wrap them in lib.non”Users don’t call Rust directly. src/lib.non is an ordinary module whose functions call into the
extension with __ext(extension, function, ...args):
//! my-ext — custom pattern generators.
/// Doubles a number.fn double(x) { return __ext("my_ext", "double", x);}
/// Creates a stepper from values.fn Stepper(values) { return __ext("my_ext", "stepper_new", values);}
/// The stepper's next value.fn stepper_next(s) { return __ext("my_ext", "stepper_next", s);}Build and use it
Section titled “Build and use it”resonon buildThis builds the crate in release mode and copies the library into bin/. Import the package by
its name; the module is bound under that name with - changed to _:
use "my-ext";
print(my_ext.double(21)); // 42
let s = my_ext.Stepper(#[60, 62, 64, 67]);print(my_ext.stepper_next(s)); // 60print(my_ext.stepper_next(s)); // 62Reload it while you work
Section titled “Reload it while you work”After a change to the Rust code, build again in a terminal, then reload the extension in your session instead of restarting it:
reload_ext("my_ext");The reload fails while any object from the extension is still alive. Reassign or drop the
variables that hold them first — s above.
Publish prebuilt binaries
Section titled “Publish prebuilt binaries”Build a binary named for your platform, and commit it:
resonon build --distThis writes, for example, bin/my_ext-macos-aarch64.dylib. The scaffolded .gitignore already
ignores the local build and keeps these. Build and commit one per platform you support, then push
the repository.
When someone runs resonon pkg install gh:you/my-ext, resonon builds the extension from source.
If that build fails — on a machine without the Rust toolchain, say — the committed binary for their
platform is used instead.
Related
Section titled “Related”- Native Extensions — macros, types, and where resonon looks for the library
- Publish a Package — push a package and tag its versions
- Fix Install and Project Problems — a package that is not found or has nothing in it