Sync Engine
For humans and LLMs contributing to Songbird.
Overview
Songbird’s Sync Engine is the central nervous system of the application. It routes all state changes between the UI, the audio engine, the file system, and collaboration peers. Every meaningful change flows through a single pipeline — guard check → echo suppression → subscriber notification → persistence — and is committed to an in-process Git repo for undo/redo. The sync engine is UI-agnostic: the same core runs whether the frontend is a Tauri WebView (desktop), a browser connecting over WebSocket (headless server), or a CLI script rendering stems on a VM.UI Client Modes
The sync engine supports multiple UI frontends connecting to the same Rust engine core. This enables desktop development, remote server deployments, and headless batch processing with the same codebase.Tauri WebView (Desktop)
The primary desktop app. React runs inside Tauri’s WebView, communicating with the Rust backend viainvoke() IPC. This is the full-featured mode with GUI, metering, plugin UIs, and real-time audio.
- Transport: Tauri IPC (
window.__SONGBIRD__.invoke()) - Real-time data: Tauri events at ~30Hz (metering, transport position)
- Use case: Day-to-day music production
WebSocket Headless (Server / VM)
Thesongbird-headless binary runs the full audio engine as a WebSocket server with no GUI. Any WebSocket client — including the same React UI running in a standard browser — can connect and control the engine remotely.
- Transport: WebSocket text frames (JSON commands) + binary frames (RT data)
- Binary frame tags:
0x01RT frame,0x02audio clip peaks,0x03bird mutation - Protocol: Same command/event schema as Tauri IPC — the React UI is backend-agnostic
- Use case: Running on cloud VMs, remote collaboration servers, CI/CD render farms, headless recording rigs
CLI (Scripted Operations)
Thesongbird-cli binary provides direct Rust API access for batch operations — export, render, validation. No UI, no WebSocket, no audio I/O.
- Transport: Direct Rust function calls (no IPC overhead)
- Use case:
./songbird-cli render project.bird -o output.wav, CI pipelines, automated stem exports
Sync Engine Architecture
SyncEngineCore — songbird-sync/src/engine_core.rs
Central router for all state updates. Every inbound event flows through a fixed pipeline:
Channels — songbird-sync/src/channels/
Twenty domain channels, each defined as its own submodule under channels/ with a defs.rs (events / command names / wiring) and commands.rs (dispatch arms). Each channel specifies its authority and event list:
The TS side additionally configures echo strategy, persistence, and guard flags per channel (see
react_ui/src/sync/channels/). Each TS channel file also defines typed commands via XCommands interfaces and buildXCommands() factories.
Naming Conventions
Command strings use snake_case for legibility, but JS handler method names remain camelCase per JS convention. The
resolveCommand() function in commandMap.ts converts 'mixer.view_mode' → ['mixer', 'viewMode'] for handler lookup.
Sub-domain commands (e.g., clip.delete, take_lane.add, recording.midi_arm, section.add) route to their parent channel (track or project) with a prefixed lookup key (e.g., 'clip:delete', 'take_lane:add').
Per-Channel Reference
Below is the complete list of events and commands for each channel, grouped by direction.mixer — Track Audio Parameters
transport — Playback & Timing
recording — Track Management & Recording (+ sub-domains: clip, take_lane)
Renamed fromtrack. The standaloneclip,take_lane, andrecordingnamespaces now route through this channel. The legacytrack:*event names have moved underrecording:*.
project — Project Settings & Sections (+ sub-domain: section)
clip — Clip Content (replaces the old bird channel)
ml — ML / AI Generation (replaces the old ai channel)
Covers Lyria music generation, Veo video generation, local LLM chat
inference, and model-download progress.
plugin — Plugin State
chat — AI Chat State
settings — App Settings
notifications — Ephemeral Engine Notifications (fire-and-forget)
meters — Real-Time Audio Metering
GuardFlags — songbird-sync/src/guards.rs
Shared state gates that block updates in specific scenarios:
Guards are implemented as
Arc<AtomicBool> flags in Rust, providing thread-safe, lock-free gating. Each channel specifies which guards must pass before an update is accepted.
UpdateSource
Every change is tagged with its origin for echo suppression:Transport Layer
The sync engine uses trait-based transports to abstract how data flows between system components. This is what enables the same engine to serve Tauri, WebSocket, and CLI frontends.Transport Trait
Transport Implementations
Binary Frame Protocol
Binary frames use a tag-byte prefix for zero-overhead routing:
The
WebSocketTransport automatically decodes binary frames via tag routing and dispatches to both binary and text (parsed JSON) handlers.
Files on Disk
Each project directory contains:
Rule: All
daw.* files are git-tracked and participate in undo/redo.
Bridge Layer — songbird-state/src/bridge_layer.rs
The bridge layer sits between the UI stores and the sync engine, handling persistence gating and mode detection.
BridgeMode
PersistGate
Centralized gating logic that decides whether a store’ssetItem call should proceed to disk:
- Hydration gate — During initial load, don’t persist back (data just came from backend)
- Slider drag gate — During mixer slider drags, suppress mixer persist (audio feedback via RT bypass)
- Streaming gate — During AI chat streaming, suppress chat persist (flush at end)
SliderDragGuard
Per-fader persist suppression using anAtomicU32 counter:
begin_drag()→ increments counterend_drag()→ decrements counter, returnstruewhen all drags endedis_dragging()→truewhile any slider is active
Zustand Stores (React)
Persisted stores synced to the Rust backend:Persist Flow
Hydration Flow (Load)
Real-Time Events (Engine → React)
High-frequency telemetry data (audio levels, transport position, stereo analysis, CPU stats) bypasses the standard state sync to minimize overhead.- Batched Rust to JS: Real-time telemetry is grouped into a single payload and emitted at ~30Hz.
- Direct-DOM Buffer (
rtBuffer): Data is written directly to a shared mutable object (getRtBuffer()). This avoids React re-renders completely. - Ballistic Smoothing: A single
requestAnimationFrameloop handles smoothing (e.g. meter decay) and notifies direct subscribers (canvas, plain DOM nodes). - Zustand Throttling: The full Zustand store (
useMeterStore) is only updated every N frames (~20Hz) for React components that need reactive bindings. - DirectEngineTransport: Slider drags use a real-time bypass path that skips guards and persistence, writing directly to the engine.
Smoothing Constants
Loading Sequence
GuardFlags During Load
Undo/Redo System — songbird-state/src/undo_redo.rs
Uses libgit2 (via the git2 crate, in-process, zero fork) for git operations.
Branch Structure
refs/heads/main— current position (HEAD), moves on undo/redorefs/redo-tip— created on first undo, points to the “newest” undone commit
Operations
Commit:- Check for uncommitted changes — skip if working tree is clean
- Delete
refs/redo-tip(new change invalidates redo) - Create commit on
main - Emit
clip:history_changedto React
- Block if HEAD message contains “Project loaded” or “Initial project state”
- If no
refs/redo-tip, create it pointing to current HEAD - Get HEAD’s parent commit
- Diff HEAD vs parent → get changed files
- Restore working directory from parent
- Move
refs/heads/mainto parent
- Look up
refs/redo-tip— if absent, nothing to redo - Walk backward from redo-tip to find the child of current HEAD
- Diff HEAD vs child → get changed files
- Restore working directory from child
- Move
refs/heads/mainto child - If HEAD now equals redo-tip, delete the ref
Undo/Redo Orchestration
- Set
undo_redo_in_progressguard flag - Flush pending state to disk
- Perform undo/redo via git2
- Reload changed files:
.bird→ re-parse via songbird-clips pipeline.plugins.json→ restore plugin state.mixer.json→ reload mixer and apply to engine + push to React
- Emit
clip:history_changedto update UI - Clear
undo_redo_in_progressguard (blocks React persist echoes)
Commit Sources
Every commit message is tagged with a source:Echo Prevention (Critical)
The sync engine uses multiple mechanisms to prevent feedback loops:- Version counter echo — Each channel tracks a monotonic version; subscribers skip updates they’ve already seen
- JSON compare echo — For channels using
JsonComparestrategy, incoming JSON is compared against cached state (with configurable field rounding for floats like volume/pan) undo_redo_in_progressguard — Blocks all channel commits during undo/redostore_hydratedguard — Blocks ALL commits until initial hydration completesslider_draggingguard — Blocks persistence while user is dragging (usesSliderDragGuardatomic counter)- UpdateSource tagging — Each change carries its origin (React, Engine, Collab); subscribers can filter by source
- PersistGate — Centralized check that gates disk writes on hydration, slider drag, and streaming state
History Panel — React
HistoryPanel.tsx displays live git history in git log --oneline format.
- Fetches via
getHistoryIPC command (reads git via git2 revwalk) - Auto-refreshes on
clip:history_changedevents - Expandable toggle at bottom of app
Key Files
Sync Engine (Rust — songbird-sync)
State Management (Rust — songbird-state)
The state crate now lives atrust/crates/data/songbird-state/. Its
public surface is the StateManager (channel-aligned slices), the
bridges/ layer (slider-drag guard, persist gate, WS bridge protocol),
and the persistence/ layer (bird IO, clips IO, git-based session
history). See its SPEC.md for the full pipeline.