Aug 2025 · Building with Web Audio
00
The project starts as a solo-device music app. old.html — 1,246 lines of Tailwind + Canvas with a purple theme — let you pick an audio file, watch frequency rings pulse, and ask Gemini for synced lyrics.
A single device. But the per-band frequency analysis — AnalyserNode plumbing, configurable cutoffs, frequency-band smoothing — lays the groundwork for the audio engine.
Oct 2025 · First P2P sync
01
Two phones, PeerJS over WebRTC — the first real attempt at device sync. sync.html already ships a dual-strategy sync algorithm, and production didn't reach that level again until April 2026.
-
The signature blue lands
--accent-color: #3b82f6 appears for the first time — three months before the MUSIXQUARE name is chosen. The wordmark, the tabs, the buttons, all still carry this exact hex today.
-
Hard-sync jump (drift > 0.5s)
If the guest falls more than half a second off, snap to the host's currentTime. Blunt, but recovers from any desync.
-
Soft-sync via playbackRate (0.05s–0.5s)
Smaller drift gets corrected by nudging playbackRate — the guest catches up or slows down smoothly without an audible skip.
-
1-second heartbeat
setInterval fires sync payloads every second: { time, paused, playbackRate, timestamp }. Network delay is approximated from the timestamp delta.
-
Audio-reactive visual
A circle that scales with playbackRate, so you can see the sync correction happening.
Nov 2025 · Simpler sync, local nudge
02
A clean-slate rewrite. October's rate-scaling algorithm is thrown out in favor of
a 3-second countdown that both sides arm at the same moment,
then setTimeout to the same local deadline. Simpler, more reliable,
and easier to reason about. Per-device offset gets a dedicated UI for the first time.
-
PeerJS handshake + ID exchange
Tap to copy your ID, paste the other device's ID, press Connect.
-
File transfer over P2P
The host's MP3 ships to the guest as a Blob, cached in an ObjectURL, ready to play.
-
Countdown-based simultaneous start
A play-command with delay: 3000. Both sides setTimeout to the same local deadline. Simple, already works.
-
Per-device offset nudge
±0.05s buttons adjust only the local device; an isLocalAdjusting flag blocks the seek from broadcasting. The direct ancestor of today's Nudge Sync.
-
iOS audio-unlock trick
play() → pause() → currentTime = 0 to satisfy iOS Safari's user-gesture requirement. Still in the codebase today.
Dec 2025 · v1.0 – v1.2 · The channel lab
03
Back to a single device. If October cracked open per-band analysis and November cracked open device sync, December cracks open channel routing — ChannelMerger, per-channel GainNodes, a dedicated subwoofer LPF. The audio-graph scaffolding MUSIXQUARE ships today is born here.
-
v1.0 · Stereo oscillator lab
220 Hz sine (left) + 440 Hz triangle (right) routed through ChannelMerger and per-channel GainNodes — first real test of stereo crossfading at the Web Audio level.
-
v1.1 · Live visualizer + dark theme
HTML5 Canvas wired to AnalyserNode for real-time frequency spectrum. Tailwind dark-mode palette, still in use today.
-
v1.2 · Subwoofer channel
Independent toggle with a 120 Hz low-pass filter. The first hint that roles (not just channels) could drive the UX.
-
v1.2 · L / Stereo / R tab controls
Three-tab channel picker with a pink active state — the direct ancestor of today's role grid.
-
v1.2 · Auto-start + Material Icons
"Start" overlay removed; any screen touch unlocks AudioContext. Emoji get swapped for Material Icons — dark-mode legibility.
-
Late Dec · Name + design system locked in
MUSIXQUARE the name, #3b82f6 the signature blue, MUSI<XQUARE> wordmark with fade — all settle into their current form. The 5-tab nav (Play / Playlist / Connect / Settings / Help) and Light / Dark / System theme switcher arrive together.
-
Late Dec · Full audio chain
BiquadFilter → ChannelSplitter → per-channel Gain → Merger → Dry/Wet → Convolver → Analyser → destination. The Convolver gets swapped for Tone.js a few weeks later, but the routing pattern is the one MUSIXQUARE still uses.
Jan · Everything but the git log
04
Between December's v1.2 channel lab and the first commit on Jan 25,
the three threads — October's frequency analysis, November's P2P sync,
December's channel routing — merged into a single app. That month of
intense iteration was only ever saved as the final file, never the steps in
between. By the time git history starts, MUSIXQUARE already exists in
recognizable shape:
-
The five-tab nav
Play / Playlist / Connect / Settings / Chat — the same pattern that ships today.
-
Host / Guest + QR-code invite
Scan to join. Copy link. Host panel, guest panel, role badge — all already wired.
-
Full audio-effect chain
Tone.js-powered reverb (mix / decay / pre-delay / low-cut / high-cut), 5-band graphic EQ + Preamp, Virtual Surround width, Virtual Bass level.
-
7.1 surround channels
FL / FR / Center / LFE / Side L·R / Rear L·R — a working 8-channel role picker. Later parked for UX complexity — now at roadmap #4, likely to come back if demand piles up.
-
Nudge Sync overlay
±1 / ±10 ms buttons with a total-offset display. November's isLocalAdjusting flag, grown into an actual UI.
-
Onboarding, help, demo, theme switcher
3-slide carousel, help modal with Host/Guest explainer, "Try the demo" button, Light / Dark / System theme picker.
After months of loose file management, Jan 25 — first commit.
Feb · TURN-ing point
05
-
Remote connections unlocked
Metered.ca TURN server integrated; WebRTC now reaches across networks for the first time.
-
Drift-aware sync
3-sample RTT replaces naive timestamps. Devices stay aligned even over flaky Wi-Fi.
-
Host-ad auto-pause
Guests pause automatically when the host's YouTube hits an ad.
-
MUSIXQUARE 2.0 rewrite begins
The 12,000-line app.js starts breaking into TypeScript ES modules under src/.
-
TypeScript introduced
71 P2P protocol messages, EventBus handlers, and state paths get real types. Build fails before runtime fails.
-
i18n key-based system
Hard-coded Korean strings → semantic keys with compile-time validation. Groundwork for every language shipping later.
Mar · Playing house
06
-
E2E test playground
174 Playwright tests covering late-join, chaos, and complex multi-device scenarios.
-
Logo animation
MUSIXQUARE animates like handwriting.
-
Chat system
Slash commands (/debug, /nick), autocomplete dropdowns, profanity filter, grouped-by-minute bubbles.
-
Beat detection
High-precision BPM analyzer with Phase Sync — an easter egg where the logo pulses to the track.
-
Spectrum visualizer
A classic line spectrum joins the circular visualizer.
-
Mobile landscape redesign
2-column dashboard layout, right-side sidebar, fade scrollbar. Mobile landscape layout finally works.
-
Promo video pipeline
First rendered trailer scenes land — useful assets for the April promo videos.
Apr · More reliable sync
07
-
SharedClock architecture
Host clock becomes the single source of truth. Every device plays at time X, not now — drift becomes correctable instead of inevitable.
-
Unified Sync v6.1
Heartbeat + SharedClock + Latency collapse into one system that works for local files and YouTube.
-
Nudge sync panel
Visible inter-device clock offset, manual offset slider, dual Auto/Manual display.
-
YouTube 2-stage sync
Guest syncs go through a mandatory rendezvous handshake. Unavailable videos (errors 100/101/150) auto-skip.
-
QR code optimization
Uppercase-alphanumeric encoding + path-based join URL — lower error-correction, cleaner scan.
-
Mobile video UI
Mobile portrait video fills the screen; YouTube mode gets its own fullscreen button.
-
Three.js promo assets
A 3D glass music note and floating UI card scenes — used across press and promo.
-
9 new locales prepared
Translation files for ja, es, zh-CN, zh-TW, de, fr, pt-BR, it, ru — fully covered, UI wiring parked until demand shows up.
-
Public /designsystem and /roadmap
Design reference + a transparent list of every technical limit we know about. You're reading the third one.