trem-cpal 0.1.0

Real-time audio backend for trem using cpal
Documentation
  • Coverage
  • 70.83%
    34 out of 48 items documented0 out of 11 items with examples
  • Size
  • Source code size: 60.21 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 3.4 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 1m 6s Average build duration of successful builds.
  • all releases: 1m 6s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • alkemdev/trem
    1 1 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • cadebrown

trem

A mathematical music engine in Rust.

trem logo showing TERM rearranged to TREM

trem is a library-first DAW built on exact arithmetic, xenharmonic pitch systems, recursive temporal trees, and typed audio graphs. The terminal UI is a first-class citizen. The name is an anagram of term and also a nod to tremolo: repeating, pulsing motion in sound.

Try it (install & run)

  1. Install Rust (stable toolchain).
  2. Clone this repo and cd into it.
  3. Run:
cargo run

Platform setup (Linux ALSA packages, Windows MSVC, macOS, WSL caveats): see docs/install.md.

Principles

  • Exact where possible. Time is rational (integer numerator/denominator pairs). Pitch degree is an integer index into an arbitrary scale. Floating-point only appears at the DSP boundary.
  • Few assumptions. No 12-TET default, no 4/4 default, no fixed grid resolution. Tuning, meter, and subdivision are all parameters.
  • Composition is a tree. Patterns are recursive Tree<Event> structures. Children of Seq subdivide the parent's time span evenly. Children of Par overlap. Triplets, quintuplets, nested polyrhythms — just tree shapes.
  • Sound is a graph. Audio processing is a DAG of typed processor nodes. Each processor declares its own inputs, outputs, and parameters. Graphs nest recursively — a Graph is itself a Processor, so complex instruments and buses are single composable nodes.
  • Library first. The core trem crate has zero I/O dependencies. It compiles to WASM. It renders offline to sample buffers. The TUI and audio driver are separate crates that depend on it.

Architecture

┌─────────────────────────────────────────────────────────┐
│  trem (core library, no I/O)                            │
│                                                         │
│  math::Rational ──▶ pitch::Scale ──▶ event::NoteEvent   │
│       │                                     │           │
│       ▼                                     ▼           │
│  time::Span ──▶ tree::Tree ──▶ render ──▶ TimedEvent    │
│                                             │           │
│  grid::Grid ──────────────────────┘         │           │
│                                             ▼           │
│  graph::Graph ◀── dsp::* ◀── euclidean    process()     │
│       │           registry                              │
│       ▼                                                 │
│  output_buffer() ──▶ [f32]                              │
└────────────┬────────────────────────────────────────────┘
             │
     ┌───────┴───────┐
     ▼               ▼
┌─────────┐   ┌───────────┐
│trem-cpal│   │  trem-tui │
│         │   │           │
│ cpal    │◀──│ ratatui   │
│ stream  │cmd│ crossterm │
│         │   │           │
└─────────┘   └───────────┘

trem — Core library. Rational arithmetic, pitch/scale systems, temporal trees, audio processing graphs, DSP primitives (oscillators, envelopes, filters, dynamics, effects), Euclidean rhythm generation, grid sequencer, processor registry, and offline rendering. No runtime dependencies beyond bitflags and num-rational.

trem-cpal — Real-time audio backend. Drives a Graph from a cpal output stream. Communicates with the UI via a lock-free ring buffer (rtrb): the UI sends Commands (play, pause, stop, set parameter), the audio thread sends back Notifications (beat position, meter levels).

trem-tui — Terminal interface. Pattern sequencer with per-step note entry, audio graph viewer with inline parameter editing, transport bar, spectrum-first bottom pane (in Graph view: side-by-side instrument bus vs master previews), waveform scope, a sidebar (cursor / project / keys / contextual hints, with PROC stats for this process — CPU % and RSS — at the bottom), and contextual key hints. Built on ratatui + crossterm.

Quick start

cargo run

Same as Try it above; full prerequisites on docs/install.md.

