Skip to main content

Songbird Rust Engine (rust/)

The Rust backend provides the complete audio engine, DSP plugins, clip management, state synchronization, and Tauri IPC layer for Songbird. It replaces the C++ Tracktion Engine / JUCE backend with a pure-Rust implementation (plus a thin FFI bridge for hosting third-party VST3/AU plugins).

Tech Stack

  • Rust 2021 edition — Systems language with memory safety guarantees
  • Tauri v2 — Desktop app shell (replaces JUCE WebBrowserComponent)
  • cpal 0.15 — Cross-platform audio I/O
  • midir 0.10 — Cross-platform MIDI I/O
  • rayon 1.10 — Data-parallel graph processing
  • serde / serde_json — Serialization (project files, IPC, state sync)
  • hound 3.5 — WAV file reading/writing
  • git2 0.19 — Git operations for sync engine
  • crossbeam-channel 0.5 — Lock-free communication between audio and UI threads

Workspace Structure

The workspace is organized into six layers, bottom-up:
rust/
├── Cargo.toml                  — Workspace root (22 crates)
├── Cargo.lock
├── README.md                   — This file
├── crates/
│   ├── core/                   — Zero-dependency foundations
│   │   ├── songbird-types/     — Shared types (ids, project defaults, value trees)
│   │   ├── songbird-dsp/       — Pure DSP primitives (filters, envelopes, oscillators)
│   │   ├── songbird-theory/    — Music theory (scales, chords, voicings)
│   │   └── songbird-ffi/       — Low-level FFI helpers
│   │
│   ├── engine/                 — Audio engine + DSP
│   │   ├── songbird-engine/    — Audio graph, transport, scheduling, automation, ring buffer
│   │   ├── songbird-plugins/   — 16 stock DSP plugins (effects + instruments)
│   │   ├── songbird-clips/     — Clip model, .bird parser/serializer
│   │   ├── songbird-export/    — Offline rendering (master, stems, bounce)
│   │   ├── songbird-record/    — Audio + MIDI recording
│   │   └── songbird-reverb-vst3/ — Stock reverb packaged as a VST3
│   │
│   ├── data/                   — State, sync, collaboration
│   │   ├── songbird-state/     — Project model, change tracking, undo/redo, presets
│   │   ├── songbird-sync/      — Sync engine (dispatch bridge, channel guards)
│   │   ├── songbird-files/     — Project file I/O, archive format
│   │   └── songbird-collab/    — Multiplayer cursors, presence, git-based merge
│   │
│   ├── integration/            — Platform + external bridges
│   │   ├── songbird-host-ffi/  — VST3/AU plugin hosting via JUCE FFI
│   │   ├── songbird-plugin-registry/ — Unified plugin registry + cache
│   │   ├── songbird-voices/    — Voice allocation + MIDI routing helpers
│   │   ├── songbird-lyria/     — Lyria audio-generation integration
│   │   └── songbird-composition/ — Gemini/AI composition glue
│   │
│   ├── orchestration/          — Project + session lifecycle (above engine/state, below app)
│   │   ├── songbird-project/   — Project open/save as a single canonical path
│   │   ├── songbird-session/   — Bird→session loader, scheduler lifecycle helpers
│   │   └── songbird-orchestrator/ — Shared EngineBackend: session build, device
│   │                               management, plugin swaps, master capture,
│   │                               plugin editor hosting. Used by BOTH the Tauri
│   │                               app and the headless server.
│   │
│   └── app/                    — Application shells
│       ├── songbird-app/       — Tauri v2 desktop app
│       ├── songbird-cli/       — CLI for batch render + validation
│       ├── songbird-headless/  — Standalone WebSocket server
│       └── songbird-wasm/      — WASM runtime entrypoint
├── vendor/                     — Patched dependencies (midir)
└── tests/                      — Integration test fixtures

Crate Dependency Graph

songbird-app (Tauri desktop)  songbird-headless (WebSocket server)
            │                           │
            └──────────┬────────────────┘

           songbird-orchestrator    ← shared EngineBackend
                       │              (RingBufferEngine, TerminalProvider,
                       │               MasterCaptureState, plugin editors)
       ┌───────────────┼────────────────┐
       ▼               ▼                ▼
 songbird-session  songbird-project  songbird-plugin-registry
       │               │                │
       ├───────────────┴────────────────┤
       ▼                                ▼
 songbird-state / songbird-sync   songbird-host-ffi
       │                                │
       ▼                                ▼
 songbird-engine ◄─────────── songbird-plugins ─── songbird-clips


 songbird-dsp / songbird-types / songbird-theory
