This file provides context for AI assistants (Claude, Copilot, etc.) working on this codebase.
GroupSync is a mobile web app for synchronizing multiple Sendspin audio players. It measures speaker offset by playing a calibration track and listening via the device microphone, then pushes calculated offsets to each player.
- WebSocket-based synchronized audio streaming protocol
- Players connect to Music Assistant server at
ws://host:port/sendspin - Clock synchronization via NTP-style
client/time/server/timemessages - Audio chunks include server timestamps (microseconds)
- Players apply sync delay to compensate for hardware latency
- Static delay: User-configurable offset in milliseconds
- Positive value: Delays playback (plays later)
- Negative value: Advances playback (plays earlier)
- Used to compensate for speaker distance, hardware latency, network jitter
- Algorithm to find time offset between two signals
- Compare expected click waveform to recorded audio
- Peak in correlation indicates sample offset
- Convert samples to milliseconds:
offsetMs = (samples * 1000) / sampleRate
┌─────────────────────────────────────────────────────────────┐
│ GroupSync Web App │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Connection │ │ Calibration │ │ Offset Push │ │
│ │ Panel │ │ Wizard │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────────▼──────────┐ │
│ │ MA Client │ │ Audio │ │ Sync Offset │ │
│ │ (WebSocket) │ │ Detector │ │ Pusher │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
└─────────┼────────────────┼────────────────────┼────────────┘
│ │ │
▼ ▼ ▼
Music Assistant Microphone Sendspin Players
Server (Web Audio) (via protocol msg)
| Directory | Purpose | Key Files |
|---|---|---|
src/ma-client/ |
Music Assistant WebSocket connection | MAWebSocketClient.ts |
src/calibration/ |
Audio detection and offset calculation | AudioDetector.ts, OffsetCalculator.ts |
src/sync-push/ |
Push offset to players | SyncOffsetPusher.ts |
src/store/ |
Zustand state management | useConnectionStore.ts, useCalibrationStore.ts |
src/components/ |
React UI components | CalibrationWizard.tsx, PlayerList.tsx |
WebSocket Messages (Music Assistant API):
// Send command
ws.send(JSON.stringify({
message_id: Date.now(),
command: 'players/all',
args: {},
}));
// Receive response
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.message_id === sentId) {
// Handle response
}
};Audio Processing (Web Audio API):
// Microphone capture
const stream = await navigator.mediaDevices.getUserMedia({
audio: { echoCancellation: false, sampleRate: 48000 }
});
const audioContext = new AudioContext({ sampleRate: 48000 });
const source = audioContext.createMediaStreamSource(stream);State Management (Zustand):
const useConnectionStore = create<ConnectionState>((set) => ({
serverUrl: '',
connected: false,
players: [],
connect: async (url) => { /* ... */ },
}));- Add message type to
src/types/protocol.ts - Implement in
src/ma-client/MAWebSocketClient.ts - Call from component or store
- Edit
src/calibration/AudioDetector.tsfor capture logic - Edit
src/calibration/OffsetCalculator.tsfor algorithm - Constants at top of files control thresholds
- Create in
src/components/ - Use Tailwind classes for styling
- Mobile-first: design for small screens
- Connect to Music Assistant server
- List available players
- Play calibration track
- Microphone capture works
- Clicks detected visually
- Offset calculated and displayed
- Offset pushed to player
Open browser console for logs. Key events:
[MA] Connected to server[MA] Players discovered: N[Audio] Microphone stream started[Calibration] Click detected at sample N[Offset] Calculated: +X.Xms
react/react-dom- UI frameworkzustand- State management
vite- Build tooltypescript- Type checkingtailwindcss- Styling
WebSocket- Server communicationAudioContext- Audio processingMediaDevices.getUserMedia()- Microphone accessAnalyserNode- FFT for frequency detection
Z:\CodeProjects\windowsSpin- Windows Sendspin playerZ:\CodeProjects\SpinDroid- Android Sendspin player
github.com/Sendspin/sendspin-js- JS player librarygithub.com/Sendspin/sendspin-cli- Python CLI player
New message type for pushing sync offset:
// Client sends to player
{
type: 'client/sync_offset',
payload: {
player_id: string,
offset_ms: number, // +delay, -advance
source: 'groupsync',
}
}
// Player responds (optional)
{
type: 'client/sync_offset_ack',
payload: {
player_id: string,
applied_offset_ms: number,
}
}- HTTPS Required - Microphone access requires secure context
- 48kHz Sample Rate - Match Sendspin's audio format
- Echo Cancellation OFF - We need the raw audio signal
- Mobile Browsers - May need user gesture to start AudioContext
- Cross-Origin - MA server needs CORS headers for WebSocket