The default graph and pattern live in src/demo/ (levels.rs for gains/FX, graph.rs for routing, pattern.rs for the grid). src/main.rs is thin I/O glue.

This launches the demo project: a ~146 BPM loop (32-step pattern) with a dense pentatonic arp on the lead (triangle-heavy dual osc, light wavetable, warm filter), short delay only on the lead for a fluttery echo, bass, a louder snare through dst distortion (foldback / crisp transient), and hats — routed through a nested bus architecture:

Lead > ────────┐
                ├── Inst Bus > ──┐
Bass > ────────┘                 │
                                  ├── Main Bus > ── [output]
Kick > ────┐                     │
Snare >(dst)──┼── Drum Bus > ──────┘
Hat > ─────┘

Every node marked > is a nested graph you can Enter to inspect and edit.

Press Space to play/pause. Press Tab to switch views. The bottom strip defaults to the spectrum. Bins use per-bin peak decay ((\tau \approx 18) ms, App::spectrum_fall_ms) and adaptive level: a decaying global peak + silence-aware reference so quiet buffers don’t normalize to full height; each column uses the max of its FFT bins. In Graph view the strip splits into IN and OUT for whichever node is highlighted — summed inputs vs that node’s outputs (including inside nested graphs). In Pattern view the spectrum shows the master output (waveform/spectrum use the same scope buffer). Press ` to toggle waveform vs spectrum.

The sidebar PROC section (bottom of the info column) reports this process only (trem CPU % and RSS), not whole-machine totals. The transport bar shows beat position with a φ-weighted phase glyph for a slightly less grid-locked readout.

Keybindings

Global (all views)

Key Action
Space Play / pause
Tab Cycle SEQGRAPH
? Full keymap overlay
+ / - BPM up / down
[ / ] Octave down / up
` Toggle bottom: waveform ↔ spectrum
Ctrl-S / Ctrl-O Save / load project (project.trem.json in cwd)
Ctrl-C / Ctrl-Q Quit

Pattern view — Navigate mode

Key Action
Move step cursor
Move voice cursor
h l k j Vim-style move
e Enter edit mode

Pattern view — Edit mode

Key Action
zm Enter note (chromatic keyboard layout)
09 Enter note by degree
Del / BS Delete note
w / q Velocity up / down
f Euclidean fill (cycle hit count)
r Randomize voice
t Reverse voice
, / . Shift voice left / right
Esc Back to navigate

Graph view — Navigate mode

Key Action
Follow connections
Move within layer
Enter Dive into nested graph
Esc Back up one level (nested graph only)
e Enter edit mode

Graph view — Edit mode

Key Action
Select parameter
Adjust value
+ / - Fine adjust
Esc Back to navigate

DSP library

All processors implement the Processor trait and declare their parameters via ParamDescriptor, enabling automatic UI generation for any frontend.

Sources

Tag Processor Description
osc Oscillator PolyBLEP oscillator (sine, saw, square, triangle)
noi Noise White noise (deterministic LCG)
wav Wavetable Wavetable oscillator with shape crossfade
kick KickSynth Sine with pitch sweep + amplitude envelope
snr SnareSynth Sine body + bandpass-filtered noise burst
hat HatSynth Highpass-filtered noise with short envelope
syn analog_voice Composite synth graph (2 osc, filter, env, gain)
ldv lead_voice Lead stack: saw + tri, wavetable air, modulated LP, ADSR

Effects & EQ

Tag Processor Description
dly StereoDelay Stereo delay with feedback and dry/wet mix
dst Distortion Mono waveshaper: tanh / hard / fold / soft / diode + mix
vrb PlateReverb Schroeder plate reverb (4 combs + 2 allpasses)
peq ParametricEq 3-band stereo parametric EQ
geq GraphicEq 7-band mono graphic EQ

Dynamics

Tag Processor Description
lim Limiter Stereo brickwall limiter
com Compressor Stereo downward compressor

