A configurable musical instrument built from composable Operators that each do something simple and combine into complex musical behavior. Easy to start with via ready-made example rigs; deeply customizable once you get the hang of it. Rube Goldberg machines, for music — hence "reuben."
Music is the primary payload, but the same data (notes, chords, timing, gestures) can drive anything controllable over time — lights, video, game engines. OSC is the lingua franca, in and out.
This repo is the engine and its SDK:
reuben-core— the portable engine and its embed surface: construct from a document, push OSC in, pull audio out. No OS dependency; compiles towasm32-unknown-unknownuntouched. You reach it throughreuben-api, which is the only crate that depends on it.reuben-api— the one window between the engine and every consumer, in two feature halves:authoring(document work and reaching a running engine, off-thread) andrender(theRenderSide/RenderSlotpair a host drives per block), so a host compiles only the one it drives. The render half isno_std, cross-built for bare metal, and carriesinstall_graphfor a host that builds its graph in Rust. It owns the tool roster, the sentence each verb is advertised by, both ends of the structure channel, and the resource seam a host fills. This is what you link against to put reuben inside something else. Ships a default-off filesystem resolver to share rather than reimplement.reuben-native— thereubenCLI and its audio/OSC/filesystem host, built on the window like any other consumer.reuben-mcp— a stdio MCP sidecar, so an agent can author instruments against a live engine.instruments/+surfaces/— the instrument library and the presentation docs over their interface pipes..claude/skills/— the authoring and developer agent skills that run inside Claude Code, each grounded on the live engine so it can't drift from the code. Authoring:patcher(build/edit an instrument) andcontrol-surface(author a surface doc and project it to TouchOSC). Development:create-operator(a new DSP operator in Rust),sync-docs(bring the living docs back in sync after a change), andrust-hot-path-review(RT-safety/perf review of an audio-thread diff). The Make your own section below walks through them.
- Rust (stable) — install via rustup.
- Linux: ALSA development headers for audio output:
(Windows needs nothing extra — audio goes through WASAPI.)
sudo apt-get install libasound2-dev # Debian/Ubuntu - Optional: an OSC sender to play notes — TouchOSC, Max/Pd, or any script that can send a UDP OSC message. Some example rigs play on their own and need no sender.
Building from source (below) is the primary path. Each tagged release also ships a prebuilt
reuben binary for Linux and Windows on the
Releases page:
- Download the archive for your platform (
…-x86_64-unknown-linux-gnu.tar.gz/…-x86_64-pc-windows-msvc.zip) and extract it. Each archive bundles thereubenbinary plusLICENSEandREADME.md; a matching.sha256sidecar lets you verify the download (sha256sum -c <file>.sha256on Linux,Get-FileHash <file>on Windows). - It's a headless CLI — run it from a terminal (no installer):
./reuben play(Linux) orreuben.exe play(Windows). All the subcommands below apply.
Run the default synth — opens your default audio device and listens for OSC on UDP
0.0.0.0:9000:
cargo reuben-playcargo reuben-play is a workspace alias (defined in .cargo/config.toml)
for cargo run -p reuben-native --bin reuben -- play; anything you add after it is passed to
play. The reuben binary is subcommand-driven: play (live audio), describe (list
operators, or an instrument's structure with --view), validate (load-check an instrument),
scaffold-operator (new-operator skeleton), new-instrument (write a minimal valid instrument to
build on).
Add --help to any of them. The other subcommands have no alias — run them via
cargo run -p reuben-native --bin reuben -- <subcommand> (everything after -- is passed to
the binary).
play opens the default device by default; play --io-map <file> loads a device profile
(docs/device-profile.md) to bind logical channels to a specific
device's channels, pick a non-default device by name, and request sample-rate/buffer-size
preferences.
Play a note by sending OSC /voicer/notes [midi, gate] from any OSC source:
[69.0, 1.0]— note-on, A4[69.0, 0.0]— note-off
Send several /voicer/notes messages to play a chord.
Instruments are data — JSON files in instruments/. Load one by
passing its path to play:
cargo reuben-play instruments/<name>.json| Rig | Plays on its own? | What it is |
|---|---|---|
default |
needs OSC notes | Polyphonic synth (8 voices): voicer → osc → filter → ADSR → out. What play loads when you give it no file. |
groovebox |
yes | The Groovebox Toy: a free-running 16-step drum machine — kick/snare/hat synthesized from operators (no samples), each a sequencer driving its own voicer voice on a shared clock. Toggle steps via /kick_step1/in../kick_step16/in (also snare_*, hat_*), ride /tempo/in; per-drum volumes (/kick_vol/in…), a master DJ-filter sweep (/tone/in), and a main volume knob (/volume/in, default −6 dB) are Good Buttons, with a warm saturator gluing the mix ahead of the filter and a /drive/in knob to ride the squash. |
chord-player |
needs OSC | The Chord player Toy: tap-and-hold diatonic triad buttons (I–vii°) at /chord/in [degree, gate]. The chord op stacks scale thirds and the voicer resolves them through the tonal context, so held chords re-spell live when you change key (/key/in). A 12-voice pad; /brightness/in tones the mix. |
strum-harp |
needs OSC | The Strum harp Toy: drag-to-strum. Stream /strum/in [0..1] and the strum op plucks a note each time the bar crosses a string boundary. Strings are scale degrees through the tonal context, so it stays in key. /octaves/in sets the span; /key/in the key. |
euclidean-drums |
yes | A self-playing 4-channel Euclidean rhythm machine — kick/snare/tom/hat synthesized from operators, each driven by a euclid generator on a shared 16th-note clock. Reshape patterns via /<chan>_pulses/in, /<chan>_steps/in, /<chan>_rotation/in; per-channel DJ-filter, level, and decay knobs; /tempo/in. |
mic-space |
needs a mic | Live-input demo: a top-level input pipe bound to logical input channel 0 feeds the nested space patch (instruments/patches/space.json) — speak/play into your default input device and hear it through the tone+reverb, broadcast to stereo out. Fails fast if no input device exists; pick a device / remap channels with play --io-map. Tweak /space/tone/in (Hz), /space/space/in (mix). |
The rows marked yes make sound immediately — good for a first run with no OSC sender. Every node's inputs are live over OSC at its address.
(The one-feature example rigs that used to fill this table — echo, reverb, vibrato, metronome,
sampler, and friends — were culled from the library; the ones tests and benches still exercise
live on as frozen fixtures under crates/*/tests/fixtures/ and
crates/reuben-document/benches/fixtures/.)
To play an instrument from a phone/tablet, project its surface doc (surfaces/<name>.json
— the presentation layer over its interface pipes) to a
TouchOSC layout with the control-surface skill. A surface doc is a portable presentation
contract, not a TouchOSC file: any host can render one.
(The v1.4-era walkthrough, docs/v1.4-control-surface-testing.md,
predates surface docs and is kept as history.)
Render a tone straight to a WAV file:
cargo run -p reuben-core --example first_sound # writes first_sound.wavOnce an example sounds good, the next step is your own. Instruments are just JSON graphs of operators, so you can author them by hand — but reuben ships agent skills that do the introspect-draft-validate loop for you. They run inside Claude Code: open this repo in Claude Code and ask in plain language; the matching skill triggers on its own. Each skill is grounded on the live engine (it reads the real operator set and validates against the real load path), so it can't drift from the code.
| Want to… | Ask Claude Code (skill) | Or do it by hand |
|---|---|---|
| Build / edit an instrument | "build a plucky bass" → patcher |
Edit JSON in instruments/, then validate it |
| Make / edit a control surface | "make a control surface for this" → control-surface |
Author surfaces/<name>.json, then gen_surface.py emit |
| Add a new DSP operator (Rust) | "add a wavefolder operator" → create-operator |
scaffold-operator, then implement process |
| Sync the docs after a change | "sync the docs" → sync-docs |
Edit ARCHITECTURE/README by hand |
A typical first session, by hand or by skill:
- See what's available. Every operator self-describes its ports and params:
This is the same introspection the
cargo run -p reuben-native --bin reuben -- describe # list all operators cargo run -p reuben-native --bin reuben -- describe filter # one operator's ports/params
patcherskill reads. - Patch. Copy an instrument in
instruments/, rewire nodeinputs(a literal or a wire-ref{"from":"/node.port"}), or ask thepatcherskill for a sound. - Validate before you play — load + plan with no audio, surfacing errors/warnings:
cargo run -p reuben-native --bin reuben -- validate instruments/my-rig.json
- Play it with
cargo reuben-play instruments/my-rig.json(above). - Play it on a tablet. Player-facing controls are the instrument's
interfaceinput pipes; presentation lives in a surface doc (surfaces/<name>.json) — or is auto-derived from the pipes when no doc exists. Thecontrol-surfaceskill authors the doc and projects it to a TouchOSC layout (.toscfiles land incontrol-surfaces/); other hosts render the same doc directly, with no emit step.surfaces/groovebox.jsonandsurfaces/euclidean-drums.jsonare worked examples.
Need behavior no operator provides? That's a new Operator in Rust — scaffold-operator
(or the create-operator skill) generates the skeleton and wires its registration; see
docs/agents/operator-dev.md for the operator contract.
- docs/agents/authoring.md — authoring Instruments and Rigs (the guide for agents and contributors).
- docs/agents/operator-dev.md — building new Operators in Rust.
BSD 3-Clause. See LICENSE.