ComposeWithMe

Multi-Track Playback

BPM-locked, sample-aligned playback via the Web Audio API

Overview

Every track in a song plays back together, sample-aligned, via the Web Audio API. This is the core listening experience: however many people uploaded a part, everyone hears the full mix together in sync in the browser — no server-side mixing or transcoding involved.

How it works

  • MultiTrackPlayer.tsx owns a single AudioContext for the song. Each track is fetched from its downloadUrl (a raw GitHub URL or a public Blob URL — both fetchable with no auth) and decoded into an AudioBuffer via decodeAudioData.
  • Play schedules every track's AudioBufferSourceNode to start() at the same future audioContext.currentTime + 0.05 — the fixed lead time gives every source the same scheduling instant, which is what keeps tracks sample-accurately in sync rather than drifting apart from independent .play() calls.
  • Volume and mute are per-track GainNodes that persist across plays/pauses, so adjusting a fader or toggling mute doesn't require re-decoding or restarting playback.
  • Pause records the elapsed offset (playbackOffsetRef) so Play resumes from the same point rather than restarting from 0; Stop resets that offset back to 0.
  • Recording a new take reuses this same playback path — see In-Browser Mic Recording.

Key files

FileRole
src/components/MultiTrackPlayer.tsxTransport (play/pause/stop), track list, AudioContext/GainNode management
src/lib/audio.tsdrawWaveform() — draws PCM waveform to canvas from a decoded AudioBuffer
src/components/TrackRow.tsxPer-track UI: waveform, volume, mute, delete

Limitations / notes

  • No per-track offset/timeline positioning — every track always plays from position 0. There's no way to nudge a track earlier/later if someone's take was recorded slightly off.
  • Sync quality depends on all tracks having been recorded at the same BPM with a click track — the app has no way to detect or correct drift between mismatched recordings.