No description
  • TypeScript 91.6%
  • CSS 4.5%
  • Shell 2.2%
  • Nix 0.7%
  • Just 0.5%
  • Other 0.5%
Find a file
Julian Schubert 651bb53eac initial commit
2026-09-27 13:30:35 +02:00
data/config initial commit 2026-09-27 13:30:35 +02:00
docs initial commit 2026-09-27 13:30:35 +02:00
frontend/lightsync initial commit 2026-09-27 13:30:35 +02:00
scripts initial commit 2026-09-27 13:30:35 +02:00
.dockerignore initial commit 2026-09-27 13:30:35 +02:00
.envrc initial commit 2026-09-27 13:30:35 +02:00
.gitignore initial commit 2026-09-27 13:30:35 +02:00
biome.json initial commit 2026-09-27 13:30:35 +02:00
compose.yaml initial commit 2026-09-27 13:30:35 +02:00
Dockerfile initial commit 2026-09-27 13:30:35 +02:00
flake.lock initial commit 2026-09-27 13:30:35 +02:00
flake.nix initial commit 2026-09-27 13:30:35 +02:00
justfile initial commit 2026-09-27 13:30:35 +02:00
pnpm-lock.yaml initial commit 2026-09-27 13:30:35 +02:00
pnpm-workspace.yaml initial commit 2026-09-27 13:30:35 +02:00
README.md initial commit 2026-09-27 13:30:35 +02:00

LightSync

A browser app that listens to music, finds the beat, and sends a cheap sound-to-light board one clean pulse per beat on its AUX input. The board stays in its music mode and steps its chase on each pulse.

How

  • git clone https://codeberg.org/buckfae/LigthSync.git && cd LigthSync
  • docker compose up -d. The first start builds the image and needs internet.
  • Open http://localhost:8080

Why

  • A sound-to-light board detects beats with a bass-energy threshold. That works on dry four-on-the-floor kicks. It fails on rock and metal, because distorted bass and guitars keep the low band full all the time.
  • LightSync does the beat detection in software (a predictive beat tracker) and gives the board the easiest possible input: a short low tone on each beat, silence in between.
  • On the synthetic test songs, a model of such a board scores F = 0.48 on raw rock and 0.15 on raw metal. Fed with the LightSync signal it scores 1.00 and 0.99 (just eval).
flowchart LR
  src["Music source<br/>AUX / USB / mic / tab / system audio / files"] --> ls["LightSync<br/>beat tracker + pulse synth"]
  ls -- "pulses only" --> board["Light board AUX in"]
  ls -. "optional: music, monitor mix" .-> pa["PA / headphones"]

Use it

  • Chrome or Edge only: only Chromium has system audio capture and output device selection (AudioContext.setSinkId).
  • Open the app on localhost only. Chromium allows audio capture only in a secure context, and a LAN address is not one. So the container listens on loopback only.
  • The Setup guide button lists the setups for a venue: line in from a mixer, Spotify on the laptop, one jack for PA and board, room microphone, headphone monitor.
  • Put the board route first (lowest latency).
  • The browser keeps the settings per address. Settings made on just dev (port 5173) do not show on port 8080, so calibrate on the address you use at the venue.
  • docker compose up -d needs no internet once the image exists. docker compose up -d --build always needs internet, also when nothing changed. So after a git pull, rebuild before you leave.
  • The container restarts with Docker, for example after a reboot. docker compose down stops it.

Keys

Key Action
Space tap tempo (in tap mode it drives the clock, in beat mode 3+ taps set the tempo)
M mute the signal
T test pulse
L lock the tempo
D / H tempo × 2 / ÷ 2
P shift the phase of "every 2nd / 4th beat"

Detection modes

Mode For Latency
Beat tracker (default) rock, pop, metal, dance predictive: can fire early with a negative offset
Kick songs with a clear kick reactive, ~30 ms late
Onsets accents, stops reactive, busy
Tap anything, as a fallback predictive

Presets set the tempo range and band weights per genre (Rock / Pop, Metal / Punk, Dance, Ballad).

Commands

For development, enter the dev shell with nix develop (or direnv allow), then run just install.

Recipe Does
just dev Vite dev server at http://localhost:5173
just build static build
just serve serve the static build
just check biome, tsc, Vitest (the gate)
just e2e Playwright, headless, with fake audio devices
just eval F-measure table: every mode on every synthetic song

How it works

  • frontend/lightsync/src/dsp/ — the signal chain, pure TypeScript. It runs in an AudioWorklet in the app, and in Node for the tests and just eval.
    • Spectral flux in three bands, each band normalized by its running mean.
    • Beat tracker after BTrack (Stark 2011): cumulative beat strength signal, comb filter tempo estimate with a Viterbi tempo transition, and a phase check that pulls the tracker off the offbeat.
    • Pulse synth, silence gate, divider, offset, and a model of a cheap board for the dashboard.
  • src/audio/ — the Web Audio graph: sources, the worklet, one chain per output route. Extra output devices get their own AudioContext.
  • src/demo/ — synthetic songs with known beats (rock, metal, half-time, ballad, tempo change, break). The tests and the Demo source use them.
  • docs/agent-notes/2026-09-25-lightsync/ — problem, research, design and test concept.