Skip to content

About

A four-lane browser rhythm game with zero audio files — every note is synthesized with the Web Audio API, and the charts are derived from the music itself.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PULSEGRID

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.

PULSEGRID gameplay — notes falling down four neon lanes above a judgement line, with a 191 combo and a PERFECT judgement


How to play

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.


Tracks

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

How it works

The music is code

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.

The chart comes from the music

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.

The audio clock is the only clock

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.

Calibration

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.


Running locally

Any static file server will do — the game uses ES modules, so file:// will not work.

python3 -m http.server 8749

Then open http://localhost:8749.

Project layout

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

Browser support

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.

Licence

MIT.

About

A four-lane browser rhythm game with zero audio files — every note is synthesized with the Web Audio API, and the charts are derived from the music itself.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages