Realtime Collaboration
Songbird supports realtime collaboration — multiple users can work on the same project simultaneously. Changes merge automatically using structured, music-aware merge strategies that understand the.bird file format, mixer state, and plugin state.
How It Works
Collaboration is built on three layers:- Collab Server — A lightweight Node.js WebSocket server that manages project rooms, syncs file changes, and merges concurrent edits
- React Client — A WebSocket client in the React UI that auto-pushes changes on every commit and applies remote changes
- Rust State Sync — The
songbird-statecollab modules (collab.rs,collab_state_sync.rs,collab_mixer_sync.rs) handle file reading for push, and write + reload remote files with echo suppression via the sync engine’s guard flags
Quick Start
1. Start the Server
ws://localhost:8765 by default.
2. Create a Room
In Songbird, open the Collaboration panel. Enter the server URL, your name, and click Connect. Then click Create Room — you’ll receive an invite code (e.g.,a1b2c3d4).
3. Invite a Collaborator
Share the invite code. The other user connects to the same server and clicks Join Room with the invite code. They receive the current project files automatically.4. Collaborate
Changes sync automatically:- Edit notes, arrangements, or structure in
.bird→ auto-synced - Move faders, pan knobs, toggle mute/solo → auto-synced
- Tweak plugin parameters → auto-synced
Sync Protocol
Message Types
| Direction | Type | Description |
|---|---|---|
| Client → Server | create | Create a new room (returns invite code) |
| Client → Server | join | Join a room with invite code |
| Client → Server | push | Push file changes after a commit |
| Client → Server | pull | Request current file state |
| Client → Server | presence | Send cursor/tool position |
| Client → Server | startTunnel | Start a Cloudflare tunnel to expose the server port |
| Client → Server | stopTunnel | Stop the active tunnel |
| Client → Server | mixerUpdate | Real-time mixer change (vol/pan/mute/solo) |
| Client → Server | cursorMove | Figma-style cursor position update |
| Server → Client | created | Room created confirmation + invite code |
| Server → Client | joined | Joined room + current files + peer list |
| Server → Client | update | Remote peer pushed changes (merged) |
| Server → Client | pushAck | Push acknowledged (may include merge result) |
| Server → Client | presence | Remote peer cursor update |
| Server → Client | peerJoined / peerLeft | Peer connectivity |
| Server → Client | tunnelStarted | Tunnel active — includes public URL |
| Server → Client | tunnelStopped | Tunnel shut down |
| Server → Client | mixerUpdate | Remote peer changed mixer state |
| Server → Client | cursorMove | Remote peer cursor position |
Sync Flow
Intelligent Merge
The server doesn’t do naive line-by-line text merge. It understands Songbird’s file formats..bird File Merge
The merge engine parses .bird files into a structured tree:
| Scenario | Result |
|---|---|
User A edits trk 1 kick in sec verse, User B edits trk 2 bass in sec verse | ✅ Auto-merge (different tracks) |
User A edits trk 1 in sec intro, User B edits trk 1 in sec verse | ✅ Auto-merge (different sections) |
| User A adds a new section, User B adds a different section | ✅ Auto-merge (both appended) |
| User A changes BPM, User B edits notes | ✅ Both applied (independent blocks) |
User A and B both edit trk 1 kick in sec verse | 🔄 Last-write-wins |
JSON File Merge (daw.state.json, daw.edit.json)
JSON files are merged with recursive deep-merge at the property level:
| Scenario | Result |
|---|---|
| A changes track 1 volume, B changes track 2 pan | ✅ Auto-merge |
| A mutes track 3, B solos track 3 | ✅ Both applied (different properties) |
| A and B both change track 1 volume | 🔄 Last-write-wins |
Conflict Resolution
All conflicts use last-write-wins — the most recent push takes precedence. This is intentionally simple: users naturally learn to avoid stepping on each other’s toes, and the granularity (track + section level) means conflicts are rare in practice.Presence
The server broadcasts real-time presence updates:- Who’s online — list of connected peers
- Cursor position — which section/track/tool each user is working with
- Activity feed — human-readable log of remote changes
Tunnel (Share Session)
The collab server has a built-in Cloudflare Tunnel integration via theuntun npm package. This lets you expose the collab server to the internet with a single click — no manual Cloudflare/Tailscale setup, no port forwarding.
- Click Share Session (Public Tunnel) in the Collaboration panel
- The server spawns a Cloudflare Tunnel on the collab port (default 8765)
- A public
https://URL is generated and displayed — share it with collaborators - Remote peers connect to the tunnel URL instead of
localhost - Click Stop tunnel when done
Optimistic Mixer Sync
Mixer fader changes (volume, pan, mute, solo) are sent optimistically over the WebSocket in real time, bypassing the git commit cycle. This provides ~5-20ms latency for continuous controls like fader drags, compared to ~100-300ms via the commit path.- Outbound: Local changes are throttled to ~30Hz and sent as
mixerUpdatemessages - Inbound: Remote changes are applied directly to the Zustand store
- Local lock: While the user is dragging a slider, remote updates for that control are ignored to prevent jitter
- Source of truth: The git commit layer remains authoritative. Optimistic updates are just a fast preview.
Deployment
The collab server is a single Node.js process with no external dependencies (no database, no Redis). It can run on any machine with Node.js 18+.Local Development
Production (Ubuntu Server)
Audio Assets
Audio samples and recordings are not synced via the collab server. For large audio assets, use a cloud storage bucket (GCS or AWS S3) with Git LFS.Key Files
| File | Purpose |
|---|---|
server/server.js | WebSocket server — room management, push/pull sync, presence, merge orchestration |
server/merge/bird-merge.js | .bird parser, serializer, and three-way structured merge |
server/merge/json-merge.js | Recursive deep-merge for JSON state files |
server/test-merge.js | Merge engine test suite (7 tests) |
react_ui/src/services/collabSocket.ts | WebSocket client — connect, push/pull, auto-push on commit |
react_ui/src/data/slices/collab.ts | Zustand store — connection state, peers, activity feed |
react_ui/src/components/CollabPanel.tsx | UI — connect dialog, room create/join, peer list, tunnel controls |
react_ui/src/components/CollabCursors.tsx | Figma-style peer cursor overlay component + tracking hook |
react_ui/src/services/collabMixerSync.ts | Optimistic mixer sync — local lock, throttled broadcast, inbound apply |
rust/crates/songbird-state/src/collab.rs | Rust collab core — state sync for push/pull |
rust/crates/songbird-state/src/collab_state_sync.rs | Collab state synchronization |
rust/crates/songbird-state/src/collab_mixer_sync.rs | Optimistic mixer sync for collaboration |
utils/collab-server.sh | Server launch script |
Design Decisions
- WebSocket from React — JavaScript has native WebSocket support. Running the WebSocket client in React reuses the existing Tauri IPC bridge for file I/O.
- File-level sync, not git-object sync — Sending full file contents (not git objects/patches) is simpler and the files are small text. Each client maintains its own local git history for undo/redo independently.
- Server-side merge — The server runs the structured merge engine. This is simpler than client-side merge and ensures a single source of truth. The tradeoff is offline editing doesn’t auto-merge (but local git undo/redo still works fine).
- Last-write-wins — Simple, predictable, and users rarely conflict at the track/section level in practice. No complex conflict resolution UI needed.
- Echo suppression on apply — When applying remote files, the
undo_redo_in_progressguard flag is set (same mechanism as undo/redo) to prevent echo-pushing the same changes back to the server.