Getting Started
Add rill-adrift to your Cargo.toml:
[dependencies]
rill-adrift = "0.6.0-M2"
For individual crates (if you don't need the full ecosystem):
[dependencies]
rill-core-dsp = "0.6.0-M2"
Example: Signal graph with sine oscillator
This example builds a signal graph with a sine oscillator using the
GraphBuilder API and the built-in registry:
use rill_graph::GraphBuilder; use rill_core::builtin::Registry; const BUF_SIZE: usize = 256; fn main() -> Result<(), Box<dyn std::error::Error>> { let mut reg = Registry::<f32>::new(); rill_core_dsp::lang::register::register_lang_builtins(&mut reg); rill_lang::register::register_core_builtins(&mut reg); let mut builder = GraphBuilder::<f32, BUF_SIZE>::new(); let osc = builder.add_node("rill/sinosc", &[("freq", 440.0)].into()); let out = builder.add_node("rill/output", &[].into()); builder.connect_signal(osc, 0, out, 0); // build_ir() produces a GraphIr, then internally calls // rill_lang::graph_compiler::compile() to produce a CompiledGraphEngine: let engine = builder.build_ir(®, 44100.0)?; let mut buf = [0.0f32; BUF_SIZE]; engine.process(Some(&[0.0f32; BUF_SIZE]), &mut buf)?; Ok(()) }
Note: For real I/O, use
Output/Inputfromrill-io(feature-gated behindio). The orchestration layer creates the backend and drives theCompiledGraphEngineviaprocess().
Using rill-lang instead of programmatic graphs
rill-lang provides a Faust-style functional DSL for signal processing. Use it to define algorithms declaratively instead of wiring Rust node types:
#![allow(unused)] fn main() { use rill_lang::{compile, compile_with, compile_graph}; use rill_core::builtin::Registry; use rill_core::traits::ParamValue; // Simple compilation to an Algorithm<T> let mut prog = compile::<f32>("main = _ * 0.5").unwrap(); let mut out = [0.0f32; 64]; prog.process(Some(&[1.0f32; 64]), &mut out).unwrap(); // Compile with a built-in registry (for DSP primitives) let registry = rill_adrift::lang_builtins::full_registry::<f32>(); let mut prog2 = compile_with::<f32>( "main = sine(?freq) * ?gain", ®istry, 44100.0, ).unwrap(); let freq_idx = prog2.param_index("freq").unwrap(); prog2.set_param(freq_idx, ParamValue::Float(440.0)); let gain_idx = prog2.param_index("gain").unwrap(); prog2.set_param(gain_idx, ParamValue::Float(0.5)); // Whole-graph compilation with runtime ?name parameters const BUF_SIZE: usize = 256; let mut engine = compile_graph::<f32, BUF_SIZE>( "main = sine ?freq=440.0 * ?gain=0.5", ®istry, 44100.0, ).unwrap(); engine.handle().send(rill_core::queues::CommandEnum::SetParameter( rill_core::queues::SetParameter { anchor: "main".into(), parameter: "freq".into(), value: ParamValue::Float(440.0), port: String::new(), source: rill_core::queues::SignalOrigin::Manual, timestamp: 0, sample_pos: None, } )).unwrap(); }
Per-crate registration without rill-adrift
If you depend on individual crates instead of the umbrella rill-adrift,
each crate provides its own register_lang_builtins() function to populate
a rill_core::builtin::Registry:
#![allow(unused)] fn main() { use rill_core::builtin::Registry; let mut reg = Registry::<f32>::new(); rill_core_dsp::lang::register::register_lang_builtins(&mut reg); rill_router::register::register_lang_builtins(&mut reg); rill_digital_effects::register::register_lang_builtins(&mut reg); // Feature-gated crates follow the same pattern: // rill_fft::register::register_lang_builtins(&mut reg); // rill_sampler::register::register_lang_builtins(&mut reg); let mut prog = compile_with::<f32>("main = sine(440) * gain(0.5)", ®, 44100.0).unwrap(); }
The rill-adrift crate provides full_registry() as a convenience that
aggregates all available per-crate registries in one call.
Using individual DSP algorithms
For algorithm-level processing without the graph infrastructure:
#![allow(unused)] fn main() { use rill_core_dsp::generators::basic::SineOsc; use rill_core_dsp::delay::Delay; use rill_core::traits::Algorithm; let sample_rate = 44100.0; let mut osc = SineOsc::<f32>::new(440.0, sample_rate); osc.set_amplitude(0.5); let mut delay = Delay::<f32>::new(0.3, sample_rate); delay.set_feedback(0.4); let mut input = [0.0f32; 64]; let mut output = [0.0f32; 64]; Algorithm::process(&mut osc, None, &mut input)?; Algorithm::process(&mut delay, Some(&input), &mut output)?; }
Signal I/O
Enable the io feature on rill-adrift (default):
rill-adrift = { version = "0.6.0-M2", features = ["io", "alsa"] }
Available backends: portaudio (default/minimal), alsa (Linux), pipewire (Linux), jack (Linux).
The Input node (push model) drives the graph from the source side.
Output (pull model) drives the graph from the sink side.
The orchestrator creates the backend and drives the CompiledGraphEngine
from the I/O callback.
Two-Thread Architecture
- Signal thread (hard or soft RT) — runs
CompiledGraphEngine::process(). No heap allocs, no locks, no syscalls. - Control thread (tokio green threads) — runs
Patchbaywith automatons (LFO, envelopes, sequencers). Communicates via the actor mailbox (ActorRef<CommandEnum>) returned byengine.handle().
Next steps
- Architecture Overview — core concepts
- Signal graph (rill-graph) — graph processing details
- The World of Automatons — automation system
- Real-Time Safety — RT constraints and rules
- Crates reference — full crate list with features