The orchestration layer (songbird-orchestrator) is the sole owner of engine lifecycle — both desktop and headless binaries call into it rather than duplicating session-build, device-management, and plugin-editor code.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│  React UI (TypeScript)                                          │
│  ├─ Zustand stores ← → Tauri IPC commands                      │
│  └─ rtFrame listener (30Hz metering data)                       │
├─────────────────────────────────────────────────────────────────┤
│  songbird-app (Tauri v2)                                        │
│  ├─ AppState (Mutex-wrapped engine components)                  │
│  ├─ 202+ #[tauri::command] handlers                             │
│  └─ invoke_handler registration                                 │
├─────────────────────────────────────────────────────────────────┤
│  songbird-engine                                                │
│  ├─ AudioGraph      — connection-based DAG + topological sort   │
│  ├─ Transport       — play/stop/record/loop/seek/metronome      │
│  ├─ ClipScheduler   — timeline MIDI/audio clip playback         │
│  ├─ ClipLauncher    — Ableton-style session view grid           │
│  ├─ AutomationState — breakpoint envelopes per parameter        │
│  ├─ Mixer           — track types, sends, groups                │
│  ├─ MidiRouter      — MIDI event routing + filtering            │
│  └─ SpscRingBuffer  — lock-free audio↔UI communication          │
├─────────────────────────────────────────────────────────────────┤
│  songbird-plugins                                               │
│  ├─ Plugin trait     — uniform interface for all DSP             │
│  ├─ 10 effects       — ChannelStrip, Reverb, Delay, Chorus, ... │
│  └─ 6 instruments    — 4OSC, STK, 303, Sampler, DrumMachine, ..│
├─────────────────────────────────────────────────────────────────┤
│  cpal / midir        — Hardware audio + MIDI I/O                │
└─────────────────────────────────────────────────────────────────┘

Build & Development

cd rust

# Build the entire workspace
cargo build --workspace

# Run all tests (5,240 tests)
cargo test --workspace --exclude songbird-app -- --skip test_robust_session_channel_concurrent_access

# Run clippy linting (zero warnings)
cargo clippy --workspace --exclude songbird-app -- -D warnings

# Build the Tauri app (requires Tauri CLI)
cargo tauri build

# Run specific crate tests
cargo test -p songbird-engine
cargo test -p songbird-plugins
cargo test -p songbird-state

# Run a specific test by name
cargo test -p songbird-engine -- test_graph_parallel

Audio Processing Pipeline

The audio callback processes data through this pipeline each block:
Transport.advance(num_samples)
  → ClipScheduler — resolves which clips are active at current position
  → AudioGraph.process() — topological sort, parallel level execution via rayon
    → For each node (track):
      1. Gather MIDI events from ClipScheduler
      2. Process through PluginChain (instruments → effects)
      3. Apply gain, pan, mute/solo
      4. Sum into output connections
  → Master output buffer
  → cpal audio callback writes to hardware

Real-Time Safety

The audio callback path is designed for zero allocation:
  • AudioBuffer — inline [*mut f32; 8], no heap allocation
  • ScratchPool — pre-allocated per-node buffers, assigned in rebuild_topology()
  • SpscRingBuffer — lock-free single-producer single-consumer for engine↔UI commands
  • No Mutex in audio path — all shared state accessed via lock-free ring buffers
  • Plugin traitprocess() operates on pre-allocated buffers

IPC Layer

The Tauri app exposes 202+ IPC commands organized by category:
CategoryCommandsExamples
Transport10transport_play, set_bpm, get_position, set_loop_range
Tracks12add_track, remove_track, set_track_volume, duplicate_track
Plugins4add_plugin_to_track, set_plugin_param, get_plugin_params
MIDI7send_midi_note_on, send_midi_cc, send_midi_pitch_bend
Automation6get_automation_lanes, add_automation_point, get_automation_value
Project8save_project, open_project, undo, redo
Session6session_new, session_save, session_load, session_close
Export4export_master, export_stem, export_all_stems, bounce_track_cmd
Presets5list_presets, save_preset, load_preset, search_presets
Arrangement5add_marker, add_region, get_arrangement
Clip Launcher7launch_clip_slot, stop_clip_slot, launch_scene
Collaboration4get_collab_status, connect_collab, disconnect_collab
MIDI Devices2list_midi_devices, refresh_midi_devices
Metronome2set_metronome_enabled, get_metronome_state
All commands return JSON via IpcResult<T>:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": "Track 5 not found" }

State Management

React setState()
  → Tauri IPC command (e.g., set_track_volume)
  → AppState (Mutex<T> fields)
  → StateStore.mutate() — snapshots, diffs, notifies
  → Change listeners fire (local/remote source tagging)
  → Undo/redo snapshots maintained (100-deep stack)
The state system replaces the C++ ValueTree + 7-mechanism echo suppression with a single clean design:
  • Versioned mutations — monotonic counter, every change gets a version
  • JSON Pointer diffing (RFC 6901) — /tracks/0/gain paths
  • Echo suppressionChangeSource::Local vs ChangeSource::Remote
  • Undo/redo — snapshot-based, up to 100 levels

Testing

