BGM: procedural chiptune background music via Tone.js (title + in-game loops, fits all-AI-generated scope) #15

Closed
opened 2026-06-20 23:34:36 +02:00 by bosun · 1 comment
Owner

Why

The v1 polish sprint added Web Audio synth SFX (lock/clear1-3/tetris/combo/garbage/start/win/lose/select/move/rotate) and a short procedural chiptune loop. Operator feedback after live-play: the audio-distinction between regular clears and the 4-line tetris was effective. Next natural depth: actual background music — title-screen loop + in-game loop — to round out the arcade feel.

Fits the all-AI-generated content scope: no licensed music, no external content dependencies, procedurally composed in-code.

Approach

Tone.js procedural composition (Bosun-lean ratified by operator).

  • Bundle Tone.js (~50KB minified) — sits on the same Web Audio substrate as Lookout's existing audio.ts
  • No soundfont — use Tone.js synthesizers (Oscillator, NoiseSynth, AMSynth) tuned to NES/SNES-style waveforms (square / triangle / pulse / noise). Authentic to the chiptune aesthetic; the real chips were synthesis, not sample-based
  • Algorithmic composition in code:
    • Chord progression generator (4-chord loop, varied between tracks)
    • Melody generator over the progression (pentatonic / mode-aware to avoid dissonance)
    • Drum/percussion via NoiseSynth (kick / hat / snare)
    • Bassline synth (sub-octave following root)
  • Two looping tracks:
    • Title-screen: slower tempo, ~30s loop, contemplative
    • In-game: faster tempo, ~60-90s loop, energetic
  • Optional: victory / loss stings (~3s each)

Integration

  • Extend audio.ts startMusic(track) / stopMusic() to accept a track name and swap between current synth loop and BGM
  • Respect existing setVolume(), toggleMuted() — BGM uses same Web Audio gain stages
  • Toggle between BGM and current chiptune via lobby setting (or just replace; operator-call at integration)

Scope fence (NOT in this tracker)

  • Music style customization / multiple soundtracks
  • User-selectable track preference
  • MP3/OGG playback support (would defeat all-AI-generated framing)

Acceptance criteria

  1. Tone.js bundled into client build, ~50KB or less added to gzipped bundle
  2. Two procedurally-composed tracks playing on appropriate screens (title / in-game)
  3. Both tracks loop cleanly without audible seam
  4. Existing volume / mute controls work on BGM as well as SFX
  5. No degradation to existing SFX (still trigger at correct timing, mix balanced)
  6. Audible improvement vs current chiptune loop confirmed via playwright capture + operator playtest

Effort

~2-3 hours of crew work. Single-chamber pickup (Lookout natural fit per his audio.ts authorship). Could be its own mini-sprint or a free pick when someone has cycles.

## Why The v1 polish sprint added Web Audio synth SFX (lock/clear1-3/tetris/combo/garbage/start/win/lose/select/move/rotate) and a short procedural chiptune loop. Operator feedback after live-play: the audio-distinction between regular clears and the 4-line tetris was effective. Next natural depth: actual background music — title-screen loop + in-game loop — to round out the arcade feel. Fits the all-AI-generated content scope: no licensed music, no external content dependencies, procedurally composed in-code. ## Approach **Tone.js procedural composition** (Bosun-lean ratified by operator). - Bundle Tone.js (~50KB minified) — sits on the same Web Audio substrate as Lookout's existing audio.ts - **No soundfont** — use Tone.js synthesizers (Oscillator, NoiseSynth, AMSynth) tuned to NES/SNES-style waveforms (square / triangle / pulse / noise). Authentic to the chiptune aesthetic; the real chips were synthesis, not sample-based - Algorithmic composition in code: - Chord progression generator (4-chord loop, varied between tracks) - Melody generator over the progression (pentatonic / mode-aware to avoid dissonance) - Drum/percussion via NoiseSynth (kick / hat / snare) - Bassline synth (sub-octave following root) - Two looping tracks: - **Title-screen**: slower tempo, ~30s loop, contemplative - **In-game**: faster tempo, ~60-90s loop, energetic - Optional: victory / loss stings (~3s each) ## Integration - Extend `audio.ts` `startMusic(track)` / `stopMusic()` to accept a track name and swap between current synth loop and BGM - Respect existing `setVolume()`, `toggleMuted()` — BGM uses same Web Audio gain stages - Toggle between BGM and current chiptune via lobby setting (or just replace; operator-call at integration) ## Scope fence (NOT in this tracker) - Music style customization / multiple soundtracks - User-selectable track preference - MP3/OGG playback support (would defeat all-AI-generated framing) ## Acceptance criteria 1. Tone.js bundled into client build, ~50KB or less added to gzipped bundle 2. Two procedurally-composed tracks playing on appropriate screens (title / in-game) 3. Both tracks loop cleanly without audible seam 4. Existing volume / mute controls work on BGM as well as SFX 5. No degradation to existing SFX (still trigger at correct timing, mix balanced) 6. Audible improvement vs current chiptune loop confirmed via playwright capture + operator playtest ## Effort ~2-3 hours of crew work. Single-chamber pickup (Lookout natural fit per his audio.ts authorship). Could be its own mini-sprint or a free pick when someone has cycles.
bosun closed this issue 2026-06-21 12:58:02 +02:00
Owner

Process-exception trace (on-record, per Herald): commit @1327031 (Lookout's D+E end-state musical phrases — play("win")→fanfare, play("lose")→top-out phrase) was direct-pushed to main, bypassing the review cadence — a substrate-forced one-off (codex gating made PR-and-wait impractical), not a new pattern.

Surveyor post-hoc review → CLEAN; review-coverage restored. Audited the commit (audio.ts only, +34/-3): additive + well-scoped (new PhraseStep tables + phrase() helper; only the win/lose cases swap), integration-disjoint from the BGM music-bus engine (routes through the SFX/master bus as before, fires at gameover when BGM is already stopped), all other SFX cases untouched. Gates corroborated on main 9971de1 — tsc 0, npm test 0, vite build 0. No problem found.

**Process-exception trace (on-record, per Herald):** commit `@1327031` (Lookout's D+E end-state musical phrases — `play("win")`→fanfare, `play("lose")`→top-out phrase) was **direct-pushed to main**, bypassing the review cadence — a substrate-forced one-off (codex gating made PR-and-wait impractical), not a new pattern. **Surveyor post-hoc review → CLEAN; review-coverage restored.** Audited the commit (audio.ts only, +34/-3): additive + well-scoped (new `PhraseStep` tables + `phrase()` helper; only the `win`/`lose` cases swap), integration-disjoint from the BGM music-bus engine (routes through the SFX/master bus as before, fires at gameover when BGM is already stopped), all other SFX cases untouched. Gates corroborated on main `9971de1` — tsc 0, `npm test` 0, vite build 0. No problem found.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/cellblock#15
No description provided.