Skip to main content

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

┌─────────────────────────────────────────────────────────┐
│  App.tsx                                                 │
│  ├─ Transport          — top bar: play/stop/BPM/scale    │
│  ├─ ArrangementView    — timeline with track lanes       │
│  ├─ ChatPanel          — AI assistant sidebar            │
│  ├─ HistoryPanel       — git commit history              │
│  ├─ BirdFilePanel      — .bird file text editor          │
│  ├─ MidiEditor         — piano roll + velocity lane      │
│  ├─ SampleEditor       — audio waveform display          │
│  ├─ SheetMusicView     — notation rendering              │
│  ├─ GuitarTabView      — guitar tablature                │
│  ├─ MixerPanel         — channel strips + master         │
│  ├─ ScriptFXEditorPanel— DSP script editor               │
│  ├─ CustomEditorPanel  — stock plugin UI host            │
│  ├─ CollabCursors      — multiplayer cursors             │
│  ├─ SettingsPanel      — audio/MIDI/UI settings          │
│  ├─ DebugPanel         — dev tools overlay               │
│  └─ StatusBar          — bottom strip + ExportStatusIndicator │
└─────────────────────────────────────────────────────────┘

Component Structure (Atomic Design)

components/
├── atoms/          — Primitive UI elements (Button, Knob, Slider, Select, etc.)
├── molecules/      — Composed units (LevelMeter, PluginSlots, MuteSoloButtons, etc.)
├── organisms/      — Larger composed blocks (MixerChannel, MasterChannel, TrackLane, etc.)
└── panels/         — Full panel/page-level components (ArrangementView, MidiEditor, etc.)

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 UIs
  • ADSRView — Interactive ADSR envelope editor with drag handles
  • AudioWaveform — Audio waveform display component
  • SessionClipSlot — Clip slot for session/performance view
  • CodeEditor — CodeMirror-based text editor (used by ScriptFX and BirdFilePanel)

Molecules (molecules/)

Composed components combining multiple atoms:
  • LevelMeter — per-track audio level visualization
  • PluginSlots — instrument/FX/strip slot management
  • MuteSoloButtons, PanControl, VolumeFader — mixer controls
  • StereoMeters — spectrum, stereo width, phase correlation meters
  • RecordStrip — MIDI/audio recording controls per track
  • PluginShell / PluginHeader / PluginBody / PluginSection — shared layout components for stock plugin UIs
  • EditorPanelShell — container shell for custom editor panels

Organisms (organisms/)

Larger self-contained UI blocks:
  • MixerChannel / MasterChannel — full channel strips
  • TrackHeader / TrackLane — arrangement view rows
  • TimelineRuler — bar/beat ruler
  • GenerateInlinePopup — Option+click-drag inline AI generation overlay
  • LoadingScreen — first-load overlay; ExportStatusIndicator — inline progress widget mounted in StatusBar

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.
StoreRust SlicePurpose
useTransportStoreTransportSlicePlayback position, BPM, key signature, scale, loop state
useMixerStoreMixerSliceTrack list (denormalized), volumes, pans, mutes, solos
useSongStoreSongSliceSong structure: sections, totalBars
useClipStoreClipSlice(types: NoteData, AudioClip, TakeLane — data in Track for now)
usePluginStorePluginSlice(types: PluginSlotView, MissingPluginInfo — data in Track for now)
useRecordingStoreRecordingSlice(types: AudioSource, MonitoringMode — data in Track for now)
useAutomationStoreAutomationSlice(types: AutomationPoint, AutomationCurve — data in Track for now)
useSettingsStoreSettingsSliceUser preferences (global, not per-project)
useChatStoreAiSliceAI chat messages, threads, right panel state
useLyriaStoreAiSliceAI music generation config
useGenerateStoreAiSliceAI generation state
useCustomEditorStoreProjectSliceCustom MIDI editor definitions
useScriptFxEditorStoreProjectSliceScript FX editor definitions
useNotesStoreProjectSlicePanel 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 from mixer.ts for backward compatibility:
ModuleTypes
slices/clip.tsNoteData, AudioClip, TakeLane, ClipSelection, ClipClipboard
slices/plugin.tsPluginSlotView, PluginInstance, MissingPluginInfo, PluginSubstitution, AvailablePlugin
slices/recording.tsMonitoringMode, ArmState, AudioSource, AudioMode, MidiInput
slices/automation.tsAutomationPoint, AutomationCurve
slices/song.tsSection

Domain Selector Hooks (data/hooks/useTrackDomain.ts)

Per-track domain hooks provide render isolation via useShallow:
HookDomainFields
useTrackSong(id)Songname, type, color, isReturn, isMaster
useTrackMixer(id)Mixervolume, pan, muted, solo, sends
useTrackClips(id)Clipnotes, audioClips, takeLanes
useTrackPlugins(id)Plugininstrument, fxSlots, channelStrip
useTrackRecording(id)RecordingrecordArmed, audioSource, monitorMode
useTrackAutomation(id)Automationautomation 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:
Rust engine change
  → ctx.send(eventName, json)
  → Tauri event / WebSocket message
  → sync engine subscriber
  → useMixerStore.setState() / useSongStore.setState() / etc.
  → addStateListener callback
  → Zustand setState() (partial merge)

Key Event Channels

EventDirectionDescription
meters:rt_frameRust → JSBatched 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:stateRust → JSFull track/section/note data after .bird load
track:notes_changedRust → JSLightweight note update from MIDI editing
mixer:track_updateRust → JSPer-track volume/pan/mute/solo
project:loading_progressRust → JSLoading progress messages during startup
project:bird_content_changedRust → JSNotifies BirdFilePanel of .bird file content changes
collabCursorUpdateServer → JSCollaborator cursor position updates (via WebSocket)
collabPresenceServer → JSUser join/leave events for the collaboration room
ai:inline_generate_progressRust → JSProgress updates during inline AI audio/MIDI generation

Key Libraries

  • React 19 — UI framework
  • Tailwind CSS v4 — Utility-first styling (via @tailwindcss/vite plugin)
  • Zustand — Lightweight state management
  • Radix UI — Accessible primitive components
  • class-variance-authority — Component variant styling
  • @tonaljs — Music theory calculations (chord, note, tonal)

Development

cd react_ui
npm install
npm run dev      # Vite dev server (for standalone development)
npm run build    # Production build (output to dist/, served by Tauri WebView)
Note: In production, the React app runs inside the Tauri WebView — not a browser. The window.__SONGBIRD__ global is injected by the Tauri bridge and provides the backend event system. All commands go through send() from @/sync/api or invoke() from @/sync/transports/promiseHandler.