The workspace has 5,240 tests organized by purpose:
Test SuiteLocationCountWhat It Tests
Unit testsEach crate’s mod tests~2,500Individual module correctness
E2E engine testsengine_e2e_tests.rs15Full pipeline: transport → graph → clips → output
Audio output testsaudio_output_tests.rs25Waveform correctness, DSP parity, golden output
IPC roundtrip testsipc_roundtrip_tests.rs87Serialize/deserialize all IPC response types
IPC workflow testsipc_workflow_tests.rs15Full user workflows through IPC layer
IPC coverage testsipc_handler_coverage_tests.rs54All 202+ Tauri command handlers wired correctly
Engine controller testsengine_controller.rs179Offline rendering, callbacks, cross-crate integration, robustness hardening
Edge case testsedge_case_tests.rs25NaN, Inf, empty buffers, extreme values
Stress testsstress_tests.rs15Concurrent mutations, MIDI floods, large graphs
Performance testsperf_tests.rs15Graph throughput, parallel speedup, zero-alloc
RT safety testsrt_safety_tests.rs25Zero allocation in audio callback path
Project format testsproject_format_tests.rs15JSON round-trip, forward compat, Unicode
Plugin factory testssongbird-plugins/lib.rs3Create, round-trip state for all 16 plugins
# Run all tests
cargo test --workspace --exclude songbird-app

# Run only E2E tests
cargo test -p songbird-engine -- engine_e2e

# Run only audio output verification
cargo test -p songbird-engine -- audio_output

# Run only IPC handler coverage
cargo test -p songbird-engine -- ipc_handler_coverage

Key Design Decisions

Graph Topology (vs Tracktion’s Flat Model)

Tracktion uses a flat track-centric model. Songbird uses a connection-based DAG with Connection structs carrying port indices. This enables sidechain routing, parallel compression, and multi-bus routing without architecture changes. Topological sort via Kahn’s algorithm is cached in rebuild_topology().

Parallel Processing (via rayon)

Independent branches of the audio graph execute concurrently. The topological sort assigns levels — nodes at the same level have no dependencies and can be processed in parallel via rayon::scope. All metadata is cached so process() does zero allocation.

Nested Device Chains (Bitwig-inspired)

The PluginChain supports ChainNode variants that can be either a Plugin or a Container holding sub-chains. This enables parallel processing chains, multiband splits, and mid/side processing at the track level.

Plugin Trait

All plugins — stock and external — implement the same Plugin trait:
pub trait Plugin: Send {
    fn type_name(&self) -> &str;
    fn name(&self) -> &str;
    fn initialize(&mut self, sample_rate: f64);
    fn reset(&mut self);
    fn process(&mut self, buffer: &mut AudioBuffer, midi_events: &[MidiEvent]);
    fn params(&self) -> Vec<PluginParam>;
    fn set_param(&mut self, name: &str, value: f64);
    fn save_state(&self) -> Vec<u8>;
    fn load_state(&mut self, data: &[u8]);
    fn tail_seconds(&self) -> f64;
    fn latency_samples(&self) -> usize;
    fn accepts_midi(&self) -> bool;
    fn produces_midi(&self) -> bool;
}

FFI Bridge (for C++ Plugin Hosting)

Rust owns the audio thread. Third-party VST3/AU plugins are hosted via JUCE through a C ABI FFI layer (songbird-host-ffi). The bridge uses non-interleaved buffer layout matching JUCE’s AudioBuffer<float>. The Plugin trait does not assume shared memory, enabling future out-of-process hosting.

Migration Status

ComponentC++ (Tracktion/JUCE)RustStatus
Audio graphTracktion EditPlaybackContextAudioGraph + rayonDone
TransportTracktion TransportControlTransportDone
Tempo mapTracktion TempoSequenceTempoMapDone
Stock plugins (16)C++ JUCE pluginsPure Rust portsDone
Clip schedulingTracktion Clip/TrackClipSchedulerDone
Clip launcherClipLauncher (new)Done
AutomationTracktion AutomatableParameterAutomationStateDone
State syncValueTree + StateSync.cpp (7 mechanisms)StateStore (single design)Done
Bird parserTypeScript bird_parser.tsRust bird_parser.rsDone
Export/renderTracktion Renderersongbird-exportDone
RecordingTracktion RecordingContextsongbird-recordDone
Undo/redoValueTree UndoManagerStateStore snapshotsDone
PresetsC++ preset filesPresetManagerDone
CollaborationCustom C++ bridgesongbird-state collabDone
IPC layerJUCE native functionsTauri commands (202+)Done
C++ plugin hostingsongbird-host-ffiFFI stubs ready
Real audio I/OJUCE AudioDeviceManagercpal AudioDeviceScaffolded

Headless WebSocket Server

The songbird-headless crate runs a standalone WebSocket server that serves the same protocol as the Tauri app, enabling browser-based UI without Tauri:
# Run headless server
cargo run -p songbird-headless -- --port 9000 --no-audio

# Flags:
#   --port <N>      WebSocket port (default: 9000)
#   --project <path> Load a .bird project file
#   --no-audio       Disable cpal audio output (timer-based transport fallback)
Protocol: Text frames use { "eventId": "<command>", "payload": <json> } format (same as Tauri IPC). Binary RT frames broadcast at ~30fps with metering, transport position, recording peaks, and Link status. See ARCHITECTURE.md for the full system design.