Add pause, remap, speed/volume, reorder genres, full README
UI improvements: - Start button now sticky at bottom of viewport — always visible without scrolling, with Remap Pads button alongside - Pause/Resume: press Escape or pause button in HUD. Suspends AudioContext to freeze all scheduled audio perfectly. - Back to Menu button on pause overlay Gamepad remap: - Remap Pads button walks through each pad (Red, Yellow, Blue, Green, Kick) and captures the button press from any USB drum kit - Mapping saved to localStorage, survives page reload - Default mapping still Wii Rock Band Speed & Volume sliders: - Speed: 50%-150%, affects BPM for drum patterns and note/bg timing for classical pieces. Slow down for practice. - Volume: 0%-150% master volume, affects both drums and synth - Both on menu Settings card Genre tab reorder: - Synth genres now appear first: Singalong, Christmas, Folk, Classical, Video Games, Chiptune Originals - Drum pattern genres after: Rock, Metal, Jazz, etc. - Default genre changed from Rock to Singalong README: Complete song list for all 65 synth pieces + 120 drum patterns, USB drum kit compatibility table, feature list. https://claude.ai/code/session_01GdFrFqAe9e2DaJ86jZPEZE
This commit is contained in:
@@ -2,32 +2,138 @@
|
||||
|
||||
A browser-based drum rhythm game. Notes scroll down the screen and you hit the matching drum pad in time — like Rock Band, but it runs entirely in your browser with no server, no dependencies, no install. Just open `index.html`.
|
||||
|
||||
Works with **keyboard**, **Wii Rock Band drum kit via USB**, or any gamepad.
|
||||
Works with **keyboard**, **Wii Rock Band drums**, **Xbox/PS Rock Band drums**, **Guitar Hero drums**, or any USB gamepad. Includes a **Remap Pads** button to configure any controller.
|
||||
|
||||
## How to Play
|
||||
|
||||
1. Open `index.html` in Chrome or Edge
|
||||
2. Pick a genre and rhythm (or a classical piece)
|
||||
3. Click **Start**
|
||||
4. Hit the pads when notes reach the line:
|
||||
2. Pick a genre and song
|
||||
3. Adjust speed/volume sliders if desired
|
||||
4. Click **Start**
|
||||
5. Hit the pads when notes reach the line:
|
||||
- **F** = Red (Tom)
|
||||
- **G** = Yellow (Snare)
|
||||
- **H** = Blue (Hi-hat)
|
||||
- **J** = Green (Tom)
|
||||
- **Space** = Kick (Bass drum)
|
||||
- **Escape** = Pause
|
||||
|
||||
## Features
|
||||
|
||||
- **9 Genres**: Rock, Metal, Jazz, Hip Hop, Funk, Latin, Electronic, Country, Classical
|
||||
- **146 Rhythms**: 120 drum patterns + 26 classical orchestral pieces
|
||||
- **Classical Mode**: Synthesized orchestra plays automatically while you hit the melody notes on the pads. Choose between drum sounds or orchestral hit sounds.
|
||||
- **26 Classical Pieces**: Beethoven's 5th, Hall of the Mountain King, O Fortuna, Ride of the Valkyries, Toccata & Fugue, Bolero, Also Sprach Zarathustra (2001), The Blue Danube, Flight of the Bumblebee, Fur Elise, Canon in D, Ode to Joy, 1812 Overture, and more
|
||||
- **MP3 Jam Mode**: Load your own music and drum along freely
|
||||
- **Multiplayer**: Take turns on the same rhythm, scores compared at the end
|
||||
- **Pad Toggles**: Turn pads on/off to adjust difficulty — start with kick+snare, add more as you improve
|
||||
- **Leaderboard**: Scores saved locally with player name, accuracy, and streak
|
||||
- **Preview**: Hear any rhythm before playing
|
||||
- **Gamepad Support**: Wii Rock Band drums via USB, auto-detected
|
||||
- **14 Genres** across two modes: synth orchestra songs + drum beat patterns
|
||||
- **65 synth pieces** with full synthesized orchestra background (48 minutes)
|
||||
- **120 drum patterns** across 8 genres
|
||||
- **Drums or Orchestra mode** — choose whether pad hits play drum sounds or the actual melody note
|
||||
- **Speed control** — slow down to 50% for practice or speed up to 150%
|
||||
- **Volume control** — adjustable master volume
|
||||
- **Pause/Resume** — press Escape or the pause button mid-song
|
||||
- **Gamepad Remap** — configure any USB drum kit with the Remap Pads button
|
||||
- **Multiplayer** — take turns, scores compared at the end
|
||||
- **Pad Toggles** — turn pads on/off to adjust difficulty
|
||||
- **Preview** — hear any song before playing
|
||||
- **Leaderboard** — scores saved locally
|
||||
|
||||
## Song List
|
||||
|
||||
### Singalong (10 songs)
|
||||
- Drunken Sailor (Sea Shanty)
|
||||
- Molly Malone (Irish, 1880s)
|
||||
- Whiskey in the Jar (Irish Traditional)
|
||||
- The Parting Glass (Irish/Scots Traditional)
|
||||
- Amazing Grace (1779)
|
||||
- Auld Lang Syne (1788)
|
||||
- When the Saints Go Marching In (Spiritual)
|
||||
- Oh My Darling, Clementine (1884)
|
||||
- Scarborough Fair (English Medieval)
|
||||
- My Bonnie Lies Over the Ocean (Scottish, 1881)
|
||||
|
||||
### Christmas (10 songs)
|
||||
- Jingle Bells (1857)
|
||||
- Silent Night (1818)
|
||||
- Deck the Halls (1862)
|
||||
- We Wish You a Merry Christmas (1500s)
|
||||
- O Christmas Tree (1824)
|
||||
- The First Noel (1823)
|
||||
- Joy to the World (1719)
|
||||
- Away in a Manger (1885)
|
||||
- 12 Days of Christmas (1780)
|
||||
- Hark the Herald Angels Sing (1840)
|
||||
|
||||
### Folk (8 songs)
|
||||
- Oh! Susanna (Stephen Foster, 1848)
|
||||
- Greensleeves (English, 1580)
|
||||
- Camptown Races (Stephen Foster, 1850)
|
||||
- Red River Valley (American, 1870s)
|
||||
- Home on the Range (American, 1870s)
|
||||
- Yankee Doodle (American, 1770s)
|
||||
- Turkey in the Straw (American, 1820s)
|
||||
- Danny Boy (Irish, 1910s)
|
||||
|
||||
### Classical (26 pieces)
|
||||
- Beethoven's 5th (Opening)
|
||||
- Hall of the Mountain King (Grieg)
|
||||
- O Fortuna (Orff)
|
||||
- Ride of the Valkyries (Wagner)
|
||||
- Mars, Bringer of War (Holst)
|
||||
- Toccata & Fugue in D Minor (Bach)
|
||||
- William Tell Overture — Gallop (Rossini)
|
||||
- Bolero (Ravel)
|
||||
- Also Sprach Zarathustra (R. Strauss) — 2001: A Space Odyssey
|
||||
- Barber of Seville Overture (Rossini)
|
||||
- The Blue Danube (J. Strauss II) — 2001: A Space Odyssey
|
||||
- Sabre Dance (Khachaturian)
|
||||
- Night on Bald Mountain (Mussorgsky) — Fantasia
|
||||
- Sorcerer's Apprentice (Dukas) — Fantasia
|
||||
- Swan Lake Theme (Tchaikovsky)
|
||||
- Morning Mood (Grieg)
|
||||
- Flight of the Bumblebee (Rimsky-Korsakov)
|
||||
- Dance of the Sugar Plum Fairy (Tchaikovsky)
|
||||
- 1812 Overture Finale (Tchaikovsky)
|
||||
- Habanera from Carmen (Bizet)
|
||||
- Spring from Four Seasons (Vivaldi)
|
||||
- Fur Elise (Beethoven)
|
||||
- Canon in D (Pachelbel)
|
||||
- Hungarian Dance No. 5 (Brahms)
|
||||
- Ode to Joy (Beethoven)
|
||||
- William Tell Overture — Storm (Rossini)
|
||||
|
||||
### Video Games (5 pieces)
|
||||
- Korobeiniki / Tetris Theme (Russian Folk, 1861)
|
||||
- Turkish March (Mozart)
|
||||
- Hungarian Rhapsody No. 2 (Liszt)
|
||||
- Can-Can (Offenbach)
|
||||
- Kalinka (Russian Folk, 1860)
|
||||
|
||||
### Chiptune Originals (6 pieces, composed by Claude.ai)
|
||||
- Pixel Quest (Platformer)
|
||||
- Starfield (Space Shooter)
|
||||
- Dragon's Keep (RPG Overworld)
|
||||
- Turbo Dash (Racing)
|
||||
- Dungeon Crawl (Dark Adventure)
|
||||
- Victory Fanfare (Boss Defeated)
|
||||
|
||||
### Drum Patterns (120 patterns)
|
||||
- **Rock** (15): Basic Rock, Classic Rock, Punk Rock, Arena Rock, and more
|
||||
- **Metal** (15): Double Bass, Thrash, Doom Metal, Blast Beat, and more
|
||||
- **Jazz** (15): Jazz Ride, Bebop, Bossa Nova, Fusion, and more
|
||||
- **Hip Hop** (15): Boom Bap, Trap, Lo-Fi, Drill, and more
|
||||
- **Funk** (15): Classic Funk, Ghost Notes, P-Funk, Afrobeat, and more
|
||||
- **Latin** (15): Samba, Salsa, Reggaeton, Cumbia, and more
|
||||
- **Electronic** (15): Four on the Floor, Breakbeat, DnB, Dubstep, and more
|
||||
- **Country** (15): Two-Step, Bluegrass, Rockabilly, Gospel, and more
|
||||
|
||||
## USB Drum Kit Compatibility
|
||||
|
||||
The Gamepad API works with any USB controller. Use the **Remap Pads** button to configure your kit.
|
||||
|
||||
| Kit | Connection |
|
||||
|-----|-----------|
|
||||
| Wii Rock Band drums | USB dongle (default mapping) |
|
||||
| Xbox 360 Rock Band drums | USB wired |
|
||||
| PS3/PS4 Rock Band drums | USB |
|
||||
| Guitar Hero drums | USB |
|
||||
| Roland/Alesis/Donner | USB-MIDI adapter |
|
||||
| Any USB gamepad | Remap to configure |
|
||||
|
||||
## Synthesizer
|
||||
|
||||
@@ -39,12 +145,13 @@ All sounds are generated in real-time using the Web Audio API — no audio files
|
||||
| Snare | Highpass noise burst + 180Hz tone |
|
||||
| Hi-hat | Highpass noise burst, very short |
|
||||
| Toms | Sine oscillator pitch sweep |
|
||||
| Strings | Sawtooth oscillator + lowpass filter |
|
||||
| Brass | Square oscillator + lowpass filter |
|
||||
| Strings | Sawtooth + lowpass filter |
|
||||
| Brass | Square + lowpass filter |
|
||||
| Woodwind | Triangle oscillator |
|
||||
| Bass | Sine oscillator, low frequency |
|
||||
| Timpani | Sine frequency sweep |
|
||||
| Choir | Multiple detuned sine oscillators |
|
||||
| Chiptune | Square wave with pulse bass |
|
||||
|
||||
## Tech
|
||||
|
||||
@@ -53,7 +160,7 @@ All sounds are generated in real-time using the Web Audio API — no audio files
|
||||
- Web Audio API for all sound synthesis
|
||||
- Canvas 2D for the note highway
|
||||
- Gamepad API for drum kit input
|
||||
- localStorage for score persistence
|
||||
- localStorage for scores and gamepad mapping
|
||||
- Works on Windows, Linux, and Mac
|
||||
|
||||
## License
|
||||
|
||||
+104
-23
@@ -45,6 +45,14 @@ canvas{display:block;}
|
||||
.stbl{width:100%;max-width:600px;border-collapse:collapse;}
|
||||
.stbl th{text-align:left;padding:7px 9px;border-bottom:1px solid #222;font-size:11px;opacity:.5;}
|
||||
.stbl td{padding:7px 9px;border-bottom:1px solid #111;font-size:12px;}
|
||||
.sticky-start{position:sticky;bottom:0;padding:12px 0 4px;background:linear-gradient(transparent,#0a0a0f 35%);z-index:10;display:flex;gap:8px;justify-content:center;align-items:center;width:100%;max-width:700px;}
|
||||
#pause-overlay{display:none;position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:20;flex-direction:column;align-items:center;justify-content:center;gap:16px;}
|
||||
#pause-overlay.show{display:flex;}
|
||||
#pause-overlay h2{font-size:28px;color:#fff;}
|
||||
#remap-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:30;flex-direction:column;align-items:center;justify-content:center;gap:16px;}
|
||||
#remap-overlay.show{display:flex;}
|
||||
#remap-prompt{font-size:20px;font-weight:bold;text-align:center;}
|
||||
#remap-status{font-size:13px;opacity:.5;text-align:center;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -103,13 +111,25 @@ canvas{display:block;}
|
||||
<button class="smbtn" style="margin-top:8px" onclick="goScores()">View All</button>
|
||||
</div>
|
||||
|
||||
<button class="bigbtn" onclick="startGame()">▶ Start</button>
|
||||
<div class="card">
|
||||
<h2>Settings</h2>
|
||||
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:center;">
|
||||
<label style="font-size:12px;display:flex;align-items:center;gap:6px;">Speed: <input type="range" min="50" max="150" value="100" step="5" id="speed-slider" oninput="gameSpeed=this.value/100;document.getElementById('speed-val').textContent=this.value+'%'"><span id="speed-val" style="font-size:11px;min-width:32px">100%</span></label>
|
||||
<label style="font-size:12px;display:flex;align-items:center;gap:6px;">Volume: <input type="range" min="0" max="150" value="100" step="5" id="vol-slider" oninput="masterVolume=this.value/100;document.getElementById('vol-val').textContent=this.value+'%'"><span id="vol-val" style="font-size:11px;min-width:32px">100%</span></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sticky-start">
|
||||
<button class="smbtn" onclick="startRemap()">🎮 Remap Pads</button>
|
||||
<button class="bigbtn" onclick="startGame()">▶ Start</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GAME -->
|
||||
<div class="screen" id="sg" style="padding:0;">
|
||||
<div id="hud">
|
||||
<button class="smbtn" onclick="goMenu()">← Menu</button>
|
||||
<button class="smbtn" onclick="togglePause()" id="pause-btn">⏸</button>
|
||||
<div id="hud-title">-</div>
|
||||
<div class="hstat"><span>Score</span><b id="hs">0</b></div>
|
||||
<div class="hstat"><span>Streak</span><b id="hst">0</b></div>
|
||||
@@ -117,9 +137,15 @@ canvas{display:block;}
|
||||
<div class="hstat"><span>Player</span><b id="hp">-</b></div>
|
||||
<span id="gpd" style="font-size:10px;opacity:.4">No gamepad</span>
|
||||
</div>
|
||||
<div id="cwrap"><canvas id="c"></canvas><div id="msg"></div><div id="cdown"></div><div id="pbanner"></div></div>
|
||||
<div id="cwrap">
|
||||
<canvas id="c"></canvas><div id="msg"></div><div id="cdown"></div><div id="pbanner"></div>
|
||||
<div id="pause-overlay"><h2>PAUSED</h2><button class="bigbtn" onclick="togglePause()">▶ Resume</button><button class="smbtn" onclick="goMenu()">← Back to Menu</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- REMAP -->
|
||||
<div id="remap-overlay"><div id="remap-prompt"></div><div id="remap-status"></div><button class="smbtn red" onclick="cancelRemap()">Cancel</button></div>
|
||||
|
||||
<!-- SCORES -->
|
||||
<div class="screen" id="ss" style="padding:16px;">
|
||||
<div style="width:100%;max-width:600px">
|
||||
@@ -544,7 +570,7 @@ const NT={C3:130.81,D3:146.83,Eb3:155.56,E3:164.81,F3:174.61,G3:196.00,Ab3:207.6
|
||||
|
||||
function playSynth(ac,freq,t,dur,inst,vol,arr){
|
||||
if(t<ac.currentTime-0.1)return;
|
||||
var v=(vol||0.25)*3;
|
||||
var v=(vol||0.25)*3*masterVolume;
|
||||
if(inst==='strings'){
|
||||
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||||
o.type='sawtooth';o.frequency.value=freq;
|
||||
@@ -1888,7 +1914,7 @@ const SINGALONG_PIECES=(function(){
|
||||
return [drunken,molly,whiskey,parting,amazing,auld,saints,clementine,scarborough,bonnie];
|
||||
})();
|
||||
|
||||
var SYNTH_GENRES={"Classical":CLASSICAL_PIECES,"Video Games":VIDEOGAME_PIECES,"Chiptune Originals":CHIPTUNE_PIECES,"Folk":FOLK_PIECES,"Christmas":CHRISTMAS_PIECES,"Singalong":SINGALONG_PIECES};
|
||||
var SYNTH_GENRES={"Singalong":SINGALONG_PIECES,"Christmas":CHRISTMAS_PIECES,"Folk":FOLK_PIECES,"Classical":CLASSICAL_PIECES,"Video Games":VIDEOGAME_PIECES,"Chiptune Originals":CHIPTUNE_PIECES};
|
||||
|
||||
function buildClassicalNotes(piece){
|
||||
var raw=piece.melody.filter(function(m){return activePads[m.l];})
|
||||
@@ -1899,7 +1925,7 @@ function buildClassicalNotes(piece){
|
||||
var b=piece.bg[i];
|
||||
if(Math.abs(b.t-m.t)<0.01&&b.i!=='bass'&&b.i!=='timpani'){freq=b.f;break;}
|
||||
}
|
||||
return{time:m.t,lane:m.l,freq:freq,hit:false,missed:false};
|
||||
return{time:m.t/gameSpeed,lane:m.l,freq:freq,hit:false,missed:false};
|
||||
})
|
||||
.sort(function(a,b){return a.time-b.time;});
|
||||
// Enforce minimum gap between notes so stacked/simultaneous notes are playable
|
||||
@@ -1921,13 +1947,13 @@ function beginClassical(){
|
||||
gameStartTime=ac.currentTime+0.15;
|
||||
for(var i=0;i<piece.bg.length;i++){
|
||||
var n=piece.bg[i];
|
||||
playSynth(ac,n.f,gameStartTime+n.t,n.d,n.i,n.v*1.5,gameSynthNodes);
|
||||
playSynth(ac,n.f,gameStartTime+n.t/gameSpeed,n.d/gameSpeed,n.i,n.v*1.5,gameSynthNodes);
|
||||
}
|
||||
setTimeout(function(){if(playing)endTurn();},piece.duration*1000+800);
|
||||
setTimeout(function(){if(playing)endTurn();},(piece.duration/gameSpeed)*1000+800);
|
||||
}
|
||||
|
||||
// ── STATE ──────────────────────────────────────────────
|
||||
let mode="builtin",curGenre=Object.keys(GENRES)[0],curIdx=0;
|
||||
let mode="builtin",curGenre="Singalong",curIdx=0;
|
||||
let players=[{name:"Player 1"}];
|
||||
let savedScores=lsLoad();
|
||||
let previewAc=null,previewNodes=[],previewing=false;
|
||||
@@ -1936,12 +1962,16 @@ let notes=[],gameStartTime=null,playing=false;
|
||||
let gamePlayers=[],activePIdx=0;
|
||||
let cScore=0,cStreak=0,cTotal=0,cGood=0,cMaxStreak=0;
|
||||
let laneFlash=[0,0,0,0,0],msgTmr=null,prevBtns={},gameSynthNodes=[],classicalHitMode='orchestra';
|
||||
let paused=false,pauseTime=0;
|
||||
var gpMap=JSON.parse(localStorage.getItem("drgameGpMap")||"null")||{0:2,1:3,2:0,3:1,4:4,8:4};
|
||||
var remapping=false,remapStep=0,remapResult={};
|
||||
var gameSpeed=1.0,masterVolume=1.0;
|
||||
let gameRhythm=null;
|
||||
const HIT_WIN=0.17,SPEED=260;
|
||||
|
||||
// ── SCREEN ─────────────────────────────────────────────
|
||||
function show(id){document.querySelectorAll(".screen").forEach(s=>s.classList.remove("active"));document.getElementById(id).classList.add("active");}
|
||||
function goMenu(){stopGame();stopPreview();show("sm");refreshMiniLB();}
|
||||
function goMenu(){if(paused){paused=false;getAc().resume();document.getElementById("pause-overlay").classList.remove("show");}stopGame();stopPreview();show("sm");refreshMiniLB();}
|
||||
function goScores(){renderScores();show("ss");}
|
||||
|
||||
// ── PAD TOGGLES ────────────────────────────────────────
|
||||
@@ -1969,20 +1999,21 @@ function updateHitModeVisibility(){
|
||||
// ── MENU ───────────────────────────────────────────────
|
||||
function buildMenu(){
|
||||
const gt=document.getElementById("gtabs");gt.innerHTML="";
|
||||
Object.keys(GENRES).forEach(g=>{
|
||||
const b=document.createElement("button");
|
||||
b.className="gtab"+(g===curGenre?" active":"");
|
||||
b.textContent=g;b.onclick=()=>{curGenre=g;curIdx=0;stopPreview();buildMenu();};
|
||||
gt.appendChild(b);
|
||||
});
|
||||
// Synth genre tabs (Classical, Video Games, Chiptune Originals, Folk)
|
||||
var synthIcons={"Classical":"\uD83C\uDFBB","Video Games":"\uD83C\uDFAE","Chiptune Originals":"\uD83D\uDC7E","Folk":"\uD83C\uDFB6","Christmas":"\uD83C\uDF84","Singalong":"\uD83C\uDFB5"};
|
||||
// Synth genres first (songs people know)
|
||||
var synthIcons={"Singalong":"\uD83C\uDFB5","Christmas":"\uD83C\uDF84","Folk":"\uD83C\uDFB6","Classical":"\uD83C\uDFBB","Video Games":"\uD83C\uDFAE","Chiptune Originals":"\uD83D\uDC7E"};
|
||||
Object.keys(SYNTH_GENRES).forEach(function(sg){
|
||||
var cb=document.createElement("button");
|
||||
cb.className="gtab"+(curGenre===sg?" active":"");
|
||||
cb.textContent=synthIcons[sg]+" "+sg;cb.onclick=function(){curGenre=sg;curIdx=0;stopPreview();buildMenu();};
|
||||
gt.appendChild(cb);
|
||||
});
|
||||
// Drum pattern genres
|
||||
Object.keys(GENRES).forEach(g=>{
|
||||
const b=document.createElement("button");
|
||||
b.className="gtab"+(g===curGenre?" active":"");
|
||||
b.textContent=g;b.onclick=()=>{curGenre=g;curIdx=0;stopPreview();buildMenu();};
|
||||
gt.appendChild(b);
|
||||
});
|
||||
const rg=document.getElementById("rgrid");rg.innerHTML="";
|
||||
(SYNTH_GENRES[curGenre]||GENRES[curGenre]).forEach((r,i)=>{
|
||||
const b=document.createElement("button");
|
||||
@@ -2101,10 +2132,10 @@ function stopGame(){playing=false;if(mp3Src){try{mp3Src.stop();}catch(e){}mp3Src
|
||||
function resetStats(){cScore=0;cStreak=0;cTotal=0;cGood=0;cMaxStreak=0;}
|
||||
|
||||
function buildNotes(r){
|
||||
const bs=60/r.bpm,bars=r.bars||8,res=[];
|
||||
const bs=60/r.bpm/gameSpeed,bars=r.bars||8,res=[];
|
||||
for(let bar=0;bar<bars;bar++)
|
||||
for(const p of r.p)
|
||||
if(activePads[p.l]) // only include active lanes
|
||||
if(activePads[p.l])
|
||||
res.push({time:(bar*4+p.t)*bs,lane:p.l,hit:false,missed:false});
|
||||
return res.sort((a,b)=>a.time-b.time);
|
||||
}
|
||||
@@ -2117,9 +2148,8 @@ function countdown(cb){
|
||||
|
||||
function beginBuiltin(){
|
||||
playing=true;
|
||||
const ac=getAc(),r=gameRhythm,bs=60/r.bpm,bars=r.bars||8;
|
||||
const ac=getAc(),r=gameRhythm,bs=60/r.bpm/gameSpeed,bars=r.bars||8;
|
||||
gameStartTime=ac.currentTime;
|
||||
// Schedule ALL drum hits as audio — this IS the music
|
||||
for(let bar=0;bar<bars;bar++)
|
||||
for(const p of r.p)
|
||||
playDrumAt(ac,p.l,gameStartTime+bar*4*bs+p.t*bs,4,[]);
|
||||
@@ -2331,6 +2361,7 @@ draw();
|
||||
|
||||
// ── DRUM SOUNDS ────────────────────────────────────────
|
||||
function playDrumAt(ac,lane,t,vol,arr){
|
||||
vol=vol*masterVolume;
|
||||
const type=LANES[lane].type;
|
||||
function mk(n){if(arr)arr.push(n);}
|
||||
if(type==="kick"||type==="bass"){
|
||||
@@ -2367,8 +2398,58 @@ function playDrumAt(ac,lane,t,vol,arr){
|
||||
}
|
||||
}
|
||||
|
||||
// ── PAUSE ─────────────────────────────────────────────
|
||||
function togglePause(){
|
||||
if(!playing&&!paused)return;
|
||||
var ac=getAc();
|
||||
if(!paused){
|
||||
paused=true;playing=false;pauseTime=ac.currentTime;
|
||||
ac.suspend();
|
||||
document.getElementById("pause-overlay").classList.add("show");
|
||||
} else {
|
||||
paused=false;playing=true;
|
||||
ac.resume();
|
||||
document.getElementById("pause-overlay").classList.remove("show");
|
||||
}
|
||||
}
|
||||
|
||||
// ── REMAP ─────────────────────────────────────────────
|
||||
var remapLanes=[{l:0,name:"RED",color:"#e53e3e"},{l:1,name:"YELLOW",color:"#d69e2e"},{l:2,name:"BLUE",color:"#3182ce"},{l:3,name:"GREEN",color:"#38a169"},{l:4,name:"KICK",color:"#805ad5"}];
|
||||
function startRemap(){
|
||||
remapping=true;remapStep=0;remapResult={};
|
||||
document.getElementById("remap-overlay").classList.add("show");
|
||||
showRemapStep();
|
||||
}
|
||||
function cancelRemap(){
|
||||
remapping=false;document.getElementById("remap-overlay").classList.remove("show");
|
||||
}
|
||||
function showRemapStep(){
|
||||
if(remapStep>=remapLanes.length){
|
||||
// Done — save mapping
|
||||
gpMap={};
|
||||
for(var k in remapResult)gpMap[k]=remapResult[k];
|
||||
localStorage.setItem("drgameGpMap",JSON.stringify(gpMap));
|
||||
remapping=false;
|
||||
document.getElementById("remap-overlay").classList.remove("show");
|
||||
document.getElementById("remap-prompt").textContent="";
|
||||
return;
|
||||
}
|
||||
var rl=remapLanes[remapStep];
|
||||
document.getElementById("remap-prompt").innerHTML="Hit your <span style='color:"+rl.color+";font-size:24px'>"+rl.name+"</span> pad";
|
||||
document.getElementById("remap-status").textContent="Step "+(remapStep+1)+" of "+remapLanes.length+(remapStep===4?" (or press foot pedal)":"");
|
||||
}
|
||||
function handleRemapButton(bi){
|
||||
if(!remapping)return false;
|
||||
remapResult[bi]=remapLanes[remapStep].l;
|
||||
remapStep++;
|
||||
showRemapStep();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── INPUT ──────────────────────────────────────────────
|
||||
window.addEventListener("keydown",e=>{
|
||||
if(e.key==="Escape"){e.preventDefault();togglePause();return;}
|
||||
if(paused)return;
|
||||
const i=LANES.findIndex(l=>l.key===e.key.toLowerCase()||(l.key===" "&&e.key===" "));
|
||||
if(i>=0){e.preventDefault();triggerPad(i);}
|
||||
});
|
||||
@@ -2380,8 +2461,8 @@ function pollGP(){
|
||||
document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20);
|
||||
gp.buttons.forEach((btn,bi)=>{
|
||||
if(btn.pressed&&!prevBtns[bi]){
|
||||
const m={0:2,1:3,2:0,3:1,4:4,8:4};
|
||||
if(m[bi]!==undefined)triggerPad(m[bi]);
|
||||
if(remapping){handleRemapButton(bi);}
|
||||
else if(!paused&&gpMap[bi]!==undefined)triggerPad(gpMap[bi]);
|
||||
}
|
||||
prevBtns[bi]=btn.pressed;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user