Filters & Modulators

Tag Processor Description
lpf BiquadFilter Low-pass biquad (2nd-order IIR)
hpf BiquadFilter High-pass biquad
bpf BiquadFilter Band-pass biquad
env Adsr Attack-decay-sustain-release envelope
lfo Lfo Low-frequency oscillator (sine, tri, saw, square)

Mixing & Utility

Tag Processor Description
vol StereoGain Stereo pass-through gain
gain MonoGain Simple mono gain
pan StereoPan Stereo panning (equal-power)
mix StereoMixer N-input stereo summing bus
xfade MonoCrossfade Mono crossfade between two inputs

Processor registry

The Registry maps short tags to factory functions, so processors can be instantiated at runtime without compile-time coupling:

use trem::registry::Registry;

let reg = Registry::standard();
let delay = reg.create("dly").unwrap();
println!("{}: {} in, {} out", delay.info().name, delay.info().sig.inputs, delay.info().sig.outputs);

Nested graphs

A Graph implements Processor, so any graph can be a node inside another graph. The demo project uses this to build self-contained instrument channels (synth + level/pan in one node) and mix buses (mixer + dynamics + gain):

use trem::graph::{Graph, Processor, ParamGroup, GroupHint};
use trem::dsp;

let mut ch = Graph::labeled(512, "lead");
let osc = ch.add_node(Box::new(dsp::Oscillator::new(dsp::Waveform::Saw)));
let gain = ch.add_node(Box::new(dsp::Gain::new(0.5)));
ch.connect(osc, 0, gain, 0);
ch.set_output(gain, 2);

// Expose internal params to the parent graph
let g = ch.add_group(ParamGroup { id: 0, name: "Channel", hint: GroupHint::Level });
ch.expose_param_in_group(gain, 0, "Level", g);

// Now `ch` acts as a single stereo-output Processor
assert_eq!(ch.info().sig.outputs, 2);

In the TUI, press Enter on any nested graph node to dive in and edit its internal parameters. Press Esc to return to the parent level. A breadcrumb trail shows your current position (e.g. Graph > Lead > Oscillator).

Examples

Runnable examples live in crates/trem/examples/:

cargo run -p trem --example offline_render   # render a pattern to samples
cargo run -p trem --example euclidean_rhythm  # generate and print euclidean patterns
cargo run -p trem --example xenharmonic       # explore tuning systems
cargo run -p trem --example custom_processor  # implement your own Processor

Building the library only

cargo build -p trem

Running tests

cargo test --workspace

Benchmarks

cargo bench -p trem          # core, DSP, and graph benchmarks
cargo bench -p trem-tui      # spectrum analysis benchmarks

Using as a library

use trem::dsp::{Oscillator, Adsr, Gain, Waveform};
use trem::graph::Graph;
use trem::pitch::Tuning;
use trem::event::NoteEvent;
use trem::math::Rational;

// Build a simple synth graph
let mut graph = Graph::new(512);
let osc = graph.add_node(Box::new(Oscillator::new(Waveform::Saw)));
let env = graph.add_node(Box::new(Adsr::new(0.01, 0.1, 0.3, 0.2)));
let gain = graph.add_node(Box::new(Gain::new(0.5)));
graph.connect(osc, 0, env, 0);
graph.connect(env, 0, gain, 0);

// Render offline
let scale = Tuning::edo12().to_scale();
let tree = trem::tree::Tree::seq(vec![
    trem::tree::Tree::leaf(NoteEvent::simple(0)),
    trem::tree::Tree::rest(),
    trem::tree::Tree::leaf(NoteEvent::simple(4)),
    trem::tree::Tree::rest(),
]);
let audio = trem::render::render_pattern(
    &tree, Rational::integer(4), 120.0, 44100.0,
    &scale, 440.0, &mut graph, gain,
);
// audio[0] = left channel, audio[1] = right channel

Contributing

See CONTRIBUTING.md and AGENTS.md.

License

MIT