tono-core 1.9.0

The pure, headless audio engine behind tono: synthesis-graph DSL, DSP, deterministic renderer, instruments, songs, and analysis — no I/O, no transport.
Documentation

Hear it

▶ The showcase site — recognizable classics rebuilt from scratch (retro-coin, the Nokia tune, THX-style deep note, a complete piano piece, a full band demo), plus game-ready BGM loops and ambient beds. Every one a deterministic render; no samples anywhere.

Why tono

  • Sounds are data. A sound is a JSON synthesis graph; rendering it is a pure function → byte-identical audio, every run. Test it, diff it, cache it, ship no asset files.
  • Zero-asset SFX. A patch renders infinite variations from gameplay parameters — impacts that scale with collision force, footsteps that vary by surface. No sample library.
  • A real game runtime. Live DSP buses, polyphony caps with priority stealing, and adaptive music that reacts on the beat — section switches, intensity stems, stingers on the downbeat.
  • An ear built in. Every render returns a spectrogram, a waveform, and LUFS/spectral stats — "does it sound right?" becomes numbers and pictures.

Quick start

cargo install tono       # the CLI
cargo add tono-core      # …or the engine as a library
cat > blip.json <<'EOF'
{ "name": "blip", "duration": 0.3, "engine": 4,
  "root": { "type": "mul", "inputs": [
    { "type": "sine", "freq": 880 },
    { "type": "env", "a": 0.002, "d": 0.08, "s": 0.0, "r": 0.05 } ] } }
EOF

tono render blip.json -o out/
#   out/blip.wav         out/blip.png (spectrogram)
#   out/blip_wave.png    out/blip.stats.json (peak/RMS/LUFS/spectral)

That loop — write a doc, render, read the images and stats, refine — is all a human or an agent needs to author sound by inspection. The cookbook has the full node vocabulary and recipes.

Where next? Pick your path:

  • New here? docs/quickstart.md — the guided first ten minutes (hear a sound, change it on purpose).
  • Make sounds — the cookbook, then tono diff, tono match REF.wav DOC.json, and tono render --watch for the loop.
  • Embed in a gamedocs/runtime.md (Engine/Mixer runtime, parametric patches).
  • Pythoncrates/tono-py.
  • No code — the desktop pattern station (build it).

All guides: docs/README.md.

Recipes — the lazy answers

Copy-paste, runnable, no tour. (Each Rust one is also a compile-checked example in crates/tono-play/examples.)

Play notes live — a drum kit and a piano in one mix, driven from your code:

use tono_core::{drumkit::DrumKit, instrument::{Instrument, Note}, presets::preset, runtime::Mixer};
use tono_play::{Speaker, device_sample_rate};

let sr = device_sample_rate()?;
let mut mixer = Mixer::new(sr);
let drums = mixer.add(DrumKit::general_midi(sr));
let piano = mixer.add(Instrument::new(preset("fm_tine").unwrap(), sr)?);
let speaker = Speaker::open(mixer)?;

speaker.control(|m| m.get_mut::<DrumKit>(drums).unwrap().note_on(Note(36), 1.0));
speaker.control(|m| m.get_mut::<Instrument>(piano).unwrap().note_on(Note::C4, 0.8));
speaker.control(|m| m.get_mut::<Instrument>(piano).unwrap().note_off(Note::C4));

Write a song — the fluent builder, catalog instruments, one timeline:

use tono_core::{catalog::{GrandPiano, Bass, Drums}, song::Song};

let song = Song::new("demo", 120.0)
    .add(GrandPiano::grand(), |t| { t.at(0.0).chord(&["C4","E4","G4"], 4.0); })
    .add(Bass::finger(), |t| { t.play("C2", 2.0).play("G1", 2.0); })
    .add(Drums::acoustic(), |t| { t.kick().rest(1.0).snare().rest(1.0); });
let doc = song.to_doc()?;          // an ordinary deterministic SoundDoc

Zero-asset SFX — one patch, endless variations from gameplay parameters:

use std::collections::BTreeMap;
use tono_core::patch::Patch;

let patch: Patch = serde_json::from_str(include_str!("impact.patch.json"))?;
let hit = patch.render(&BTreeMap::from([
    ("hardness".into(), force), ("size".into(), object_size),
]))?;                              // mono samples, byte-identical per input

Adaptive game music — stems and section swaps on the beat:

use tono_core::adaptive::{AdaptiveMusic, Quantize};

let mut music = AdaptiveMusic::new(48_000);
music.set_tempo(120.0, 4);
music.add_section("explore", &explore_doc);
let battle = music.add_section("battle", &battle_doc);

music.transition_to(battle, Quantize::Bar);  // combat! — swaps on the next bar
music.set_intensity(0.9);                    // stems swell with the action
music.stinger_at(&boss_hit, Quantize::Bar);  // lands on the downbeat

Python — the same engine, one import:

import tono

engine = tono.Engine(48000)                  # owns the stream + render thread
engine.drumkit().note_on(36, 1.0)            # kick
engine.load_patch(impact_json).trigger(hardness=0.8, size=0.3)  # zero WAVs

The Rust crates install from crates.io; the Python extension builds from source. More: embedding & patches · API docs.

One engine, five faces

Every face renders the same SoundDoc byte-identically:

  • CLIcargo install tono — render to audio + spectrogram + stats.
  • Rust librarycargo add tono-core — the engine embedded in a game or tool.
  • Python bindings — live engine + deterministic numpy renders; build from source.
  • Pattern station — a Tauri studio: a step grid over catalog instruments, live audio, undo — build.
  • Playground — hear Rust snippets through the speakers — examples.

The last two are developer faces that live in this repo — the architecture guide covers them and the rest of the codebase.

A personal note

Every line of code in tono was written by AI — my part was direction, and holding the project to the standards I use where I still write the code myself. If tono helps you as a tool, a reference, or a kick-start, that makes me genuinely happy: the tokens are already spent; the least they can do is be useful to you too.

⚠️ The 1.x series is AI-generated and not fully human-reviewed. Breaking changes may land in minor releases despite my best intentions — every removal is called out in the CHANGELOG. Use at your own risk.

License

MIT — permissive, no warranty.