Songbird React UI (react_ui/src/)
The React frontend provides the full DAW interface for Songbird, running inside a Tauri WebView. Built with Vite, React 19, Tailwind CSS v4, and Zustand for state management.
Architecture Overview
Component Structure (Atomic Design)
Atoms (atoms/)
Reusable primitives built on Radix UI. Styled with Tailwind + class-variance-authority:
button,input,select,slider,knob,switch,tabs,toast,tooltip, etc.RotaryKnob— Rotary parameter knob with arc visualization, used across all stock plugin UIsADSRView— Interactive ADSR envelope editor with drag handlesAudioWaveform— Audio waveform display componentSessionClipSlot— Clip slot for session/performance viewCodeEditor— CodeMirror-based text editor (used by ScriptFX and BirdFilePanel)
Molecules (molecules/)
Composed components combining multiple atoms:
LevelMeter— per-track audio level visualizationPluginSlots— instrument/FX/strip slot managementMuteSoloButtons,PanControl,VolumeFader— mixer controlsStereoMeters— spectrum, stereo width, phase correlation metersRecordStrip— MIDI/audio recording controls per trackPluginShell/PluginHeader/PluginBody/PluginSection— shared layout components for stock plugin UIsEditorPanelShell— container shell for custom editor panels
Organisms (organisms/)
Larger self-contained UI blocks:
MixerChannel/MasterChannel— full channel stripsTrackHeader/TrackLane— arrangement view rowsTimelineRuler— bar/beat rulerGenerateInlinePopup— Option+click-drag inline AI generation overlayLoadingScreen— first-load overlay;ExportStatusIndicator— inline progress widget mounted inStatusBar
State Management
Zustand Stores (data/)
Stores are organized to mirror Rust’s StateManager slices (10 domain slices).
Types live in their canonical domain module and are re-exported from mixer.ts
for backward compatibility.
| Store | Rust Slice | Purpose |
|---|---|---|
useTransportStore | TransportSlice | Playback position, BPM, key signature, scale, loop state |
useMixerStore | MixerSlice | Track list (denormalized), volumes, pans, mutes, solos |
useSongStore | SongSlice | Song structure: sections, totalBars |
useClipStore | ClipSlice | (types: NoteData, AudioClip, TakeLane — data in Track for now) |
usePluginStore | PluginSlice | (types: PluginSlotView, MissingPluginInfo — data in Track for now) |
useRecordingStore | RecordingSlice | (types: AudioSource, MonitoringMode — data in Track for now) |
useAutomationStore | AutomationSlice | (types: AutomationPoint, AutomationCurve — data in Track for now) |
useSettingsStore | SettingsSlice | User preferences (global, not per-project) |
useChatStore | AiSlice | AI chat messages, threads, right panel state |
useLyriaStore | AiSlice | AI music generation config |
useGenerateStore | AiSlice | AI generation state |
useCustomEditorStore | ProjectSlice | Custom MIDI editor definitions |
useScriptFxEditorStore | ProjectSlice | Script FX editor definitions |
useNotesStore | ProjectSlice | Panel notes attached to views |
useUIStore | (ephemeral) | Panel toggles, editor state, clip selection, drag state |
useInlineGenerateStore | (client-only) | Inline AI generation state: drag region, progress, results |
Domain Type Modules
Types are defined in their canonical domain module and re-exported frommixer.ts for backward compatibility:
| Module | Types |
|---|---|
slices/clip.ts | NoteData, AudioClip, TakeLane, ClipSelection, ClipClipboard |
slices/plugin.ts | PluginSlotView, PluginInstance, MissingPluginInfo, PluginSubstitution, AvailablePlugin |
slices/recording.ts | MonitoringMode, ArmState, AudioSource, AudioMode, MidiInput |
slices/automation.ts | AutomationPoint, AutomationCurve |
slices/song.ts | Section |
Domain Selector Hooks (data/hooks/useTrackDomain.ts)
Per-track domain hooks provide render isolation via useShallow:
| Hook | Domain | Fields |
|---|---|---|
useTrackSong(id) | Song | name, type, color, isReturn, isMaster |
useTrackMixer(id) | Mixer | volume, pan, muted, solo, sends |
useTrackClips(id) | Clip | notes, audioClips, takeLanes |
useTrackPlugins(id) | Plugin | instrument, fxSlots, channelStrip |
useTrackRecording(id) | Recording | recordArmed, audioSource, monitorMode |
useTrackAutomation(id) | Automation | automation curves |
Event-Only Hydration
Zustand stores are pure UI caches — no persistence middleware. Rust pushes initial state via events on startup (settings.react_ready), and all runtime updates flow through the same event path:
Key Event Channels
| Event | Direction | Description |
|---|---|---|
meters:rt_frame | Rust → JS | Batched real-time data at 30Hz: per-track levels, master levels, transport position, spectrum, stereo analysis, CPU stats. JS-side ballistic smoothing provides visual 60Hz. |
track:state | Rust → JS | Full track/section/note data after .bird load |
track:notes_changed | Rust → JS | Lightweight note update from MIDI editing |
mixer:track_update | Rust → JS | Per-track volume/pan/mute/solo |
project:loading_progress | Rust → JS | Loading progress messages during startup |
project:bird_content_changed | Rust → JS | Notifies BirdFilePanel of .bird file content changes |
collabCursorUpdate | Server → JS | Collaborator cursor position updates (via WebSocket) |
collabPresence | Server → JS | User join/leave events for the collaboration room |
ai:inline_generate_progress | Rust → JS | Progress updates during inline AI audio/MIDI generation |
Key Libraries
- React 19 — UI framework
- Tailwind CSS v4 — Utility-first styling (via
@tailwindcss/viteplugin) - Zustand — Lightweight state management
- Radix UI — Accessible primitive components
- class-variance-authority — Component variant styling
- @tonaljs — Music theory calculations (chord, note, tonal)
Development
Note: In production, the React app runs inside the Tauri WebView — not a browser. Thewindow.__SONGBIRD__global is injected by the Tauri bridge and provides the backend event system. All commands go throughsend()from@/sync/apiorinvoke()from@/sync/transports/promiseHandler.