diff --git a/index.html b/index.html
index e6c926e..20bc3f5 100644
--- a/index.html
+++ b/index.html
@@ -110,7 +110,7 @@ canvas{display:block;}
No scores yet.
-
+
@@ -1984,7 +1984,142 @@ const SINGALONG_PIECES=(function(){
return [drunken,wellerman,molly,whiskey,parting,leavejohnny,blowman,randy,southaus,chariot,hauljoe,spanish,skye,bella,amazing,auld,saints,clementine,scarborough,bonnie];
})();
-var SYNTH_GENRES={"Singalong":SINGALONG_PIECES,"Christmas":CHRISTMAS_PIECES,"Folk":FOLK_PIECES,"Classical":CLASSICAL_PIECES,"Video Games":VIDEOGAME_PIECES,"Chiptune Originals":CHIPTUNE_PIECES};
+// ── SPIRITUALS (traditional African American songs) ──
+const SPIRITUAL_PIECES=(function(){
+ var C3=NT.C3,D3=NT.D3,E3=NT.E3,F3=NT.F3,G3=NT.G3,A3=NT.A3,Bb3=NT.Bb3,B3=NT.B3,
+ C4=NT.C4,D4=NT.D4,Eb4=NT.Eb4,E4=NT.E4,F4=NT.F4,G4=NT.G4,A4=NT.A4,Bb4=NT.Bb4,B4=NT.B4,
+ C5=NT.C5,D5=NT.D5,E5=NT.E5;
+ function spirit(name,origin,bpm,theme,lanes,durations,passes,root){
+ var bg=[],mel=[],t=0,q=0.25;
+ for(var rep=0;rep0)bg.push({t:t,f:f/2,d:d*0.85,i:'bass',v:v*0.3});
+ if(rep>Math.floor(passes*0.5))bg.push({t:t,f:f,d:d*0.85,i:'choir',v:v*0.35});
+ if(rep>Math.floor(passes*0.7))bg.push({t:t,f:f,d:d*0.85,i:'brass',v:v*0.3});
+ mel.push({t:t,l:lanes[i]});t+=d;
+ });
+ t+=0.4;
+ }
+ var r=root||theme[0];
+ [r,r*5/4,r*3/2,r*2].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'choir',v:0.5});bg.push({t:t,f:f,d:2.5,i:'strings',v:0.35});});
+ bg.push({t:t,f:r/2,d:2.0,i:'timpani',v:0.5});mel.push({t:t,l:4});
+ return {n:name,composer:origin,bpm:bpm,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
+ }
+ var swing=spirit("Swing Low, Sweet Chariot","Spiritual (1862)",80,
+ [G4,C5,C5,E5,D5,C5,A4,G4,E4,G4,A4,G4,E4,C4,D4,E4,G4,C5,C5,E5,D5,C5,A4,G4,E4,G4,A4,C5,G4],
+ [3,0,0,2,1,0,0,3,2,3,0,3,2,0,1,2,3,0,0,2,1,0,0,3,2,3,0,0,3],
+ [2,2,1,2,1,1,2,2,1,1,1,2,1,2,1,1,2,2,1,2,1,1,2,2,1,1,2,1,4],
+ 4,C4);
+ var godown=spirit("Go Down Moses","Spiritual (Trad.)",90,
+ [A3,C4,C4,D4,E4,E4,D4,C4,A3,C4,D4,E4,G4,E4,D4,C4,A3,C4,E4,G4,A4,G4,E4,D4,C4,A3],
+ [0,0,0,1,2,2,1,0,0,0,1,2,3,2,1,0,0,0,2,3,0,3,2,1,0,0],
+ [1,2,1,1,2,1,1,2,1,1,1,2,1,1,1,2,1,1,1,2,1,1,1,1,2,4],
+ 5,A3);
+ var wade=spirit("Wade in the Water","Spiritual (Trad.)",100,
+ [E4,E4,G4,A4,B4,A4,G4,E4,D4,E4,G4,A4,B4,D5,B4,A4,G4,E4,D4,E4,G4,B4,A4,G4,E4],
+ [2,2,3,0,3,0,3,2,1,2,3,0,3,1,3,0,3,2,1,2,3,3,0,3,2],
+ [1,1,1,1,2,1,1,2,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,4],
+ 5,E4);
+ var kumbaya=spirit("Kumbaya","Spiritual (Trad.)",75,
+ [C4,E4,F4,G4,G4,F4,E4,C4,C4,E4,F4,G4,A4,G4,E4,C4,E4,F4,G4,G4,F4,E4,D4,C4],
+ [0,2,1,3,3,1,2,0,0,2,1,3,0,3,2,0,2,1,3,3,1,2,1,0],
+ [1,1,1,2,1,1,2,2,1,1,1,2,1,2,1,1,1,1,2,1,1,1,1,4],
+ 5,C4);
+ var michael=spirit("Michael Row the Boat Ashore","Spiritual (1860s)",90,
+ [C4,E4,F4,E4,C4,D4,E4,F4,G4,A4,G4,E4,C4,D4,C4,C4,E4,G4,A4,G4,E4,F4,E4,D4,C4],
+ [0,2,1,2,0,1,2,1,3,0,3,2,0,1,0,0,2,3,0,3,2,1,2,1,0],
+ [1,1,1,2,1,1,1,1,2,1,1,1,2,1,3,1,1,2,1,1,1,1,1,1,4],
+ 5,C4);
+ var wholeworld=spirit("He's Got the Whole World","Spiritual (Trad.)",110,
+ [C4,C4,E4,G4,G4,E4,G4,A4,G4,E4,C4,E4,G4,C5,A4,G4,E4,C4,E4,G4,E4,C4],
+ [0,0,2,3,3,2,3,0,3,2,0,2,3,0,0,3,2,0,2,3,2,0],
+ [1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,2,1,1,1,4],
+ 5,C4);
+ var nobody=spirit("Nobody Knows the Trouble I've Seen","Spiritual (Trad.)",75,
+ [C4,E4,G4,A4,G4,E4,C4,D4,E4,G4,C5,A4,G4,E4,D4,C4,E4,G4,A4,C5,A4,G4,E4,D4,C4],
+ [0,2,3,0,3,2,0,1,2,3,0,0,3,2,1,0,2,3,0,0,0,3,2,1,0],
+ [1,1,2,1,1,2,2,1,1,2,1,1,1,2,1,2,1,2,1,2,1,1,1,1,4],
+ 4,C4);
+ var battle=spirit("Battle Hymn of the Republic","J.W. Howe (1861)",115,
+ [C4,C4,C4,E4,E4,D4,C4,D4,E4,F4,E4,D4,C4,C4,E4,G4,G4,F4,E4,F4,G4,A4,G4,E4,C4,E4,D4,C4],
+ [0,0,0,2,2,1,0,1,2,1,2,1,0,0,2,3,3,1,2,1,3,0,3,2,0,2,1,0],
+ [1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,2,1,1,4],
+ 4,C4);
+ return [swing,godown,wade,kumbaya,michael,wholeworld,nobody,battle];
+})();
+
+// ── WORLD (folk songs from various cultures) ──
+const WORLD_PIECES=(function(){
+ var C3=NT.C3,D3=NT.D3,E3=NT.E3,F3=NT.F3,G3=NT.G3,Ab3=NT.Ab3,A3=NT.A3,Bb3=NT.Bb3,B3=NT.B3,
+ C4=NT.C4,D4=NT.D4,Eb4=NT.Eb4,E4=NT.E4,F4=NT.F4,Gb4=NT.Gb4,G4=NT.G4,Ab4=NT.Ab4,A4=NT.A4,Bb4=NT.Bb4,B4=NT.B4,
+ C5=NT.C5,D5=NT.D5,E5=NT.E5;
+ function world(name,origin,bpm,theme,lanes,durations,passes,root){
+ var bg=[],mel=[],t=0,q=0.25;
+ for(var rep=0;rep1)bg.push({t:t,f:f/2,d:d*0.85,i:'bass',v:v*0.35});
+ if(rep>Math.floor(passes*0.6))bg.push({t:t,f:f,d:d*0.85,i:'brass',v:v*0.35});
+ mel.push({t:t,l:lanes[i]});t+=d;
+ });
+ t+=0.4;
+ }
+ var r=root||theme[0];
+ [r,r*5/4,r*3/2,r*2].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'brass',v:0.5});bg.push({t:t,f:f,d:2.0,i:'strings',v:0.4});});
+ bg.push({t:t,f:r/2,d:1.5,i:'timpani',v:0.55});mel.push({t:t,l:4});
+ return {n:name,composer:origin,bpm:bpm,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
+ }
+ var hava=world("Hava Nagila","Jewish/Israeli (1918)",130,
+ [E4,F4,E4,D4,E4,F4,G4,Ab4,G4,F4,E4,F4,G4,Ab4,B4,Ab4,G4,F4,E4,F4,G4,Ab4,G4,F4,E4,D4,E4],
+ [2,1,2,1,2,1,3,0,3,1,2,1,3,0,3,0,3,1,2,1,3,0,3,1,2,1,2],
+ [1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,4],
+ 5,E4);
+ var cucaracha=world("La Cucaracha","Mexican (Trad.)",135,
+ [C4,C4,C4,F4,A4,C4,C4,C4,F4,A4,F4,F4,E4,E4,D4,D4,C4,C4,C4,G4,Bb4,C4,C4,C4,G4,Bb4,A4,G4,F4,E4,D4,C4],
+ [0,0,0,1,0,0,0,0,1,0,1,1,2,2,1,1,0,0,0,3,2,0,0,0,3,2,0,3,1,2,1,0],
+ [1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,2,1,1,1,1,2,1,1,1,1,1,4],
+ 4,F4);
+ var frere=world("Fr\u00e8re Jacques","French (1780s)",110,
+ [C4,D4,E4,C4,C4,D4,E4,C4,E4,F4,G4,E4,F4,G4,G4,A4,G4,F4,E4,C4,G4,A4,G4,F4,E4,C4,C4,G3,C4,C4,G3,C4],
+ [0,1,2,0,0,1,2,0,2,1,3,2,1,3,3,0,3,1,2,0,3,0,3,1,2,0,0,3,0,0,3,0],
+ [1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,2,1,1,1,1,1,2,1,1,2,1,1,4],
+ 4,C4);
+ var cielito=world("Cielito Lindo","Mexican (1882)",120,
+ [E4,E4,F4,G4,A4,G4,F4,E4,D4,E4,F4,E4,D4,C4,E4,G4,C5,C5,B4,A4,G4,F4,E4,D4,C4,E4,G4,C5],
+ [2,2,1,3,0,3,1,2,1,2,1,2,1,0,2,3,0,0,3,0,3,1,2,1,0,2,3,0],
+ [1,1,1,1,2,1,1,1,1,1,1,1,1,3,1,1,2,1,1,1,1,1,1,1,2,1,1,4],
+ 4,C4);
+ var waltzing=world("Waltzing Matilda","Australian (1895)",110,
+ [G4,G4,A4,B4,D5,B4,A4,G4,E4,G4,A4,B4,A4,G4,E4,D4,G4,G4,A4,B4,D5,B4,A4,G4,E4,D4,G4],
+ [3,3,0,3,1,3,0,3,2,3,0,3,0,3,2,1,3,3,0,3,1,3,0,3,2,1,3],
+ [1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,1,1,1,1,1,1,4],
+ 5,G4);
+ var osole=world("'O Sole Mio","Italian (1898)",90,
+ [C4,D4,E4,F4,G4,A4,G4,F4,E4,D4,C4,D4,E4,F4,G4,C5,B4,A4,G4,F4,E4,F4,G4,A4,G4,F4,E4,D4,C4],
+ [0,1,2,1,3,0,3,1,2,1,0,1,2,1,3,0,3,0,3,1,2,1,3,0,3,1,2,1,0],
+ [1,1,1,1,2,1,1,1,1,1,2,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,4],
+ 4,C4);
+ var ballgame=world("Take Me Out to the Ball Game","American (1908)",125,
+ [C4,C4,A4,G4,E4,G4,D4,C4,C4,A4,G4,E4,G4,C4,C4,A4,G4,F4,E4,D4,E4,F4,D4,C4,A4,G4,E4,C4],
+ [0,0,0,3,2,3,1,0,0,0,3,2,3,0,0,0,3,1,2,1,2,1,1,0,0,3,2,0],
+ [1,1,2,1,1,2,1,2,1,2,1,1,3,1,1,2,1,1,1,1,1,1,2,1,1,1,1,4],
+ 4,C4);
+ var mountain=world("She'll Be Coming Round the Mountain","American (1890s)",130,
+ [G4,G4,B4,B4,D5,D5,B4,G4,A4,B4,C5,C5,A4,A4,G4,B4,D5,D5,B4,G4,A4,C5,B4,A4,G4],
+ [3,3,3,3,1,1,3,3,0,3,0,0,0,0,3,3,1,1,3,3,0,0,3,0,3],
+ [1,1,2,1,2,1,1,2,1,1,2,1,2,1,2,1,2,1,1,2,1,1,1,1,4],
+ 5,G4);
+ return [hava,cucaracha,frere,cielito,waltzing,osole,ballgame,mountain];
+})();
+
+var SYNTH_GENRES={"Singalong":SINGALONG_PIECES,"Christmas":CHRISTMAS_PIECES,"Folk":FOLK_PIECES,"Spirituals":SPIRITUAL_PIECES,"World":WORLD_PIECES,"Classical":CLASSICAL_PIECES,"Video Games":VIDEOGAME_PIECES,"Chiptune Originals":CHIPTUNE_PIECES};
function buildClassicalNotes(piece){
return piece.melody.filter(function(m){return activePads[m.l];})
@@ -2026,6 +2161,7 @@ var gpMap=JSON.parse(localStorage.getItem("drgameGpMap")||"null")||{0:2,1:3,2:0,
var remapping=false,remapStep=0,remapResult={};
var _savedSettings=JSON.parse(localStorage.getItem("drgameSettings")||"{}");
var gameSpeed=_savedSettings.speed||1.0,masterVolume=_savedSettings.volume||1.0;
+var hiddenGenres=JSON.parse(localStorage.getItem("drgameHidden")||"[]");
let gameRhythm=null;
const HIT_WIN=0.17,SPEED=260;
@@ -2057,6 +2193,24 @@ function loadSettings(){
document.getElementById("vol-slider").value=Math.round(masterVolume*100);
document.getElementById("vol-val").textContent=Math.round(masterVolume*100)+"%";
}
+function confirmReset(){
+ var a=Math.floor(Math.random()*20)+5,b=Math.floor(Math.random()*20)+5;
+ var ans=prompt("To reset ALL saved data (scores, settings, gamepad mapping, hidden genres), solve this:\n\nWhat is "+a+" + "+b+"?");
+ if(ans===null)return;
+ if(parseInt(ans)===a+b){
+ localStorage.removeItem("drgame5");localStorage.removeItem("drgameSettings");
+ localStorage.removeItem("drgameGpMap");localStorage.removeItem("drgameHidden");
+ savedScores=[];hiddenGenres=[];gpMap={0:2,1:3,2:0,3:1,4:4,8:4};
+ refreshMiniLB();resetSettings();buildMenu();
+ alert("All data cleared!");
+ } else {alert("Incorrect — data NOT cleared.");}
+}
+function hideGenre(g){
+ if(hiddenGenres.indexOf(g)<0)hiddenGenres.push(g);
+ localStorage.setItem("drgameHidden",JSON.stringify(hiddenGenres));
+ if(curGenre===g){var allG=Object.keys(SYNTH_GENRES).concat(Object.keys(GENRES));curGenre=allG.find(function(x){return hiddenGenres.indexOf(x)<0;})||allG[0];curIdx=0;}
+ buildMenu();
+}
function setHitMode(m){
classicalHitMode=m;
document.getElementById("hitmode-drums").className="smbtn"+(m==='drums'?' purple':'');
@@ -2069,21 +2223,25 @@ function updateHitModeVisibility(){
// ── MENU ───────────────────────────────────────────────
function buildMenu(){
const gt=document.getElementById("gtabs");gt.innerHTML="";
- // 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");
+ var synthIcons={"Singalong":"\uD83C\uDFB5","Christmas":"\uD83C\uDF84","Folk":"\uD83C\uDFB6","Spirituals":"\uD83D\uDE4F","World":"\uD83C\uDF0D","Classical":"\uD83C\uDFBB","Video Games":"\uD83C\uDFAE","Chiptune Originals":"\uD83D\uDC7E"};
+ var allGenres=Object.keys(SYNTH_GENRES).concat(Object.keys(GENRES));
+ var anyHidden=hiddenGenres.length>0;
+ allGenres.forEach(function(g){
+ if(hiddenGenres.indexOf(g)>=0)return;
+ var b=document.createElement("button");
b.className="gtab"+(g===curGenre?" active":"");
- b.textContent=g;b.onclick=()=>{curGenre=g;curIdx=0;stopPreview();buildMenu();};
+ b.textContent=(synthIcons[g]?synthIcons[g]+" ":"")+g;
+ b.onclick=function(){curGenre=g;curIdx=0;stopPreview();buildMenu();};
+ b.oncontextmenu=function(e){e.preventDefault();hideGenre(g);};
gt.appendChild(b);
});
+ if(anyHidden){
+ var sb=document.createElement("button");
+ sb.className="gtab";sb.style.opacity="0.4";sb.style.fontSize="10px";
+ sb.textContent="\u2795 Show All ("+hiddenGenres.length+" hidden)";
+ sb.onclick=function(){hiddenGenres=[];localStorage.setItem("drgameHidden","[]");buildMenu();};
+ gt.appendChild(sb);
+ }
const rg=document.getElementById("rgrid");rg.innerHTML="";
(SYNTH_GENRES[curGenre]||GENRES[curGenre]).forEach((r,i)=>{
const b=document.createElement("button");