A four-lane rhythm game that runs entirely in the browser — and ships without a single audio file. Every kick, pad and lead note is synthesized with the Web Audio API at run time, and the note charts are derived from that same music, so what you are hitting is the melody you are hearing.
▶ Play it: https://ctbot000.github.io/rhythm-game/
No install, no build step, no dependencies. Four tracks, three difficulties each.
Notes fall down four lanes. Hit the key for a lane at the moment its note crosses the glowing judgement line.
| Lanes | D F J K — or ← ↓ ↑ →, or tap the lanes on a touch screen |
| Pause | Esc |
| Restart | R |
| Judgement | Window | Weight |
|---|---|---|
PERFECT |
±45 ms | 100% |
GREAT |
±90 ms | 72% |
GOOD |
±140 ms | 36% |
MISS |
beyond ±140 ms | 0% |
Hold notes (the long bars) are pressed at the head and released after the tail. Combo multiplies every note you score, rising from ×1 to ×2 over a 200-note chain — so a long clean run is worth far more than the same notes hit scrappily. The life gauge drains on misses; empty it and the run ends, unless you turn on No Fail.
A flawless run scores exactly 1,000,000, on every chart, which makes scores comparable across songs and difficulties.
| Track | BPM | Key | Length | Easy / Normal / Hard |
|---|---|---|---|---|
| Cherry Byte | 100 | C major | 1:26 | 1 / 3 / 6 |
| Neon Drive | 128 | A minor | 1:15 | 2 / 5 / 8 |
| Glass Rain | 84 | F major | 1:43 | 1 / 4 / 7 |
| Midnight Protocol | 152 | D minor | 1:03 | 4 / 7 / 11 |
There is no .mp3 anywhere in this repository. Songs are written as tracker
patterns in js/songs.js, in a notation compact enough to read
directly:
leadA: pat(
'E5 . D5 C5 . A4 . C5 | D5 - C5 A4 . . . . | ' +
'C5 . D5 E5 . G5 . E5 | D5 - - . B4 . . .',
{ div: 2, part: 'lead', gain: 0.21 }),div: 2 means two steps per beat (eighth notes); . is a rest, - holds the
previous note, | is a bar divider that the parser ignores, and A3+C4+E4 is a
chord. Patterns are assembled into sections, flattened to an absolute-time event
list, and played by a look-ahead scheduler in js/audio.js —
detuned saw leads, a saw bass with a sine sub, three-oscillator pads, and drums
built from a noise buffer and a pitch-swept sine.
js/chart.js turns those events into playable notes rather than
having a chart authored alongside the audio. The melody can therefore never drift
out of sync with what you hit.
Parts are placed in priority order — lead first, then kick and snare, then the arpeggio as filler — and a lower-priority part is only allowed in where it does not crowd what is already there. Placement respects a minimum gap (expressed in beats, so it scales with tempo), a per-lane gap, a rolling notes-per-second cap, and a maximum chord size. Pitch decides the lane: each part's own pitch distribution is split into quartiles, so a rising melody reads as a rising run across the board and no single lane collects the whole song.
A final pass fills genuine holes — intros and breakdowns drop the melody entirely, which would otherwise leave ten seconds with nothing to hit — and promotes sustained melody notes to holds, clearing filler out of the lane so that a hold is always physically sustainable.
Song position is read from AudioContext.currentTime, never from
requestAnimationFrame. A dropped frame moves the picture, never the music.
outputLatency is subtracted so the visuals line up with what actually reaches
the speakers, and key presses are timestamped against a per-frame bridge between
the audio clock and performance.now() for sub-frame accuracy.
Frame time is used for exactly one thing: animation lifetimes, so that a backgrounded tab cannot freeze a hit effect on screen.
If your machine runs the audio late or early, Settings → Calibrate plays a metronome, takes sixteen taps, discards a four-tap warm-up, and uses the median error to suggest an offset.
Any static file server will do — the game uses ES modules, so file:// will not
work.
python3 -m http.server 8749Then open http://localhost:8749.
index.html markup for every screen
css/style.css all styling; dark-only by design
js/audio.js synth voices, drum kit, look-ahead scheduler
js/songs.js the four tracks, as tracker patterns
js/chart.js music -> playable notes; judgement windows and scoring
js/game.js clock, input, judgement, canvas rendering
js/storage.js settings and personal bests in localStorage
js/main.js screens, settings, calibration, results
Any current browser with Web Audio and ES modules — Chrome, Firefox, Safari and Edge, on desktop and mobile. Browsers block audio until you interact with the page, so the first sound arrives when you press PLAY.
MIT.
