Files
drum-rhythm-game/index.html
T
Outis 5280f94882 Add Classical genre tab with 26 synthesized orchestral pieces
Implements a full Classical mode with Web Audio synthesizer engine supporting strings, brass, woodwind, bass, timpani, and choir instruments. 26 playable pieces from Beethoven's 5th to Flight of the Bumblebee with 19 minutes of total content and 3,173 hittable melody notes. Includes drums/orchestra hit sound toggle.
2026-03-30 08:40:43 -04:00

1919 lines
90 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🥁 Drum Rhythm Game</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;}
body{background:#0a0a0f;color:#eee;font-family:sans-serif;height:100vh;display:flex;flex-direction:column;overflow:hidden;}
.screen{display:none;flex-direction:column;align-items:center;width:100%;height:100%;overflow-y:auto;padding:16px;gap:10px;}
.screen.active{display:flex;}
.card{background:#13131f;border:1px solid #222;border-radius:10px;padding:14px;width:100%;max-width:700px;}
.card h2{font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;}
.mode-row{display:flex;gap:8px;}
.mode-card{flex:1;padding:10px;border-radius:8px;border:2px solid #222;background:#0d0d1a;cursor:pointer;text-align:center;}
.mode-card.sel{border-color:#805ad5;}
.mode-card h3{font-size:13px;margin-bottom:3px;}
.mode-card p{font-size:11px;opacity:.5;}
.genre-tabs{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px;}
.gtab{padding:5px 12px;border-radius:16px;border:1px solid #333;background:transparent;color:#aaa;cursor:pointer;font-size:12px;}
.gtab.active{background:#805ad5;border-color:#805ad5;color:#fff;}
.rgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;max-height:160px;overflow-y:auto;}
.rbtn{padding:6px 8px;border-radius:5px;border:1px solid #2a2a3a;background:#0d0d1a;color:#ccc;cursor:pointer;font-size:11px;text-align:left;}
.rbtn.sel{border-color:#38a169;color:#eee;}
.rbtn small{opacity:.5;display:block;}
.pad-toggle{display:flex;gap:8px;flex-wrap:wrap;}
.ptog{padding:7px 14px;border-radius:20px;border:2px solid;cursor:pointer;font-size:12px;font-weight:bold;opacity:.4;transition:opacity .15s;}
.ptog.on{opacity:1;}
.prow{display:flex;gap:6px;align-items:center;margin-bottom:5px;}
.prow input{background:#0d0d1a;border:1px solid #333;border-radius:5px;padding:5px 8px;color:#eee;font-size:12px;flex:1;}
.smbtn{padding:5px 10px;border-radius:5px;border:1px solid #333;background:transparent;color:#eee;cursor:pointer;font-size:12px;}
.smbtn.green{color:#68d391;border-color:#68d391;}
.smbtn.red{color:#fc8181;border-color:#fc8181;}
.smbtn.purple{color:#b794f4;border-color:#b794f4;}
.bigbtn{padding:11px 32px;border-radius:9px;border:none;background:#805ad5;color:#fff;font-size:15px;cursor:pointer;font-weight:bold;}
.bigbtn:hover{background:#9f7aea;}
#hud{padding:7px 12px;background:#111;border-bottom:1px solid #1e1e2e;display:flex;align-items:center;gap:10px;flex-shrink:0;width:100%;}
#hud-title{font-size:12px;font-weight:bold;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.hstat{text-align:center;min-width:50px;}.hstat span{font-size:9px;opacity:.5;display:block;}.hstat b{font-size:16px;}
#cwrap{flex:1;position:relative;width:100%;}
canvas{display:block;}
#msg{position:absolute;top:28%;left:50%;transform:translate(-50%,-50%);font-size:28px;font-weight:bold;pointer-events:none;opacity:0;text-shadow:0 0 20px currentColor;white-space:nowrap;}
#cdown{position:absolute;top:40%;left:50%;transform:translate(-50%,-50%);font-size:80px;font-weight:bold;pointer-events:none;opacity:0;color:#fff;}
#pbanner{position:absolute;top:12px;left:50%;transform:translateX(-50%);background:#1a1a2e;border:1px solid #444;border-radius:7px;padding:7px 18px;font-size:13px;opacity:0;pointer-events:none;transition:opacity .3s;}
#ss{padding:16px;}
.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;}
</style>
</head>
<body>
<!-- MENU -->
<div class="screen active" id="sm">
<div style="text-align:center;padding:6px 0"><h1 style="font-size:24px">🥁 Drum Rhythm Game</h1><p style="opacity:.4;font-size:12px">Hit pads when notes reach the line</p></div>
<div class="card">
<h2>Mode</h2>
<div class="mode-row">
<div class="mode-card sel" id="mc-b" onclick="setMode('builtin')"><h3>🥁 Rhythm</h3><p>Play along to drum charts</p></div>
<div class="mode-card" id="mc-m" onclick="setMode('mp3')"><h3>🎵 MP3 Jam</h3><p>Load music, drum freely</p></div>
</div>
</div>
<div class="card" id="rc">
<h2>Choose Rhythm</h2>
<div class="genre-tabs" id="gtabs"></div>
<div class="rgrid" id="rgrid"></div>
<div style="display:flex;gap:8px;align-items:center;margin-top:8px;">
<button class="smbtn purple" id="pvbtn" onclick="togglePreview()">▶ Preview</button>
<span id="pvlbl" style="font-size:11px;opacity:.5">Select a rhythm to preview</span>
</div>
</div>
<div class="card" id="mc-mp3" style="display:none">
<h2>Load MP3</h2>
<div style="display:flex;gap:8px;align-items:center">
<button class="smbtn" onclick="document.getElementById('fi').click()">📂 Browse</button>
<span id="mp3n" style="font-size:12px;opacity:.5">No file</span>
<input type="file" id="fi" accept="audio/*" style="display:none">
</div>
</div>
<div class="card">
<h2>Active Pads</h2>
<div class="pad-toggle" id="pad-toggles"></div>
<p style="font-size:11px;opacity:.4;margin-top:8px">Toggle which pads show notes. Inactive pads still make sound when hit.</p>
<div id="hitmode-toggle" style="display:none;margin-top:10px;padding-top:8px;border-top:1px solid #222;">
<span style="font-size:11px;opacity:.5;margin-right:8px;">Classical hit sound:</span>
<button class="smbtn" id="hitmode-drums" onclick="setHitMode('drums')">🥁 Drums</button>
<button class="smbtn purple" id="hitmode-orch" onclick="setHitMode('orchestra')">🎻 Orchestra</button>
</div>
</div>
<div class="card">
<h2>Players</h2>
<div id="plist"></div>
<button class="smbtn green" onclick="addPlayer()" style="margin-top:5px">+ Add Player</button>
</div>
<div class="card">
<h2>Leaderboard</h2>
<div id="mlb" style="font-size:12px;opacity:.5">No scores yet.</div>
<button class="smbtn" style="margin-top:8px" onclick="goScores()">View All</button>
</div>
<button class="bigbtn" onclick="startGame()">▶ Start</button>
</div>
<!-- GAME -->
<div class="screen" id="sg" style="padding:0;">
<div id="hud">
<button class="smbtn" onclick="goMenu()">← Menu</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>
<div class="hstat"><span>Acc</span><b id="ha">-</b></div>
<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>
<!-- SCORES -->
<div class="screen" id="ss" style="padding:16px;">
<div style="width:100%;max-width:600px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h2>🏆 Leaderboard</h2>
<div style="display:flex;gap:6px">
<button class="smbtn red" onclick="clearScores()">Clear</button>
<button class="smbtn" onclick="goMenu()">← Back</button>
</div>
</div>
<table class="stbl"><thead><tr><th></th><th>Player</th><th>Rhythm</th><th>Score</th><th>Acc</th><th>Streak</th><th>Date</th></tr></thead>
<tbody id="stbody"></tbody></table>
</div>
</div>
<script>
// ── LANE DEFINITIONS ──────────────────────────────────
const LANES=[
{label:"Red", color:"#e53e3e",key:"f", type:"tom"},
{label:"Yellow",color:"#d69e2e",key:"g", type:"snare"},
{label:"Blue", color:"#3182ce",key:"h", type:"hihat"},
{label:"Green", color:"#38a169",key:"j", type:"tom2"},
{label:"Kick", color:"#805ad5",key:" ", type:"kick"},
];
// which pads are active (show notes) — all on by default
let activePads=[true,true,true,true,true];
// ── RHYTHMS ───────────────────────────────────────────
// Every pattern now uses ALL 5 lanes (0=Red/tom, 1=Yellow/snare, 2=Blue/hihat, 3=Green/tom2, 4=Kick)
// t = beat offset within a 4-beat bar
const GENRES={
Rock:[
{n:"Basic Rock",bpm:90,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1,l:2},{t:1.5,l:2},
{t:2,l:4},{t:2,l:2},{t:2.5,l:2},{t:3,l:1},{t:3,l:2},{t:3.5,l:2}]},
{n:"Classic Rock",bpm:110,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1.5,l:2},
{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3,l:0},{t:3.5,l:2}]},
{n:"Punk Rock",bpm:160,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1,l:2},{t:1.5,l:2},
{t:2,l:4},{t:2.5,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.25,l:0},{t:3.5,l:2}]},
{n:"Arena Rock",bpm:120,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1,l:3},{t:1.5,l:2},
{t:2,l:4},{t:2,l:2},{t:2.5,l:2},{t:3,l:1},{t:3,l:0},{t:3.5,l:2}]},
{n:"Hard Rock",bpm:140,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:2},{t:3,l:0},{t:3.25,l:1},{t:3.5,l:3},{t:3.75,l:0}]},
{n:"Indie Rock",bpm:100,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.75,l:3},{t:1,l:1},{t:1.5,l:2},
{t:2,l:4},{t:2,l:2},{t:2.75,l:0},{t:3,l:1},{t:3.5,l:2}]},
{n:"Rock Shuffle",bpm:100,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.67,l:2},{t:1,l:1},{t:1,l:3},{t:1.67,l:2},
{t:2,l:4},{t:2,l:2},{t:2.67,l:2},{t:3,l:1},{t:3,l:0},{t:3.67,l:2}]},
{n:"Power Ballad",bpm:72,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:0}]},
{n:"Half Time",bpm:95,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:2},{t:1.5,l:3},
{t:2,l:1},{t:2,l:2},{t:2.5,l:2},{t:3,l:2},{t:3.5,l:0}]},
{n:"Tom Heavy",bpm:105,bars:8,p:[
{t:0,l:4},{t:.5,l:3},{t:1,l:1},{t:1.5,l:0},
{t:2,l:4},{t:2.5,l:3},{t:3,l:1},{t:3.5,l:0}]},
{n:"Stadium Rock",bpm:118,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1,l:3},{t:1.5,l:2},
{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3,l:0},{t:3.5,l:2}]},
{n:"Garage Rock",bpm:145,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:0}]},
{n:"Post Rock",bpm:80,bars:8,p:[
{t:0,l:4},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:2},{t:3,l:1},{t:3,l:0},{t:3.5,l:2}]},
{n:"Glam Rock",bpm:130,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:.75,l:2},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:2}]},
{n:"Rock Fill",bpm:110,bars:4,p:[
{t:0,l:4},{t:.5,l:3},{t:1,l:2},{t:1.5,l:1},
{t:2,l:0},{t:2.25,l:1},{t:2.5,l:2},{t:2.75,l:3},{t:3,l:4},{t:3.5,l:0}]},
],
Metal:[
{n:"Basic Metal",bpm:160,bars:8,p:[
{t:0,l:4},{t:.25,l:4},{t:0,l:2},{t:.5,l:2},{t:1,l:1},{t:1,l:3},
{t:1.25,l:4},{t:1.5,l:2},{t:2,l:4},{t:2.25,l:4},{t:2,l:2},
{t:2.5,l:0},{t:3,l:1},{t:3,l:4},{t:3.5,l:2}]},
{n:"Double Bass",bpm:155,bars:8,p:[
{t:0,l:4},{t:.25,l:4},{t:.5,l:4},{t:.75,l:4},{t:1,l:1},{t:1,l:0},
{t:1.25,l:4},{t:1.5,l:4},{t:1.75,l:4},{t:2,l:4},{t:2.25,l:4},
{t:2.5,l:4},{t:2.75,l:4},{t:3,l:1},{t:3,l:3},{t:3.5,l:2}]},
{n:"Thrash",bpm:190,bars:8,p:[
{t:0,l:4},{t:.5,l:4},{t:1,l:1},{t:1,l:0},{t:1.5,l:4},
{t:2,l:4},{t:2.5,l:3},{t:3,l:1},{t:3.5,l:4}]},
{n:"Groove Metal",bpm:120,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:2},{t:2,l:4},{t:2,l:2},{t:2.75,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"Doom Metal",bpm:55,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:3,l:1},{t:3.5,l:0}]},
{n:"Nu Metal",bpm:100,bars:8,p:[
{t:0,l:4},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.25,l:4},{t:2.5,l:1},{t:3.25,l:0},{t:3.5,l:2}]},
{n:"Power Metal",bpm:175,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:.5,l:2},{t:1,l:1},{t:1,l:3},
{t:1.5,l:4},{t:1.5,l:2},{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:2}]},
{n:"Metalcore",bpm:150,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1,l:2},{t:1.5,l:3},
{t:2,l:4},{t:2.25,l:4},{t:2.5,l:1},{t:3,l:0},{t:3.5,l:2}]},
{n:"Prog Metal",bpm:130,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.25,l:3},
{t:1.5,l:2},{t:2,l:4},{t:2.75,l:0},{t:3,l:1},{t:3,l:2},{t:3.5,l:3},{t:3.75,l:0}]},
{n:"Industrial",bpm:140,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1,l:2},
{t:1.5,l:4},{t:1.5,l:2},{t:2,l:4},{t:2,l:2},{t:2.5,l:3},{t:3,l:1},{t:3.5,l:2}]},
{n:"Sludge",bpm:65,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.75,l:0},{t:1.5,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.75,l:2},{t:3,l:0},{t:3.5,l:1}]},
{n:"Death Metal",bpm:185,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:1},{t:.75,l:3},{t:1,l:4},
{t:1.25,l:1},{t:1.5,l:0},{t:1.75,l:4},{t:2,l:1},{t:2.25,l:3},
{t:2.5,l:4},{t:2.75,l:0},{t:3,l:4},{t:3.5,l:1}]},
{n:"Blast Beat",bpm:200,bars:4,p:[
{t:0,l:4},{t:0,l:1},{t:.25,l:4},{t:.25,l:2},{t:.5,l:4},{t:.5,l:1},
{t:.75,l:4},{t:.75,l:3},{t:1,l:4},{t:1,l:0}]},
{n:"Breakdown",bpm:90,bars:4,p:[
{t:0,l:4},{t:.5,l:0},{t:.75,l:4},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:4},{t:2,l:4},{t:2.5,l:0},{t:2.75,l:1},{t:3,l:3},{t:3.5,l:4}]},
{n:"Black Metal",bpm:210,bars:4,p:[
{t:0,l:4},{t:0,l:2},{t:.25,l:0},{t:.25,l:2},{t:.5,l:1},{t:.5,l:4},
{t:.75,l:3},{t:.75,l:2},{t:1,l:4},{t:1,l:0}]},
],
Jazz:[
{n:"Jazz Ride",bpm:120,bars:8,p:[
{t:0,l:3},{t:.67,l:3},{t:1,l:3},{t:1,l:1},{t:1.67,l:3},
{t:2,l:4},{t:2,l:3},{t:2.67,l:0},{t:3,l:3},{t:3,l:1},{t:3.67,l:3}]},
{n:"Slow Swing",bpm:75,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1.5,l:0},{t:1.67,l:3},
{t:2,l:4},{t:2.67,l:3},{t:3,l:1},{t:3.67,l:3}]},
{n:"Bebop",bpm:200,bars:8,p:[
{t:0,l:3},{t:.67,l:3},{t:1,l:3},{t:1,l:1},{t:1.67,l:3},
{t:2,l:1},{t:2,l:3},{t:2.67,l:0},{t:3,l:3},{t:3.67,l:3}]},
{n:"Jazz Waltz",bpm:140,bars:8,p:[
{t:0,l:4},{t:0,l:3},{t:.67,l:3},{t:1,l:3},{t:1.67,l:3},
{t:2,l:1},{t:2,l:0},{t:2,l:3},{t:2.67,l:3}]},
{n:"Bossa Nova",bpm:110,bars:8,p:[
{t:0,l:4},{t:0,l:3},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},{t:1.75,l:3},
{t:2,l:4},{t:2,l:3},{t:2.75,l:3},{t:3,l:1},{t:3.5,l:0}]},
{n:"Cool Jazz",bpm:95,bars:8,p:[
{t:0,l:4},{t:.5,l:3},{t:1,l:1},{t:1.5,l:0},{t:1.5,l:3},
{t:2,l:3},{t:2.5,l:4},{t:3,l:1},{t:3.5,l:3}]},
{n:"Fusion",bpm:135,bars:8,p:[
{t:0,l:4},{t:.25,l:2},{t:.5,l:3},{t:1,l:1},{t:1,l:0},
{t:1.25,l:4},{t:1.5,l:2},{t:2,l:3},{t:2.75,l:1},{t:3,l:0},{t:3.5,l:3}]},
{n:"Jazz Shuffle",bpm:105,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1.33,l:0},{t:1.67,l:3},
{t:2,l:4},{t:2.67,l:3},{t:3,l:1},{t:3.67,l:3}]},
{n:"Afro-Cuban",bpm:115,bars:8,p:[
{t:0,l:4},{t:.5,l:3},{t:.75,l:0},{t:1,l:1},{t:1.5,l:4},
{t:1.75,l:3},{t:2,l:3},{t:2.5,l:0},{t:3,l:1},{t:3.25,l:3},{t:3.75,l:4}]},
{n:"New Orleans",bpm:100,bars:8,p:[
{t:0,l:4},{t:.67,l:2},{t:1,l:1},{t:1.33,l:0},{t:1.67,l:2},
{t:2,l:4},{t:2.33,l:2},{t:2.67,l:0},{t:3,l:1},{t:3.33,l:2}]},
{n:"Hard Bop",bpm:165,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1,l:0},{t:1.5,l:4},
{t:1.67,l:3},{t:2,l:3},{t:2.67,l:0},{t:3,l:1},{t:3,l:4},{t:3.67,l:3}]},
{n:"Modal Jazz",bpm:115,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1.33,l:0},{t:2,l:1},{t:2,l:4},
{t:2.67,l:3},{t:3.33,l:0}]},
{n:"Jazz Ballad",bpm:60,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1.5,l:0},
{t:2,l:4},{t:2.67,l:3},{t:3,l:1}]},
{n:"Post-Bop",bpm:155,bars:8,p:[
{t:0,l:3},{t:.5,l:0},{t:.67,l:3},{t:1,l:1},{t:1.33,l:3},
{t:1.67,l:4},{t:2,l:3},{t:2.33,l:0},{t:2.67,l:1},{t:3,l:3},{t:3.5,l:4}]},
{n:"Medium Swing",bpm:130,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1,l:0},{t:1.67,l:3},
{t:2,l:4},{t:2.67,l:3},{t:3,l:1},{t:3.67,l:3}]},
],
"Hip Hop":[
{n:"Boom Bap",bpm:90,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:2},{t:1.75,l:0},
{t:2,l:4},{t:2.25,l:3},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:2}]},
{n:"Trap",bpm:140,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:4},{t:1,l:1},{t:1.5,l:2},{t:1.75,l:2},
{t:2,l:4},{t:2.25,l:3},{t:3,l:1},{t:3.5,l:2},{t:3.75,l:4}]},
{n:"Lo-Fi",bpm:75,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:0},{t:1.75,l:4},
{t:2,l:2},{t:2.5,l:3},{t:3,l:1},{t:3.5,l:2}]},
{n:"Classic Hip Hop",bpm:85,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:2},
{t:2,l:4},{t:2.5,l:3},{t:2.75,l:2},{t:3,l:1},{t:3.5,l:2},{t:3.75,l:0}]},
{n:"East Coast",bpm:88,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:0},{t:1.75,l:4},
{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3}]},
{n:"West Coast",bpm:100,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.25,l:0},{t:3.5,l:2}]},
{n:"Drill",bpm:145,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:4},{t:1,l:1},{t:1.25,l:3},
{t:1.75,l:4},{t:2,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.75,l:4}]},
{n:"UK Grime",bpm:140,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.25,l:0},{t:1.5,l:3},
{t:2,l:4},{t:2.5,l:2},{t:2.75,l:1},{t:3.5,l:0}]},
{n:"Jazz Rap",bpm:92,bars:8,p:[
{t:0,l:4},{t:.67,l:2},{t:1,l:1},{t:1.5,l:0},{t:1.67,l:2},
{t:2,l:4},{t:2.33,l:3},{t:2.67,l:4},{t:3,l:1},{t:3.67,l:2}]},
{n:"R&B Hip Hop",bpm:88,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:4},{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.25,l:0},{t:3.5,l:2}]},
{n:"Crunk",bpm:75,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1,l:2},{t:1.5,l:3},
{t:2,l:4},{t:2.5,l:4},{t:3,l:1},{t:3,l:0},{t:3.5,l:4}]},
{n:"Cloud Rap",bpm:78,bars:8,p:[
{t:0,l:4},{t:.75,l:2},{t:1,l:1},{t:1.5,l:0},
{t:2,l:3},{t:3,l:1},{t:3.5,l:4}]},
{n:"Gangsta",bpm:95,bars:8,p:[
{t:0,l:4},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.5,l:4},{t:3,l:1},{t:3.75,l:0}]},
{n:"Freestyle",bpm:95,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.25,l:0},{t:1.5,l:3},
{t:2,l:4},{t:2.25,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3},{t:3.75,l:4}]},
{n:"Chopped",bpm:60,bars:8,p:[
{t:0,l:4},{t:1,l:1},{t:1.5,l:0},{t:2,l:3},{t:2.5,l:4},{t:3,l:1}]},
],
Funk:[
{n:"Classic Funk",bpm:98,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.25,l:0},{t:.5,l:2},{t:.75,l:1},
{t:1,l:2},{t:1.5,l:3},{t:1.75,l:2},{t:2,l:4},{t:2,l:2},
{t:2.5,l:1},{t:2.75,l:0},{t:3,l:4},{t:3.5,l:2}]},
{n:"Funk Pocket",bpm:95,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.25,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3},{t:3.75,l:4}]},
{n:"Ghost Notes",bpm:95,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.25,l:1},{t:.5,l:2},{t:.75,l:0},
{t:1,l:1},{t:1.25,l:0},{t:1.5,l:3},{t:2,l:4},{t:2.25,l:0},
{t:2.5,l:2},{t:3,l:1},{t:3,l:2},{t:3.5,l:0}]},
{n:"P-Funk",bpm:108,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:2},{t:1,l:1},{t:1.25,l:3},
{t:1.5,l:2},{t:1.75,l:4},{t:2,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3},{t:3.75,l:2}]},
{n:"Disco Funk",bpm:118,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:.5,l:2},{t:1,l:1},{t:1,l:3},
{t:1.5,l:4},{t:1.5,l:2},{t:2,l:0},{t:2,l:2},{t:2.5,l:4},
{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3}]},
{n:"Soul Funk",bpm:85,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:0},
{t:1.75,l:3},{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:0}]},
{n:"Syncopated",bpm:100,bars:8,p:[
{t:0,l:4},{t:.25,l:2},{t:.75,l:0},{t:1,l:1},{t:1.25,l:3},
{t:1.5,l:4},{t:2,l:0},{t:2.25,l:2},{t:2.75,l:3},{t:3,l:1},{t:3.5,l:2}]},
{n:"Afrobeat",bpm:105,bars:8,p:[
{t:0,l:4},{t:.5,l:3},{t:.75,l:0},{t:1,l:1},{t:1.25,l:2},
{t:1.5,l:3},{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3.25,l:3},{t:3.75,l:2}]},
{n:"Hi-Hat Funk",bpm:100,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.25,l:2},{t:.5,l:0},{t:.75,l:2},
{t:1,l:1},{t:1.25,l:2},{t:1.5,l:3},{t:1.75,l:4},
{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:2}]},
{n:"New Orleans",bpm:90,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.67,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.67,l:2},{t:2,l:4},{t:2.5,l:2},{t:2.67,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"Electro Funk",bpm:112,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1,l:0},{t:1.5,l:3},
{t:1.5,l:2},{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3,l:0},{t:3.5,l:4}]},
{n:"Trap Funk",bpm:130,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:2},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:4},{t:2,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.75,l:4}]},
{n:"Slow Funk",bpm:72,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:0},
{t:2,l:3},{t:2.5,l:2},{t:2.75,l:4},{t:3,l:1}]},
{n:"Funk Breakdown",bpm:88,bars:4,p:[
{t:0,l:4},{t:.25,l:2},{t:.5,l:0},{t:.75,l:2},{t:1,l:1},
{t:1.25,l:3},{t:1.5,l:2},{t:1.75,l:4},{t:2,l:0},{t:2.5,l:4},{t:2.75,l:1},{t:3,l:2}]},
{n:"Sly Groove",bpm:102,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:2},{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3.25,l:3},{t:3.75,l:2}]},
],
Latin:[
{n:"Samba",bpm:130,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:3},{t:.75,l:0},{t:1,l:1},{t:1,l:2},
{t:1.5,l:3},{t:1.75,l:4},{t:2,l:0},{t:2,l:2},{t:2.5,l:3},
{t:2.75,l:3},{t:3,l:1},{t:3.5,l:0}]},
{n:"Bossa Nova",bpm:110,bars:8,p:[
{t:0,l:4},{t:0,l:3},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:3},{t:2,l:4},{t:2,l:0},{t:2.75,l:3},{t:3,l:1},{t:3.5,l:0}]},
{n:"Salsa",bpm:185,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:2},{t:3.5,l:1}]},
{n:"Cha Cha",bpm:120,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1.5,l:2},{t:1.75,l:3},
{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:2},{t:3.75,l:3}]},
{n:"Reggaeton",bpm:95,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1,l:2},{t:1.5,l:3},
{t:2,l:4},{t:2.5,l:4},{t:3,l:1},{t:3,l:0},{t:3.5,l:4}]},
{n:"Merengue",bpm:150,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:.5,l:2},{t:1,l:1},{t:1,l:2},
{t:1.5,l:3},{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3}]},
{n:"Cumbia",bpm:120,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:2},{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"Mambo",bpm:165,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:4},{t:.75,l:2},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.25,l:0},{t:2.75,l:4},{t:3,l:1},{t:3.5,l:3}]},
{n:"Tango",bpm:65,bars:8,p:[
{t:0,l:4},{t:0,l:1},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:1},{t:2.5,l:0},{t:3,l:1}]},
{n:"Afro-Cuban",bpm:115,bars:8,p:[
{t:0,l:4},{t:.5,l:3},{t:.75,l:0},{t:1,l:1},{t:1.5,l:4},
{t:1.75,l:3},{t:2,l:0},{t:2.5,l:4},{t:3,l:1},{t:3.25,l:3},{t:3.75,l:0}]},
{n:"Flamenco",bpm:140,bars:8,p:[
{t:0,l:4},{t:.33,l:3},{t:.67,l:0},{t:1,l:1},{t:1.33,l:3},
{t:1.67,l:4},{t:2,l:0},{t:2.33,l:3},{t:2.67,l:1},{t:3,l:4},{t:3.33,l:0}]},
{n:"Bolero",bpm:72,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1.33,l:0},{t:1.67,l:3},
{t:2,l:4},{t:2.67,l:3},{t:3,l:1},{t:3.67,l:0}]},
{n:"Baiao",bpm:110,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:.75,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:4},{t:2,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.25,l:3},{t:3.5,l:0}]},
{n:"Son Cubano",bpm:100,bars:8,p:[
{t:0,l:4},{t:.5,l:3},{t:1,l:1},{t:1.5,l:0},
{t:1.75,l:4},{t:2,l:0},{t:2.5,l:3},{t:3,l:1},{t:3.5,l:3}]},
{n:"Timba",bpm:175,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:4},{t:.75,l:2},{t:1,l:1},{t:1.25,l:3},
{t:1.5,l:4},{t:1.75,l:2},{t:2,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:3}]},
],
Electronic:[
{n:"4-on-Floor",bpm:128,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1,l:3},
{t:1.5,l:2},{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3,l:4},{t:3.5,l:2}]},
{n:"Techno",bpm:135,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:4},{t:1,l:2},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:4},{t:3,l:2},{t:3.5,l:3}]},
{n:"Drum & Bass",bpm:174,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:.75,l:1},{t:1.5,l:3},
{t:1.75,l:4},{t:2,l:1},{t:2.5,l:0},{t:2.75,l:4},{t:3,l:1},{t:3.5,l:4}]},
{n:"Dubstep",bpm:140,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:4},{t:2,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.75,l:4}]},
{n:"Breakbeat",bpm:110,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:.75,l:4},{t:1,l:1},{t:1.5,l:3},
{t:1.75,l:2},{t:2,l:4},{t:2.25,l:0},{t:2.5,l:2},{t:3,l:1},{t:3.25,l:4},{t:3.5,l:0}]},
{n:"Trance",bpm:138,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.25,l:0},{t:.5,l:2},{t:.75,l:3},
{t:1,l:1},{t:1,l:4},{t:1.5,l:0},{t:2,l:4},{t:2,l:2},{t:2.5,l:3},
{t:3,l:1},{t:3,l:4},{t:3.5,l:0}]},
{n:"Jungle",bpm:160,bars:8,p:[
{t:0,l:4},{t:.25,l:1},{t:.5,l:0},{t:.75,l:2},{t:1,l:3},
{t:1.25,l:1},{t:1.5,l:4},{t:1.75,l:2},{t:2,l:0},{t:2.5,l:1},
{t:2.75,l:3},{t:3,l:2},{t:3.25,l:4},{t:3.75,l:0}]},
{n:"Synthwave",bpm:100,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1,l:3},
{t:1.5,l:2},{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3,l:3},{t:3.5,l:2}]},
{n:"Future Bass",bpm:150,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1,l:3},{t:1.5,l:2},
{t:1.75,l:4},{t:2,l:0},{t:2.5,l:3},{t:3,l:1},{t:3.25,l:4},{t:3.75,l:0}]},
{n:"IDM",bpm:120,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:1},{t:.75,l:3},{t:1,l:2},
{t:1.25,l:4},{t:1.75,l:0},{t:2,l:2},{t:2.25,l:3},{t:2.5,l:1},
{t:2.75,l:0},{t:3.25,l:4},{t:3.75,l:2}]},
{n:"Big Beat",bpm:115,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:.75,l:2},{t:1,l:1},{t:1,l:3},
{t:1.5,l:4},{t:1.75,l:0},{t:2,l:4},{t:2,l:2},{t:2.5,l:3},{t:3,l:1},{t:3.5,l:0}]},
{n:"Electro",bpm:118,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1,l:3},
{t:1.5,l:4},{t:1.5,l:2},{t:2,l:0},{t:2,l:2},{t:2.5,l:4},{t:3,l:1},{t:3.5,l:3}]},
{n:"Minimal",bpm:130,bars:8,p:[
{t:0,l:4},{t:1,l:0},{t:1,l:2},{t:2,l:4},{t:2,l:1},{t:3,l:0},{t:3,l:2}]},
{n:"Footwork",bpm:160,bars:8,p:[
{t:0,l:4},{t:.25,l:0},{t:.5,l:1},{t:.75,l:4},{t:1,l:3},
{t:1.25,l:4},{t:1.5,l:0},{t:1.75,l:4},{t:2,l:1},{t:2.5,l:0},
{t:2.75,l:3},{t:3,l:4},{t:3.25,l:1},{t:3.5,l:0}]},
{n:"Ambient",bpm:75,bars:8,p:[
{t:0,l:4},{t:1,l:2},{t:1.5,l:0},{t:2,l:1},{t:2.5,l:3},{t:3,l:2}]},
],
Country:[
{n:"Two-Step",bpm:105,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:1,l:1},{t:1,l:3},{t:1.5,l:0},
{t:2,l:4},{t:2,l:2},{t:3,l:1},{t:3,l:3}]},
{n:"Honky Tonk",bpm:120,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"Shuffle",bpm:100,bars:8,p:[
{t:0,l:4},{t:.67,l:2},{t:1,l:1},{t:1.5,l:0},{t:1.67,l:2},
{t:2,l:4},{t:2.67,l:2},{t:3,l:1},{t:3.5,l:0},{t:3.67,l:2}]},
{n:"Bluegrass",bpm:145,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1,l:3},
{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3,l:3}]},
{n:"Ballad",bpm:68,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:1,l:1},{t:1.5,l:0},
{t:2,l:4},{t:2,l:2},{t:3,l:1},{t:3.5,l:3}]},
{n:"Western Swing",bpm:130,bars:8,p:[
{t:0,l:4},{t:.67,l:3},{t:1,l:1},{t:1.5,l:0},{t:1.67,l:3},
{t:2,l:4},{t:2.33,l:0},{t:2.67,l:3},{t:3,l:1},{t:3.67,l:3}]},
{n:"Southern Rock",bpm:125,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1,l:3},{t:1.5,l:0},
{t:2,l:4},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:0}]},
{n:"Rockabilly",bpm:155,bars:8,p:[
{t:0,l:4},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2.25,l:4},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"Outlaw",bpm:110,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1,l:3},
{t:1.5,l:2},{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"New Country",bpm:118,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1.5,l:3},
{t:2,l:4},{t:2,l:2},{t:2.5,l:0},{t:3,l:1},{t:3.5,l:3}]},
{n:"Gospel",bpm:88,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:1,l:1},{t:1,l:3},{t:1.5,l:0},
{t:2,l:4},{t:2,l:2},{t:2.5,l:2},{t:3,l:1},{t:3,l:0},{t:3.5,l:3}]},
{n:"Americana",bpm:95,bars:8,p:[
{t:0,l:4},{t:.5,l:2},{t:1,l:1},{t:1.5,l:0},
{t:1.5,l:4},{t:2,l:3},{t:2.5,l:2},{t:3,l:1},{t:3.5,l:0}]},
{n:"Texas Blues",bpm:78,bars:8,p:[
{t:0,l:4},{t:.67,l:2},{t:1,l:1},{t:1.5,l:0},{t:1.67,l:2},
{t:2,l:4},{t:2.33,l:0},{t:2.67,l:2},{t:3,l:1},{t:3.67,l:2}]},
{n:"Cajun",bpm:115,bars:8,p:[
{t:0,l:4},{t:.67,l:2},{t:1,l:1},{t:1.33,l:0},{t:1.67,l:2},
{t:2,l:4},{t:2.33,l:3},{t:2.67,l:2},{t:3,l:1},{t:3.33,l:0}]},
{n:"Barn Dance",bpm:138,bars:8,p:[
{t:0,l:4},{t:0,l:2},{t:.5,l:0},{t:1,l:1},{t:1,l:3},
{t:1.5,l:2},{t:2,l:4},{t:2.5,l:0},{t:3,l:1},{t:3,l:0},{t:3.5,l:2}]},
],
};
// ── CLASSICAL MODE ────────────────────────────────────
const NT={C3:130.81,D3:146.83,Eb3:155.56,E3:164.81,F3:174.61,G3:196.00,Ab3:207.65,A3:220.00,Bb3:233.08,B3:246.94,C4:261.63,D4:293.66,Eb4:311.13,E4:329.63,F4:349.23,G4:392.00,Ab4:415.30,A4:440.00,Bb4:466.16,B4:493.88,C5:523.25,D5:587.33,Eb5:622.25,E5:659.25,F5:698.46,G5:783.99,A5:880.00,Gb3:185.00,Db4:277.18,Gb4:369.99,Db5:554.37,C6:1046.50};
function playSynth(ac,freq,t,dur,inst,vol,arr){
if(t<ac.currentTime-0.1)return;
var v=(vol||0.25)*3;
if(inst==='strings'){
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
o.type='sawtooth';o.frequency.value=freq;
f.type='lowpass';f.frequency.value=Math.min(freq*3,4000);
o.connect(f);f.connect(g);g.connect(ac.destination);
var att=Math.min(0.08,dur*0.15);
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v,t+att);
if(dur>att+0.05)g.gain.setValueAtTime(v*0.8,t+dur*0.7);
g.gain.linearRampToValueAtTime(0.001,t+dur);
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
}else if(inst==='brass'){
var o=ac.createOscillator(),f=ac.createBiquadFilter(),g=ac.createGain();
o.type='square';o.frequency.value=freq;
f.type='lowpass';f.frequency.value=Math.min(freq*2,3000);
o.connect(f);f.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.5,t+0.03);
if(dur>0.08)g.gain.setValueAtTime(v*0.5,t+dur*0.8);
g.gain.linearRampToValueAtTime(0.001,t+dur);
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
}else if(inst==='woodwind'){
var o=ac.createOscillator(),g=ac.createGain();
o.type='triangle';o.frequency.value=freq;
o.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.45,t+0.05);
if(dur>0.1)g.gain.setValueAtTime(v*0.45,t+dur*0.8);
g.gain.linearRampToValueAtTime(0.001,t+dur);
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
}else if(inst==='bass'){
var o=ac.createOscillator(),g=ac.createGain();
o.type='sine';o.frequency.value=freq;
o.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(v*0.6,t);
if(dur>0.08)g.gain.setValueAtTime(v*0.6,t+dur*0.8);
g.gain.linearRampToValueAtTime(0.001,t+dur);
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
}else if(inst==='timpani'){
var o=ac.createOscillator(),g=ac.createGain();
o.type='sine';o.connect(g);g.connect(ac.destination);
o.frequency.setValueAtTime(freq*1.5,t);
o.frequency.exponentialRampToValueAtTime(Math.max(freq*0.5,20),t+dur);
g.gain.setValueAtTime(v*0.8,t);
g.gain.exponentialRampToValueAtTime(0.001,t+dur);
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
}else if(inst==='choir'){
[-6,0,6].forEach(function(d){
var o=ac.createOscillator(),g=ac.createGain();
o.type='sine';o.frequency.value=freq+d;
o.connect(g);g.connect(ac.destination);
var at2=Math.min(0.1,dur*0.2);
g.gain.setValueAtTime(0.001,t);g.gain.linearRampToValueAtTime(v*0.3,t+at2);
if(dur>at2+0.05)g.gain.setValueAtTime(v*0.3,t+dur*0.8);
g.gain.linearRampToValueAtTime(0.001,t+dur);
o.start(t);o.stop(t+dur+0.02);if(arr)arr.push(o);
});
}
}
const CLASSICAL_PIECES=(function(){
var 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,G4=NT.G4,Ab4=NT.Ab4,A4=NT.A4,Bb4=NT.Bb4,
C5=NT.C5,D5=NT.D5,Eb5=NT.Eb5,E5=NT.E5,F5=NT.F5,G5=NT.G5,A5=NT.A5,
C3=NT.C3,D3=NT.D3,E3=NT.E3,B4=NT.B4,F3=NT.F3,Gb3=NT.Gb3,Db4=NT.Db4,Gb4=NT.Gb4,Db5=NT.Db5;
// ── Beethoven's 5th Symphony (Opening) ──
var b5=(function(){
var q=0.375,e=q/2,bg=[],mel=[];
function orch(t,freq,dur,v){
bg.push({t:t,f:freq,d:dur,i:'strings',v:v||0.35});
bg.push({t:t,f:freq,d:dur,i:'brass',v:(v||0.35)*0.6});
if(freq>200)bg.push({t:t,f:freq/2,d:dur,i:'bass',v:(v||0.35)*0.5});
}
function str(t,freq,dur,v){bg.push({t:t,f:freq,d:dur,i:'strings',v:v||0.3});}
// === EXPOSITION ===
// Section 1: G-G-G-Eb (THE famous motif, ff)
orch(0,G4,0.18,0.5);orch(q,G4,0.18,0.5);orch(q*2,G4,0.18,0.5);
orch(q*3,Eb4,1.8,0.55);
mel.push({t:0,l:0},{t:q,l:0},{t:q*2,l:0},{t:q*3,l:3});
// Section 2: F-F-F-D (answer phrase)
var s=3.2;
orch(s,F4,0.18,0.5);orch(s+q,F4,0.18,0.5);orch(s+q*2,F4,0.18,0.5);
orch(s+q*3,D4,1.8,0.55);
mel.push({t:s,l:1},{t:s+q,l:1},{t:s+q*2,l:1},{t:s+q*3,l:3});
// Section 3: Motif restated by strings (softer echo)
s=6.8;
str(s,G4,0.16,0.3);str(s+q,G4,0.16,0.3);str(s+q*2,G4,0.16,0.3);
str(s+q*3,Eb4,1.2,0.35);
mel.push({t:s,l:0},{t:s+q,l:0},{t:s+q*2,l:0},{t:s+q*3,l:3});
s=9.5;
str(s,F4,0.16,0.3);str(s+q,F4,0.16,0.3);str(s+q*2,F4,0.16,0.3);
str(s+q*3,D4,1.2,0.35);
mel.push({t:s,l:1},{t:s+q,l:1},{t:s+q*2,l:1},{t:s+q*3,l:3});
// === DEVELOPMENT ===
// Section 4: Faster motifs (compressed)
s=12.0;
orch(s,G4,0.15,0.45);orch(s+e,G4,0.15,0.45);orch(s+e*2,G4,0.15,0.45);
orch(s+e*3,Eb4,0.5,0.5);
mel.push({t:s,l:0},{t:s+e,l:0},{t:s+e*2,l:0},{t:s+e*3,l:3});
s=13.0;
orch(s,F4,0.15,0.45);orch(s+e,F4,0.15,0.45);orch(s+e*2,F4,0.15,0.45);
orch(s+e*3,D4,0.5,0.5);
mel.push({t:s,l:1},{t:s+e,l:1},{t:s+e*2,l:1},{t:s+e*3,l:3});
// Section 5: Extended scalar passage (descending then ascending)
s=14.2;
[G4,F4,Eb4,D4,C4,B3,C4,D4,Eb4,F4,G4,Ab4,Bb4,Ab4,G4,F4,Eb4,D4,C4,B3].forEach(function(f,i){
var t2=s+i*e;orch(t2,f,0.16,0.4);mel.push({t:t2,l:[2,1,0,1][i%4]});
});
// Section 6: Power chords with timpani
s=18.0;
[[G4,Eb4],[Ab4,F4],[Bb4,G4],[Ab4,F4],[G4,Eb4],[F4,D4],[Eb4,C4],[D4,B3]].forEach(function(ch,i){
var t2=s+i*0.7;ch.forEach(function(f){orch(t2,f,0.6,0.55);});
bg.push({t:t2,f:ch[1]/2,d:0.6,i:'timpani',v:0.5});mel.push({t:t2,l:4});
});
// === RECAPITULATION ===
// Section 7: Motif returns fortissimo with choir
s=24.0;
orch(s,G4,0.18,0.6);orch(s+q,G4,0.18,0.6);orch(s+q*2,G4,0.18,0.6);
orch(s+q*3,Eb4,1.5,0.65);bg.push({t:s+q*3,f:Eb4,d:1.5,i:'choir',v:0.3});
mel.push({t:s,l:0},{t:s+q,l:0},{t:s+q*2,l:0},{t:s+q*3,l:3});
s=27.0;
orch(s,F4,0.18,0.6);orch(s+q,F4,0.18,0.6);orch(s+q*2,F4,0.18,0.6);
orch(s+q*3,D4,1.5,0.65);bg.push({t:s+q*3,f:D4,d:1.5,i:'choir',v:0.3});
mel.push({t:s,l:1},{t:s+q,l:1},{t:s+q*2,l:1},{t:s+q*3,l:3});
// Section 8: Ascending scalar with full orchestra
s=30.0;
[C4,D4,Eb4,F4,G4,Ab4,Bb4,C5,D5,Eb5,D5,C5,Bb4,Ab4,G4,F4].forEach(function(f,i){
var t2=s+i*e;orch(t2,f,0.16,0.5);mel.push({t:t2,l:[0,1,2,3][i%4]});
});
// Section 9: Rapid motif at rising pitches
s=33.5;
[[G4,Eb4],[Ab4,F4],[Bb4,G4],[C5,Ab4],[D5,Bb4]].forEach(function(fr,fi){
var t2=s+fi*0.9;
orch(t2,fr[0],0.14,0.55);orch(t2+e,fr[0],0.14,0.55);orch(t2+e*2,fr[0],0.14,0.55);
orch(t2+e*3,fr[1],0.45,0.6);
mel.push({t:t2,l:0},{t:t2+e,l:0},{t:t2+e*2,l:0},{t:t2+e*3,l:3});
});
// Section 10: Dramatic sustained chords with choir
s=38.5;
[[C4,Eb4,G4],[Bb3,D4,F4],[Ab3,C4,Eb4],[G3,B3,D4]].forEach(function(ch,i){
var t2=s+i*1.5;
ch.forEach(function(f){orch(t2,f,1.3,0.6);bg.push({t:t2,f:f,d:1.3,i:'choir',v:0.25});});
bg.push({t:t2,f:NT.C3,d:1.3,i:'timpani',v:0.55});
mel.push({t:t2,l:4},{t:t2+0.75,l:i%2===0?0:1});
});
// Section 11: Final motif statement fff
s=45.0;
orch(s,G4,0.2,0.7);orch(s+q,G4,0.2,0.7);orch(s+q*2,G4,0.2,0.7);
orch(s+q*3,Eb4,2.0,0.7);
bg.push({t:s+q*3,f:Eb4,d:2.0,i:'choir',v:0.4});
bg.push({t:s,f:G3,d:0.2,i:'timpani',v:0.6});
mel.push({t:s,l:0},{t:s+q,l:0},{t:s+q*2,l:0},{t:s+q*3,l:3});
// Section 12: Final chords
s=48.5;
[0,0.5,1.0,1.5,2.0].forEach(function(dt){
var t2=s+dt;
[C4,Eb4,G4].forEach(function(f){orch(t2,f,0.4,0.7);});
bg.push({t:t2,f:NT.C3,d:0.4,i:'timpani',v:0.65});mel.push({t:t2,l:4});
});
// Very final long chord
s=51.0;
[C4,Eb4,G4,C5].forEach(function(f){orch(s,f,2.5,0.65);bg.push({t:s,f:f,d:2.5,i:'choir',v:0.35});});
bg.push({t:s,f:NT.C3,d:2.0,i:'timpani',v:0.6});
return {n:"Beethoven's 5th (Opening)",composer:"Beethoven",bpm:160,duration:54,isClassical:true,bg:bg,melody:mel};
})();
// ── Hall of the Mountain King ──
var hmk=(function(){
var bg=[],mel=[];
function orch(t,freq,dur,v,inst){bg.push({t:t,f:freq,d:dur,i:inst||'strings',v:v||0.3});}
var theme=[A3,B3,C4,D4,E4,C4,E4,Eb4,E4,Eb4,E4,D4,C4,B3];
var lanes=[0,1,2,0,1,2,1,3,1,3,1,0,2,1];
var t=0;
// Pass 1: Very slow, barely audible (creeping start)
var d0=0.55;
theme.forEach(function(f,i){
orch(t,f,d0*0.8,0.2,'woodwind');
mel.push({t:t,l:lanes[i]});t+=d0;
});
t+=0.5;
// Pass 2: Slow, a bit louder
var d1=0.45;
theme.forEach(function(f,i){
orch(t,f,d1*0.8,0.25,'woodwind');orch(t,f/2,d1*0.8,0.15,'bass');
mel.push({t:t,l:lanes[i]});t+=d1;
});
t+=0.4;
// Pass 3: Medium-slow, strings enter
var d2=0.35;
theme.forEach(function(f,i){
orch(t,f,d2*0.8,0.3,'strings');orch(t,f/2,d2*0.8,0.2,'bass');
mel.push({t:t,l:lanes[i]});t+=d2;
});
t+=0.35;
// Pass 4: Medium, strings + woodwind
var d3=0.27;
theme.forEach(function(f,i){
orch(t,f,d3*0.8,0.35,'strings');orch(t,f,d3*0.8,0.2,'woodwind');
orch(t,f/2,d3*0.8,0.25,'bass');mel.push({t:t,l:lanes[i]});t+=d3;
});
t+=0.3;
// Pass 5: Medium-fast, add brass
var d4=0.21;
theme.forEach(function(f,i){
orch(t,f,d4*0.8,0.4,'strings');orch(t,f,d4*0.8,0.25,'brass');
orch(t,f/2,d4*0.8,0.3,'bass');mel.push({t:t,l:lanes[i]});t+=d4;
});
t+=0.25;
// Pass 6: Fast, full orchestra
var d5=0.16;
theme.forEach(function(f,i){
orch(t,f,d5*0.8,0.5,'strings');orch(t,f,d5*0.8,0.35,'brass');
orch(t,f/2,d5*0.8,0.35,'bass');
if(i%4===0)bg.push({t:t,f:A3/2,d:0.3,i:'timpani',v:0.45});
mel.push({t:t,l:lanes[i]});t+=d5;
});
t+=0.2;
// Pass 7: Very fast, frantic
var d6=0.12;
theme.forEach(function(f,i){
orch(t,f,d6*0.8,0.6,'strings');orch(t,f,d6*0.8,0.45,'brass');
orch(t,f/2,d6*0.8,0.4,'bass');
if(i%2===0)bg.push({t:t,f:A3/2,d:0.2,i:'timpani',v:0.55});
mel.push({t:t,l:lanes[i]});t+=d6;
});
t+=0.15;
// Pass 8: Extremely fast, maximum intensity
var d7=0.095;
theme.forEach(function(f,i){
orch(t,f,d7*0.8,0.65,'strings');orch(t,f,d7*0.8,0.5,'brass');
orch(t,f/2,d7*0.8,0.45,'bass');
bg.push({t:t,f:A3/2,d:0.15,i:'timpani',v:0.6});
mel.push({t:t,l:lanes[i]});t+=d7;
});
// Big finale: repeated chords
t+=0.15;
for(var ci=0;ci<4;ci++){
var t2=t+ci*0.4;
[A3,C4,E4,A4].forEach(function(f){
orch(t2,f,0.35,0.65,'brass');orch(t2,f,0.35,0.55,'strings');
});
bg.push({t:t2,f:A3/2,d:0.4,i:'timpani',v:0.65});
mel.push({t:t2,l:4});
}
// Final long chord
t=t+1.8;
[A3,C4,E4,A4].forEach(function(f){
orch(t,f,2.0,0.6,'brass');orch(t,f,2.0,0.5,'strings');
});
bg.push({t:t,f:A3/2,d:1.5,i:'timpani',v:0.7});
mel.push({t:t,l:4});
return {n:"Hall of the Mountain King",composer:"Grieg",bpm:120,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── O Fortuna (Carmina Burana) ──
var ofort=(function(){
var bg=[],mel=[],q=0.6,h=q/2;
function orch(t,freq,dur,v){
bg.push({t:t,f:freq,d:dur,i:'strings',v:v||0.35});
bg.push({t:t,f:freq,d:dur,i:'brass',v:(v||0.35)*0.7});
}
function choir(t,freq,dur,v){bg.push({t:t,f:freq,d:dur,i:'choir',v:v||0.4});}
function timp(t,v){bg.push({t:t,f:110,d:0.5,i:'timpani',v:v||0.5});}
// Section 1: Opening chant "O Fortuna"
var t=0;
var ch1=[D4,D4,D4,D4,A3,A3,Bb3,Bb3,A3,A3];
var ch1d=[h,h,h,q,h,q,h,q,h,q*1.5];
var ch1l=[0,0,0,0,1,1,2,2,1,1];
ch1.forEach(function(f,i){
choir(t,f,ch1d[i]*0.9,0.45);orch(t,f,ch1d[i]*0.9,0.25);
if(ch1d[i]>=q)timp(t,0.4);
mel.push({t:t,l:ch1l[i]});t+=ch1d[i];
});
// Section 2: Second chant phrase
t+=0.4;
var ch2=[G3,G3,A3,Bb3,A3,G3,A3,A3];
var ch2d=[h,h,h,h,q,q,h,q*2];
var ch2l=[3,3,1,2,1,3,1,1];
ch2.forEach(function(f,i){
choir(t,f,ch2d[i]*0.9,0.45);orch(t,f,ch2d[i]*0.9,0.25);
if(ch2d[i]>=q)timp(t,0.4);
mel.push({t:t,l:ch2l[i]});t+=ch2d[i];
});
// Section 3: Ascending passage
t+=0.4;
var asc=[D4,E4,F4,E4,D4,D4,E4,F4,G4,A4];
var ascd=[h,h,h,h,q*1.5,h,h,h,h,q*2];
var ascl=[0,1,2,1,0,0,1,2,3,0];
asc.forEach(function(f,i){
choir(t,f,ascd[i]*0.9,0.5);orch(t,f,ascd[i]*0.9,0.35);
bg.push({t:t,f:f/2,d:ascd[i]*0.9,i:'bass',v:0.3});
if(ascd[i]>=q)timp(t,0.5);
mel.push({t:t,l:ascl[i]});t+=ascd[i];
});
// Section 4: Quiet interlude (strings only, mysterious)
t+=0.5;
var qi=[A3,Bb3,A3,G3,F3,G3,A3,Bb3,C4,D4];
var qid=[q,h,q,h,q,h,h,h,h,q*2];
var qil=[1,2,1,3,4,3,1,2,0,0];
qi.forEach(function(f,i){
bg.push({t:t,f:f,d:qid[i]*0.9,i:'strings',v:0.25});
bg.push({t:t,f:f/2,d:qid[i]*0.9,i:'bass',v:0.2});
mel.push({t:t,l:qil[i]});t+=qid[i];
});
// Section 5: Dramatic descent
t+=0.4;
var desc=[A4,G4,F4,E4,D4,E4,F4,G4,A4,A4,G4,F4,E4,D4];
var descd=[h,h,h,h,h,h,h,h,q,h,h,h,h,q*2];
var descl=[3,2,1,0,4,1,2,3,3,3,2,1,0,4];
desc.forEach(function(f,i){
choir(t,f,descd[i]*0.9,0.55);orch(t,f,descd[i]*0.9,0.4);
bg.push({t:t,f:f/2,d:descd[i]*0.9,i:'bass',v:0.35});
if(i%3===0)timp(t,0.55);
mel.push({t:t,l:descl[i]});t+=descd[i];
});
// Section 6: Second ascending at higher intensity
t+=0.4;
var a2=[D4,E4,F4,G4,A4,Bb4,A4,G4,A4,Bb4,C5,D5];
var a2d=[h,h,h,h,h,h,h,h,h,h,h,q*2];
var a2l=[0,1,2,3,0,2,0,3,0,2,1,4];
a2.forEach(function(f,i){
choir(t,f,a2d[i]*0.9,0.6);orch(t,f,a2d[i]*0.9,0.45);
bg.push({t:t,f:f/2,d:a2d[i]*0.9,i:'bass',v:0.4});
if(i%2===0)timp(t,0.55);
mel.push({t:t,l:a2l[i]});t+=a2d[i];
});
// Section 7: Extended climactic reprise
t+=0.5;
for(var i=0;i<12;i++){
var f=i<4?D4:i<8?D5:A4;
choir(t,f,q*0.9,0.6);orch(t,f,q*0.9,0.55);
bg.push({t:t,f:NT.D3,d:q*0.9,i:'bass',v:0.4});
timp(t,0.6);mel.push({t:t,l:i<4?0:i<8?4:i%2===0?0:3});t+=q;
}
// Section 8: Final massive chord sequence
t+=0.3;
[D4,F4,A4,D5].forEach(function(f){
bg.push({t:t,f:f,d:1.5,i:'choir',v:0.55});
bg.push({t:t,f:f,d:1.5,i:'strings',v:0.5});
bg.push({t:t,f:f,d:1.5,i:'brass',v:0.45});
});
bg.push({t:t,f:NT.D3,d:1.5,i:'bass',v:0.5});
timp(t,0.65);mel.push({t:t,l:4});
t+=2.0;
// Final D unison
[D4,D5].forEach(function(f){
bg.push({t:t,f:f,d:2.0,i:'choir',v:0.5});
bg.push({t:t,f:f,d:2.0,i:'brass',v:0.5});
});
bg.push({t:t,f:NT.D3,d:2.0,i:'bass',v:0.55});
timp(t,0.7);mel.push({t:t,l:4});
return {n:"O Fortuna",composer:"Orff",bpm:120,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// Theme-variation helper: repeats a theme across passes with growing intensity
function tp(name,comp,bpm,th,ln,passes,br){
var bg=[],mel=[],t=0,root=th[0];
passes.forEach(function(p,pi){
th.forEach(function(f,ni){
bg.push({t:t,f:f,d:p.d*0.85,i:p.i,v:p.v});
if(pi>=Math.floor(passes.length*0.4))bg.push({t:t,f:f/2,d:p.d*0.85,i:'bass',v:p.v*0.4});
if(pi>=Math.floor(passes.length*0.6)){
bg.push({t:t,f:f,d:p.d*0.85,i:pi>=Math.floor(passes.length*0.75)?'brass':'strings',v:p.v*0.5});
if(ni%4===0)bg.push({t:t,f:root/2,d:0.25,i:'timpani',v:p.v*0.5});
}
mel.push({t:t,l:ln[ni%ln.length]});t+=p.d;
});
t+=0.35;
if(br&&pi===Math.floor(passes.length/2)-1){
br.forEach(function(bf){bg.push({t:t,f:bf,d:1.0,i:'brass',v:0.5});bg.push({t:t,f:bf,d:1.0,i:'strings',v:0.4});bg.push({t:t,f:root/2,d:0.8,i:'timpani',v:0.5});mel.push({t:t,l:4});t+=1.15;});
}
});
for(var c=0;c<3;c++){bg.push({t:t,f:root,d:0.4,i:'brass',v:0.65});bg.push({t:t,f:root*3/2,d:0.4,i:'brass',v:0.6});bg.push({t:t,f:root*2,d:0.4,i:'strings',v:0.55});bg.push({t:t,f:root/2,d:0.4,i:'timpani',v:0.6});mel.push({t:t,l:4});t+=0.5;}
bg.push({t:t,f:root,d:2.0,i:'brass',v:0.6});bg.push({t:t,f:root*2,d:2.0,i:'strings',v:0.5});bg.push({t:t,f:root/2,d:1.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:name,composer:comp,bpm:bpm,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
}
// ── Ride of the Valkyries ──
var valk=tp("Ride of the Valkyries","Wagner",140,
[Bb3,Bb3,D4,D4,D4,F4,F4,F4,Bb4,Bb4,F4,D4,Bb3,D4,F4,Bb4,F4,D4,Bb3,F3],
[0,0,1,1,1,2,2,2,3,3,2,1,0,1,2,3,2,1,0,4],
[{d:0.30,v:0.25,i:'woodwind'},{d:0.27,v:0.3,i:'strings'},{d:0.24,v:0.38,i:'strings'},{d:0.21,v:0.45,i:'brass'},{d:0.18,v:0.52,i:'brass'},{d:0.15,v:0.6,i:'brass'}],
[Bb3,D4,F4,Bb4]);
// ── William Tell Overture (Gallop) ──
var tell=tp("William Tell Overture (Gallop)","Rossini",152,
[G4,G4,B4,G4,G4,B4,D5,D5,B4,G4,E4,G4,B4,D5,B4,G4,E4,D4,E4,G4],
[0,0,3,0,0,3,1,1,3,0,2,0,3,1,3,0,2,1,2,0],
[{d:0.28,v:0.22,i:'strings'},{d:0.25,v:0.28,i:'strings'},{d:0.22,v:0.35,i:'strings'},{d:0.20,v:0.42,i:'strings'},{d:0.18,v:0.48,i:'brass'},{d:0.16,v:0.54,i:'brass'},{d:0.14,v:0.58,i:'brass'},{d:0.12,v:0.62,i:'brass'}],
[G4,B4,D5,G5]);
// ── Bolero ──
var bolero=tp("Bolero","Ravel",72,
[C4,D4,E4,F4,E4,D4,C4,C4,D4,E4,F4,G4,F4,E4,D4,C4,E4,G4,E4,C4],
[0,1,2,3,2,1,0,0,1,2,3,0,3,2,1,0,2,0,2,0],
[{d:0.38,v:0.15,i:'woodwind'},{d:0.36,v:0.2,i:'woodwind'},{d:0.34,v:0.28,i:'strings'},{d:0.32,v:0.35,i:'strings'},{d:0.30,v:0.42,i:'strings'},{d:0.28,v:0.5,i:'brass'},{d:0.26,v:0.58,i:'brass'}],
[C4,E4,G4,C5]);
// ── Barber of Seville Overture ──
var barber=tp("Barber of Seville Overture","Rossini",144,
[G4,A4,B4,G4,E4,G4,A4,B4,C5,B4,A4,G4,E4,D4,E4,G4,B4,D5,C5,B4],
[0,1,3,0,2,0,1,3,0,3,1,0,2,1,2,0,3,1,0,3],
[{d:0.24,v:0.22,i:'strings'},{d:0.22,v:0.3,i:'strings'},{d:0.20,v:0.38,i:'strings'},{d:0.18,v:0.42,i:'strings'},{d:0.16,v:0.48,i:'brass'},{d:0.14,v:0.54,i:'brass'},{d:0.12,v:0.6,i:'brass'}],
[G4,B4,D5,G5]);
// ── Sabre Dance ──
var sabre=tp("Sabre Dance","Khachaturian",168,
[D4,D4,D4,Eb4,D4,D4,D4,Eb4,D4,E4,F4,G4,A4,G4,F4,E4,D4,D4,D4,A3],
[0,0,0,1,0,0,0,1,0,2,3,0,1,0,3,2,0,0,0,4],
[{d:0.24,v:0.28,i:'strings'},{d:0.22,v:0.34,i:'strings'},{d:0.20,v:0.4,i:'strings'},{d:0.18,v:0.46,i:'brass'},{d:0.16,v:0.52,i:'brass'},{d:0.14,v:0.56,i:'brass'},{d:0.12,v:0.6,i:'brass'},{d:0.10,v:0.65,i:'brass'}],
[D4,F4,A4,D5]);
// ── Sorcerer's Apprentice ──
var sorcerer=tp("Sorcerer's Apprentice","Dukas",132,
[A3,E4,C4,E4,A3,E4,C4,E4,A3,B3,C4,D4,E4,D4,C4,B3,A3,C4,E4,A4],
[0,2,1,2,0,2,1,2,0,1,2,0,2,0,2,1,0,1,2,3],
[{d:0.28,v:0.2,i:'woodwind'},{d:0.26,v:0.28,i:'woodwind'},{d:0.24,v:0.35,i:'strings'},{d:0.22,v:0.42,i:'strings'},{d:0.20,v:0.5,i:'brass'},{d:0.18,v:0.58,i:'brass'}],
[A3,C4,E4,A4]);
// ── Morning Mood ──
var morning=tp("Morning Mood","Grieg",60,
[F4,A4,C5,A4,F4,A4,C5,E5,F5,E5,C5,A4,F4,G4,A4,Bb4,C5,Bb4,A4,F4],
[0,1,2,1,0,1,2,3,0,3,2,1,0,1,1,2,2,2,1,0],
[{d:0.45,v:0.15,i:'woodwind'},{d:0.42,v:0.2,i:'woodwind'},{d:0.39,v:0.28,i:'woodwind'},{d:0.36,v:0.35,i:'strings'},{d:0.33,v:0.42,i:'strings'},{d:0.30,v:0.5,i:'brass'}],
[F4,A4,C5,F5]);
// ── Mars, Bringer of War ──
var mars=(function(){
var bg=[],mel=[],t=0,q=0.38;
// 5/4 ostinato: 20 bars of relentless 5-beat march
for(var bar=0;bar<20;bar++){
var v=0.2+bar*0.022;
var inst=bar<7?'strings':bar<14?'brass':'brass';
for(var beat=0;beat<5;beat++){
var bt=t+beat*q;
bg.push({t:bt,f:G3,d:q*0.7,i:'timpani',v:v*0.8});
bg.push({t:bt,f:G3,d:q*0.7,i:'bass',v:v*0.6});
if(bar>3)bg.push({t:bt,f:beat<3?G4:D4,d:q*0.7,i:inst,v:v*0.4});
mel.push({t:bt,l:beat<3?[0,1,2][beat]:beat===3?3:4});
}
if(bar>=4&&bar%2===0){
var mf=bar%4===0?[G4,Bb4,D5,C5,Bb4]:[D5,C5,Bb4,A4,G4];
mf.forEach(function(f,i){bg.push({t:t+i*q,f:f,d:q*1.5,i:inst,v:v*0.6});});
}
t+=5*q;
}
[G3,Bb3,D4,G4].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'brass',v:0.65});});
bg.push({t:t,f:G3/2,d:2.0,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Mars, Bringer of War",composer:"Holst",bpm:126,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Toccata & Fugue in D Minor ──
var toccata=(function(){
var bg=[],mel=[],t=0;
function org(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});bg.push({t:t,f:f,d:d,i:'woodwind',v:(v||0.4)*0.5});}
// Opening trill: A-Ab-A
org(0,A4,0.12,0.55);org(0.15,Ab4,0.12,0.55);org(0.3,A4,0.9,0.6);
mel.push({t:0,l:0},{t:0.15,l:1},{t:0.3,l:0});
// Descending: G-F-E-D
[G4,F4,E4,D4].forEach(function(f,i){org(1.3+i*0.4,f,0.35,0.55);mel.push({t:1.3+i*0.4,l:[3,2,1,0][i]});});
// Lower octave statement
org(3.2,A3,0.12,0.55);org(3.35,Ab3,0.12,0.55);org(3.5,A3,0.9,0.6);
mel.push({t:3.2,l:0},{t:3.35,l:1},{t:3.5,l:0});
[G3,F3,E3,D3].forEach(function(f,i){org(4.5+i*0.4,f,0.35,0.55);bg.push({t:4.5+i*0.4,f:f,d:0.35,i:'bass',v:0.45});mel.push({t:4.5+i*0.4,l:[3,2,1,4][i]});});
// D minor chord
t=6.2;
[D3,A3,D4,F4,A4,D5].forEach(function(f){org(t,f,2.0,0.65);});
bg.push({t:t,f:D3/2,d:1.5,i:'timpani',v:0.6});mel.push({t:t,l:4});
// Ascending scale
t=8.5;
[D4,E4,F4,G4,A4,Bb4,C5,D5].forEach(function(f,i){org(t,f,0.2,0.5);mel.push({t:t,l:[0,1,2,3][i%4]});t+=0.22;});
// Descending chromatic
[D5,C5,Bb4,A4,Ab4,G4,F4,E4,Eb4,D4].forEach(function(f,i){org(t,f,0.18,0.5);mel.push({t:t,l:[1,2,3,0][i%4]});t+=0.2;});
// Big chord
[D3,A3,D4,F4,A4].forEach(function(f){org(t,f,1.5,0.6);});
bg.push({t:t,f:D3/2,d:1.2,i:'timpani',v:0.55});mel.push({t:t,l:4});t+=1.8;
// Fugue: 6 entries accelerating
var subj=[D4,E4,F4,G4,A4,Bb4,A4,G4,F4,E4,D4,A3,D4];
var sLn=[0,1,2,3,0,2,0,3,2,1,0,4,0];
var speeds=[0.30,0.26,0.23,0.20,0.17,0.14];
var vols=[0.3,0.35,0.4,0.48,0.55,0.62];
speeds.forEach(function(spd,si){
subj.forEach(function(f,i){
org(t,f,spd*0.85,vols[si]);
if(si>1)bg.push({t:t,f:f/2,d:spd*0.85,i:'bass',v:vols[si]*0.5});
if(si>3)bg.push({t:t,f:f,d:spd*0.85,i:'strings',v:vols[si]*0.4});
if(si>3&&i%3===0)bg.push({t:t,f:D3,d:0.3,i:'timpani',v:vols[si]*0.6});
mel.push({t:t,l:sLn[i]});t+=spd;
});
t+=0.4;
});
// Final cascade
[D5,C5,Bb4,A4,G4,F4,E4,D4,C4,Bb3,A3,G3].forEach(function(f,i){
org(t,f,0.13,0.6);bg.push({t:t,f:f,d:0.13,i:'strings',v:0.4});mel.push({t:t,l:[0,2,3,1][i%4]});t+=0.14;
});
t+=0.2;
[D3,A3,D4,F4,A4,D5].forEach(function(f){org(t,f,3.0,0.7);});
bg.push({t:t,f:D3/2,d:2.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Toccata & Fugue in D Minor",composer:"Bach",bpm:100,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Also Sprach Zarathustra ──
var zarath=(function(){
var bg=[],mel=[],t=0;
// Timpani rumble + pulse
bg.push({t:0,f:C3,d:4.0,i:'timpani',v:0.1});
bg.push({t:0,f:C3,d:8.0,i:'bass',v:0.08});
[0.5,1.5,3.0,4.5].forEach(function(pt){bg.push({t:pt,f:C3,d:0.4,i:'timpani',v:0.15});mel.push({t:pt,l:4});});
// C note emerges
t=2.0;bg.push({t:t,f:C4,d:3.0,i:'brass',v:0.15});mel.push({t:t,l:0});
// G note
t=5.0;bg.push({t:t,f:G4,d:3.0,i:'brass',v:0.25});bg.push({t:t,f:C4,d:3.0,i:'strings',v:0.15});mel.push({t:t,l:1});
// High C — the sunrise!
t=8.0;
bg.push({t:t,f:C5,d:4.0,i:'brass',v:0.5});bg.push({t:t,f:G4,d:4.0,i:'brass',v:0.4});
bg.push({t:t,f:C4,d:4.0,i:'strings',v:0.3});bg.push({t:t,f:C3,d:4.0,i:'timpani',v:0.5});
mel.push({t:t,l:3});
// Big C major chord
t=12.0;
[C3,G3,C4,E4,G4,C5].forEach(function(f){bg.push({t:t,f:f,d:3.0,i:'brass',v:0.6});bg.push({t:t,f:f,d:3.0,i:'strings',v:0.45});});
bg.push({t:t,f:C3/2,d:2.5,i:'timpani',v:0.65});mel.push({t:t,l:4});
// Second statement: faster and louder
t=16.0;bg.push({t:t,f:C4,d:1.5,i:'brass',v:0.35});mel.push({t:t,l:0});
t=17.5;bg.push({t:t,f:G4,d:1.5,i:'brass',v:0.45});mel.push({t:t,l:1});
t=19.0;bg.push({t:t,f:C5,d:2.0,i:'brass',v:0.55});bg.push({t:t,f:G4,d:2.0,i:'brass',v:0.45});
bg.push({t:t,f:C3,d:2.0,i:'timpani',v:0.55});mel.push({t:t,l:3});
t=21.0;
[C3,G3,C4,E4,G4,C5].forEach(function(f){bg.push({t:t,f:f,d:3.0,i:'brass',v:0.65});});
bg.push({t:t,f:C3/2,d:2.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
// Development: ascending scale
t=25.0;
[C4,D4,E4,F4,G4,A4,B4,C5,D5,E5].forEach(function(f,i){
bg.push({t:t,f:f,d:0.5,i:'strings',v:0.4+i*0.02});bg.push({t:t,f:f,d:0.5,i:'brass',v:0.3+i*0.02});
mel.push({t:t,l:[0,1,2,3,0][i%5]});t+=0.55;
});
// Descending
[E5,D5,C5,B4,A4,G4,F4,E4,D4,C4].forEach(function(f,i){
bg.push({t:t,f:f,d:0.5,i:'strings',v:0.5});bg.push({t:t,f:f,d:0.5,i:'brass',v:0.4});
mel.push({t:t,l:[3,2,1,0,4][i%5]});t+=0.5;
});
// Third sunrise: biggest
t+=0.5;bg.push({t:t,f:C3,d:2.0,i:'timpani',v:0.4});
bg.push({t:t,f:C4,d:1.0,i:'brass',v:0.5});mel.push({t:t,l:0});t+=1.0;
bg.push({t:t,f:G4,d:1.0,i:'brass',v:0.55});mel.push({t:t,l:1});t+=1.0;
bg.push({t:t,f:C5,d:1.5,i:'brass',v:0.6});mel.push({t:t,l:3});t+=1.5;
// Final massive chord
[C3,G3,C4,E4,G4,C5,E5,G5].forEach(function(f){bg.push({t:t,f:f,d:4.0,i:'brass',v:0.7});bg.push({t:t,f:f,d:4.0,i:'choir',v:0.4});});
bg.push({t:t,f:C3/2,d:3.0,i:'timpani',v:0.75});mel.push({t:t,l:4});
return {n:"Also Sprach Zarathustra",composer:"R. Strauss",bpm:60,duration:Math.ceil(t+4.5),isClassical:true,bg:bg,melody:mel};
})();
// ── The Blue Danube ──
var danube=(function(){
var bg=[],mel=[],t=0,q=0.5;
// Intro: gentle ascending
[C4,E4,G4,C5,E5,C5,G4,E4].forEach(function(f,i){
bg.push({t:t,f:f,d:q*2,i:'strings',v:0.25});mel.push({t:t,l:[0,2,3,0,2,0,3,2][i]});t+=q*1.5;
});
t+=0.5;
// Theme 1: the famous waltz melody
var th1=[[E4,q,2],[E4,q*2,2],[G4,q,3],[G4,q*2,3],[F4,q,1],[F4,q,1],[E4,q,2],[D4,q*2,0],[C4,q,0],[E4,q,2],[G4,q*2,3],[C5,q*3,0]];
for(var r1=0;r1<2;r1++){
var v1=0.3+r1*0.1;
th1.forEach(function(n){bg.push({t:t,f:n[0],d:n[1]*0.9,i:r1<1?'strings':'brass',v:v1});mel.push({t:t,l:n[2]});t+=n[1];});
t+=0.4;
}
// Theme 2: contrasting phrase
var th2=[[G4,q,3],[A4,q,1],[G4,q,3],[E4,q*2,2],[C4,q,0],[D4,q,0],[E4,q,2],[F4,q,1],[E4,q*2,2],[D4,q,0],[C4,q*3,0]];
for(var r2=0;r2<2;r2++){
var v2=0.4+r2*0.1;
th2.forEach(function(n){
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'strings',v:v2});
if(r2>0)bg.push({t:t,f:n[0],d:n[1]*0.9,i:'brass',v:v2*0.5});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=0.4;
}
// Recap theme 1 with full orchestra
th1.forEach(function(n){
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'brass',v:0.55});bg.push({t:t,f:n[0],d:n[1]*0.9,i:'strings',v:0.45});
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.35});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=0.4;
// Finale waltz chords
for(var c=0;c<4;c++){
[C4,E4,G4,C5].forEach(function(f){bg.push({t:t,f:f,d:q*2.5,i:'brass',v:0.6});bg.push({t:t,f:f,d:q*2.5,i:'strings',v:0.5});});
mel.push({t:t,l:4});t+=q*3;
}
[C4,E4,G4,C5].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'brass',v:0.6});bg.push({t:t,f:f,d:2.5,i:'strings',v:0.5});});
bg.push({t:t,f:C3,d:2.0,i:'timpani',v:0.65});mel.push({t:t,l:4});
return {n:"The Blue Danube",composer:"J. Strauss II",bpm:180,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Night on Bald Mountain ──
var bald=(function(){
var bg=[],mel=[],t=0,q=0.35;
function dark(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});bg.push({t:t,f:f,d:d,i:'strings',v:(v||0.4)*0.8});bg.push({t:t,f:f/2,d:d,i:'bass',v:(v||0.4)*0.5});}
// Ominous opening
bg.push({t:0,f:D3,d:3.0,i:'timpani',v:0.2});bg.push({t:0,f:D3,d:3.0,i:'bass',v:0.15});
t=1.0;
// Dark ascending chromatic
[D4,Eb4,E4,F4,G4,Ab4,A4,Bb4,B4,C5,D5].forEach(function(f,i){
dark(t,f,q*0.8,0.25+i*0.02);mel.push({t:t,l:[0,1,2,3,0,1,2,3,0,1,4][i]});t+=q;
});
t+=0.5;
// Dark sustained chords
[[D4,F4],[A3,E4],[Bb3,D4],[A3,C4],[D4,F4],[A4,D5]].forEach(function(ch){
ch.forEach(function(f){dark(t,f,1.0,0.4);});mel.push({t:t,l:4});t+=1.1;
});
// Witches' dance: 7 passes accelerating
var witch=[D4,F4,A4,D5,A4,F4,D4,E4,F4,G4,A4,Bb4,A4,G4,F4,E4,D4,A3,D4,F4];
var wLn=[0,2,0,1,0,2,0,1,2,3,0,2,0,3,2,1,0,4,0,2];
[0.26,0.24,0.22,0.20,0.18,0.16,0.14].forEach(function(spd,si){
var wv=0.3+si*0.05;
witch.forEach(function(f,i){
dark(t,f,spd*0.85,wv);
if(si>3&&i%4===0)bg.push({t:t,f:D3,d:0.3,i:'timpani',v:wv*0.7});
mel.push({t:t,l:wLn[i]});t+=spd;
});
t+=0.3;
});
// Massive ending
for(var c=0;c<4;c++){
[D3,A3,D4,F4,A4].forEach(function(f){dark(t,f,0.4,0.65);});
bg.push({t:t,f:D3/2,d:0.4,i:'timpani',v:0.65});mel.push({t:t,l:4});t+=0.5;
}
[D3,A3,D4,F4,A4,D5].forEach(function(f){dark(t,f,2.5,0.65);});
bg.push({t:t,f:D3/2,d:2.0,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Night on Bald Mountain",composer:"Mussorgsky",bpm:152,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Swan Lake (Theme) ──
var swan=(function(){
var bg=[],mel=[],t=0,q=0.55;
var theme=[A4,B4,C5,B4,A4,E4,A4,G4,F4,E4,D4,E4,F4,E4,D4,C4,B3,A3,B3,C4,D4,E4,A4];
var tLn=[0,1,2,1,0,2,0,3,2,1,0,1,2,1,0,2,1,0,1,2,0,2,0];
for(var rep=0;rep<3;rep++){
var v=0.25+rep*0.12;
var inst=rep===0?'woodwind':rep===1?'strings':'brass';
theme.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.85,i:inst,v:v});
if(rep>0)bg.push({t:t,f:f/2,d:q*0.85,i:'bass',v:v*0.4});
if(rep>1)bg.push({t:t,f:f,d:q*0.85,i:'strings',v:v*0.5});
if(i%3===0){bg.push({t:t,f:A3,d:q*2,i:'bass',v:v*0.3});}
mel.push({t:t,l:tLn[i]});t+=q;
});
t+=0.5;
}
for(var c=0;c<3;c++){
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:q*2.5,i:'strings',v:0.55});bg.push({t:t,f:f,d:q*2.5,i:'brass',v:0.45});});
mel.push({t:t,l:4});t+=q*3;
}
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'strings',v:0.55});});
bg.push({t:t,f:A3/2,d:2.0,i:'timpani',v:0.6});mel.push({t:t,l:4});
return {n:"Swan Lake (Theme)",composer:"Tchaikovsky",bpm:110,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Flight of the Bumblebee ──
var bumblebee=tp("Flight of the Bumblebee","Rimsky-Korsakov",180,
[E5,Eb5,D5,C5,B4,Bb4,A4,Ab4,G4,Gb4,F4,E4,F4,Gb4,G4,Ab4,A4,Bb4,B4,C5,D5,Eb5],
[2,1,0,2,3,2,1,0,3,2,1,0,1,2,3,0,1,2,3,2,0,1],
[{d:0.20,v:0.2,i:'strings'},{d:0.16,v:0.26,i:'strings'},{d:0.14,v:0.32,i:'strings'},{d:0.12,v:0.38,i:'strings'},{d:0.11,v:0.44,i:'strings'},{d:0.10,v:0.48,i:'strings'},{d:0.09,v:0.52,i:'strings'},{d:0.08,v:0.56,i:'strings'},{d:0.07,v:0.6,i:'strings'}],
[E4,Ab4,B4,E5]);
// ── Dance of the Sugar Plum Fairy ──
var sugarplum=(function(){
var bg=[],mel=[],t=0,q=0.4;
var theme=[E4,Eb4,E4,B3,E4,Eb4,E4,B3,E4,F4,E4,D4,C4,B3,A3,B3];
var tLn=[2,1,2,3,2,1,2,3,2,1,2,0,1,3,0,3];
var ans=[E4,G4,B4,E5,B4,G4,E4,B3];
var aLn=[2,3,0,2,0,3,2,3];
for(var rep=0;rep<5;rep++){
var v=0.2+rep*0.08;
var inst=rep<2?'woodwind':rep<4?'strings':'brass';
theme.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.5,i:inst,v:v});
if(rep>1)bg.push({t:t,f:f/2,d:q*0.5,i:'bass',v:v*0.3});
if(rep>3)bg.push({t:t,f:f,d:q*0.5,i:'strings',v:v*0.4});
mel.push({t:t,l:tLn[i]});t+=q;
});
t+=0.5;
ans.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.4,i:inst,v:v*0.8});
mel.push({t:t,l:aLn[i]});t+=q*0.8;
});
t+=0.4;
}
[E4,G4,B4,E5].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'strings',v:0.5});});
bg.push({t:t,f:E3,d:1.5,i:'bass',v:0.4});mel.push({t:t,l:4});
return {n:"Dance of the Sugar Plum Fairy",composer:"Tchaikovsky",bpm:120,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── 1812 Overture (Finale) ──
var overture1812=(function(){
var bg=[],mel=[],t=0,q=0.4;
function brass(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.5});bg.push({t:t,f:f,d:d,i:'strings',v:(v||0.5)*0.7});}
function cannon(t,v){bg.push({t:t,f:60,d:0.8,i:'timpani',v:v||0.7});}
var theme=[G4,G4,A4,B4,B4,A4,G4,Gb4,E4,E4,Gb4,G4,G4,Gb4,Gb4];
var tLn=[0,0,1,3,3,1,0,2,1,1,2,0,0,2,2];
for(var rep=0;rep<5;rep++){
var v=0.3+rep*0.08;
theme.forEach(function(f,i){
brass(t,f,q*0.9,v);
if(rep>0)bg.push({t:t,f:f/2,d:q*0.9,i:'bass',v:v*0.5});
if(rep>2)bg.push({t:t,f:f,d:q*0.9,i:'choir',v:v*0.3});
mel.push({t:t,l:tLn[i]});t+=q;
});
cannon(t,0.4+rep*0.08);mel.push({t:t,l:4});t+=0.8;
cannon(t,0.45+rep*0.08);mel.push({t:t,l:4});t+=0.8;
t+=0.3;
}
for(var c=0;c<8;c++){
[G3,B3,D4,G4].forEach(function(f){brass(t,f,0.4,0.65);});
cannon(t,0.7);mel.push({t:t,l:4});t+=0.5;
}
[G3,B3,D4,G4,B4,D5].forEach(function(f){brass(t,f,3.0,0.7);bg.push({t:t,f:f,d:3.0,i:'choir',v:0.4});});
cannon(t,0.8);mel.push({t:t,l:4});
return {n:"1812 Overture (Finale)",composer:"Tchaikovsky",bpm:120,duration:Math.ceil(t+3.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Habanera (Carmen) ──
var habanera=tp("Habanera (Carmen)","Bizet",108,
[D5,Db5,C5,B4,Bb4,A4,Ab4,G4,Gb4,F4,E4,F4,Gb4,G4,Ab4,A4,Bb4,A4,Ab4,G4],
[0,1,2,3,2,1,0,3,2,1,0,1,2,3,0,1,2,1,0,3],
[{d:0.32,v:0.2,i:'strings'},{d:0.30,v:0.28,i:'strings'},{d:0.28,v:0.35,i:'strings'},{d:0.26,v:0.42,i:'brass'},{d:0.24,v:0.48,i:'brass'},{d:0.22,v:0.55,i:'brass'}],
[D4,F4,A4,D5]);
// ── Spring (Four Seasons) ──
var spring=tp("Spring (Four Seasons)","Vivaldi",120,
[F4,A4,C5,A4,F4,G4,A4,Bb4,C5,D5,C5,Bb4,A4,G4,F4,E4,F4,G4,A4,C5],
[0,1,2,1,0,3,1,2,2,0,2,2,1,3,0,2,0,3,1,2],
[{d:0.24,v:0.22,i:'strings'},{d:0.22,v:0.28,i:'strings'},{d:0.20,v:0.35,i:'strings'},{d:0.18,v:0.42,i:'strings'},{d:0.16,v:0.48,i:'brass'},{d:0.14,v:0.54,i:'brass'},{d:0.12,v:0.6,i:'brass'}],
[F4,A4,C5,F5]);
// ── Für Elise ──
var furelise=(function(){
var bg=[],mel=[],t=0,q=0.35;
var motif=[E4,Eb4,E4,Eb4,E4,B3,D4,C4,A3];
var mLn=[2,1,2,1,2,3,0,2,0];
var ph2=[C4,E4,A4,B4,E4,C5,B4,A4];
var p2Ln=[1,2,0,3,2,0,3,0];
for(var rep=0;rep<5;rep++){
var v=0.2+rep*0.08;
var inst=rep<2?'woodwind':rep<3?'strings':'brass';
motif.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.7,i:inst,v:v});
if(rep>1)bg.push({t:t,f:f/2,d:q*0.7,i:'bass',v:v*0.3});
mel.push({t:t,l:mLn[i]});t+=q;
});
t+=0.2;
ph2.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.7,i:inst,v:v});
if(rep>1)bg.push({t:t,f:f/2,d:q*0.7,i:'bass',v:v*0.3});
mel.push({t:t,l:p2Ln[i]});t+=q;
});
t+=0.2;
motif.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.7,i:inst,v:v});
if(rep>2)bg.push({t:t,f:f,d:q*0.7,i:'strings',v:v*0.4});
mel.push({t:t,l:mLn[i]});t+=q;
});
t+=0.5;
}
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'strings',v:0.45});});
bg.push({t:t,f:A3/2,d:2.0,i:'bass',v:0.35});mel.push({t:t,l:4});
return {n:"F\u00fcr Elise",composer:"Beethoven",bpm:130,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Canon in D ──
var canon=(function(){
var bg=[],mel=[],t=0,q=0.55;
var bassLine=[D3,A3,B3,Gb3,G3,D3,G3,A3];
var melody2=[A4,Gb4,G4,A4,Gb4,D4,Gb4,G4,A4,B4,G4,A4,Gb4,D4,E4,Gb4];
var m2Ln=[1,2,3,1,2,0,2,3,1,0,3,1,2,0,1,2];
for(var rep=0;rep<4;rep++){
var v=0.2+rep*0.08;
bassLine.forEach(function(f,i){
var bt=t+i*q*2;
bg.push({t:bt,f:f,d:q*1.8,i:'bass',v:v*0.5});
bg.push({t:bt,f:f*2,d:q*1.8,i:'strings',v:v*0.3});
});
if(rep>=1){
melody2.forEach(function(f,i){
bg.push({t:t+i*q,f:f,d:q*0.85,i:rep<3?'strings':'brass',v:v});
if(rep>2)bg.push({t:t+i*q,f:f,d:q*0.85,i:'choir',v:v*0.3});
mel.push({t:t+i*q,l:m2Ln[i]});
});
} else {
bassLine.forEach(function(f,i){mel.push({t:t+i*q*2,l:4});});
}
t+=bassLine.length*q*2;t+=0.3;
}
[D3,A3,D4,Gb4,A4,D5].forEach(function(f){bg.push({t:t,f:f,d:3.0,i:'strings',v:0.55});bg.push({t:t,f:f,d:3.0,i:'brass',v:0.4});});
bg.push({t:t,f:D3/2,d:2.5,i:'timpani',v:0.55});mel.push({t:t,l:4});
return {n:"Canon in D",composer:"Pachelbel",bpm:60,duration:Math.ceil(t+3.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Hungarian Dance No. 5 ──
var hungarian=(function(){
var bg=[],mel=[],t=0;
function orch(t,f,d,v,i){bg.push({t:t,f:f,d:d,i:i||'strings',v:v||0.4});}
var fast=[A4,G4,A4,E4,A4,G4,A4,E4,F4,E4,D4,C4,B3,A3,B3,C4,D4,E4,F4,E4];
var fLn=[0,3,0,2,0,3,0,2,1,2,0,1,3,0,3,1,0,2,1,2];
var slow=[A4,A4,G4,F4,E4,D4,E4,F4,G4,A4];
var sLn=[0,0,3,1,2,0,2,1,3,0];
// Fast section 1
[0.20,0.18].forEach(function(qf,qi){
var v=0.35+qi*0.1;
fast.forEach(function(f,i){
orch(t,f,qf*0.85,v);if(qi>0)orch(t,f,qf*0.85,v*0.6,'brass');
orch(t,f/2,qf*0.85,v*0.5,'bass');mel.push({t:t,l:fLn[i]});t+=qf;
});
t+=0.35;
});
t+=0.3;
// Slow section (dramatic)
[0.55,0.55,0.50].forEach(function(qs,qi){
var v=0.45+qi*0.05;
slow.forEach(function(f,i){
orch(t,f,qs*0.85,v,'brass');orch(t,f,qs*0.85,v*0.8);
bg.push({t:t,f:f/2,d:qs*0.85,i:'bass',v:v*0.5});
if(qi>0&&i%2===0)bg.push({t:t,f:A3/2,d:0.3,i:'timpani',v:v*0.6});
mel.push({t:t,l:sLn[i]});t+=qs;
});
t+=0.5;
});
t+=0.3;
// Fast section 2 (even faster!)
[0.15,0.13,0.11].forEach(function(qf,qi){
var v=0.5+qi*0.06;
fast.forEach(function(f,i){
orch(t,f,qf*0.85,v);orch(t,f,qf*0.85,v*0.7,'brass');
orch(t,f/2,qf*0.85,v*0.5,'bass');
if(i%3===0)bg.push({t:t,f:A3/2,d:0.2,i:'timpani',v:v*0.7});
mel.push({t:t,l:fLn[i]});t+=qf;
});
t+=0.3;
});
for(var c=0;c<4;c++){
[A3,C4,E4,A4].forEach(function(f){orch(t,f,0.35,0.65,'brass');});
bg.push({t:t,f:A3/2,d:0.35,i:'timpani',v:0.65});mel.push({t:t,l:4});t+=0.45;
}
[A3,C4,E4,A4].forEach(function(f){orch(t,f,2.0,0.6,'brass');orch(t,f,2.0,0.5);});
bg.push({t:t,f:A3/2,d:1.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Hungarian Dance No. 5",composer:"Brahms",bpm:140,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Ode to Joy ──
var odeToJoy=tp("Ode to Joy","Beethoven",100,
[E4,E4,F4,G4,G4,F4,E4,D4,C4,C4,D4,E4,E4,D4,D4,C4,C4,D4,E4,D4,C4,C4],
[2,2,1,3,3,1,2,0,0,0,1,2,2,0,0,0,0,1,2,0,0,0],
[{d:0.42,v:0.18,i:'woodwind'},{d:0.40,v:0.25,i:'strings'},{d:0.38,v:0.32,i:'strings'},{d:0.36,v:0.4,i:'brass'},{d:0.34,v:0.48,i:'brass'}],
[C4,E4,G4,C5]);
// ── William Tell Overture (Storm) ──
var tellStorm=(function(){
var bg=[],mel=[],t=0;
function dark(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});bg.push({t:t,f:f/2,d:d,i:'bass',v:(v||0.4)*0.5});}
var theme=[E4,E4,F4,E4,D4,C4,D4,E4,F4,G4,Ab4,G4,F4,E4,D4,C4];
var tLn=[2,2,1,2,0,1,0,2,1,3,0,3,1,2,0,1];
[0.30,0.27,0.24,0.21,0.18,0.16,0.14,0.12].forEach(function(spd,si){
var v=0.25+si*0.05;
theme.forEach(function(f,i){
dark(t,f,spd*0.85,v);
if(si>2)bg.push({t:t,f:f,d:spd*0.85,i:'brass',v:v*0.5});
if(si>3&&i%4===0)bg.push({t:t,f:C3,d:0.4,i:'timpani',v:v*0.8});
mel.push({t:t,l:tLn[i]});t+=spd;
});
t+=0.3;
bg.push({t:t,f:C3,d:0.6,i:'timpani',v:0.35+si*0.04});mel.push({t:t,l:4});t+=0.7;
});
for(var c=0;c<6;c++){
bg.push({t:t,f:C3,d:0.5,i:'timpani',v:0.65});
[C4,Eb4,G4].forEach(function(f){dark(t,f,0.4,0.6);});
mel.push({t:t,l:4});t+=0.5;
}
[C4,Eb4,G4,C5].forEach(function(f){bg.push({t:t,f:f,d:3.0,i:'strings',v:0.5});});
bg.push({t:t,f:C3,d:2.5,i:'timpani',v:0.55});mel.push({t:t,l:4});
return {n:"William Tell Overture (Storm)",composer:"Rossini",bpm:132,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
return [b5,hmk,ofort,valk,mars,toccata,tell,bolero,zarath,barber,danube,sabre,bald,sorcerer,swan,morning,bumblebee,sugarplum,overture1812,habanera,spring,furelise,canon,hungarian,odeToJoy,tellStorm];
})();
function buildClassicalNotes(piece){
return piece.melody.filter(function(m){return activePads[m.l];})
.map(function(m){
// Find matching frequency from background notes at same time
var freq=261.63; // default C4
for(var i=0;i<piece.bg.length;i++){
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};
})
.sort(function(a,b){return a.time-b.time;});
}
function beginClassical(){
playing=true;gameSynthNodes=[];
var ac=getAc();ac.resume();
var piece=gameRhythm;
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,gameSynthNodes);
}
setTimeout(function(){if(playing)endTurn();},piece.duration*1000+800);
}
// ── STATE ──────────────────────────────────────────────
let mode="builtin",curGenre=Object.keys(GENRES)[0],curIdx=0;
let players=[{name:"Player 1"}];
let savedScores=lsLoad();
let previewAc=null,previewNodes=[],previewing=false;
let gameAc=null,mp3Buffer=null,mp3Src=null;
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 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 goScores(){renderScores();show("ss");}
// ── PAD TOGGLES ────────────────────────────────────────
function buildPadToggles(){
const el=document.getElementById("pad-toggles");el.innerHTML="";
LANES.forEach((lane,i)=>{
const b=document.createElement("button");
b.className="ptog"+(activePads[i]?" on":"");
b.style.borderColor=lane.color;b.style.color=lane.color;
b.textContent=lane.label+(i===4?" (Kick)":"");
b.onclick=()=>{activePads[i]=!activePads[i];buildPadToggles();};
el.appendChild(b);
});
}
buildPadToggles();
function setHitMode(m){
classicalHitMode=m;
document.getElementById("hitmode-drums").className="smbtn"+(m==='drums'?' purple':'');
document.getElementById("hitmode-orch").className="smbtn"+(m==='orchestra'?' purple':'');
}
function updateHitModeVisibility(){
document.getElementById("hitmode-toggle").style.display=curGenre==="Classical"?"block":"none";
}
// ── 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);
});
// Classical genre tab
var cb=document.createElement("button");
cb.className="gtab"+(curGenre==="Classical"?" active":"");
cb.textContent="\uD83C\uDFBB Classical";cb.onclick=function(){curGenre="Classical";curIdx=0;stopPreview();buildMenu();};
gt.appendChild(cb);
const rg=document.getElementById("rgrid");rg.innerHTML="";
(curGenre==="Classical"?CLASSICAL_PIECES:GENRES[curGenre]).forEach((r,i)=>{
const b=document.createElement("button");
b.className="rbtn"+(i===curIdx?" sel":"");
b.innerHTML=`${r.n}<small>${r.composer?r.composer+' \u00b7 ':''}${r.bpm} bpm</small>`;
b.onclick=()=>{curIdx=i;stopPreview();document.getElementById("pvlbl").textContent=r.n+" selected";buildMenu();};
rg.appendChild(b);
});
const pl=document.getElementById("plist");pl.innerHTML="";
players.forEach((p,i)=>{
const row=document.createElement("div");row.className="prow";
const inp=document.createElement("input");inp.type="text";inp.value=p.name;
inp.oninput=e=>players[i].name=e.target.value;
row.appendChild(inp);
if(players.length>1){const d=document.createElement("button");d.className="smbtn red";d.textContent="✕";d.onclick=()=>{players.splice(i,1);buildMenu();};row.appendChild(d);}
pl.appendChild(row);
});
updateHitModeVisibility();
}
buildMenu();
function addPlayer(){players.push({name:"Player "+(players.length+1)});buildMenu();}
function setMode(m){
mode=m;
document.getElementById("mc-b").classList.toggle("sel",m==="builtin");
document.getElementById("mc-m").classList.toggle("sel",m==="mp3");
document.getElementById("rc").style.display=m==="builtin"?"block":"none";
document.getElementById("mc-mp3").style.display=m==="mp3"?"block":"none";
}
document.getElementById("fi").addEventListener("change",async e=>{
const f=e.target.files[0];if(!f)return;
document.getElementById("mp3n").textContent="Loading…";
const ac=getAc(true);
mp3Buffer=await ac.decodeAudioData(await f.arrayBuffer());
document.getElementById("mp3n").textContent=f.name;
});
// ── AUDIO CTX ──────────────────────────────────────────
// Linux/Chrome requires a user gesture before AudioContext will run.
// We create one shared context and resume it on every user interaction.
let _ac=null;
function getAc(prev=false){
if(prev){
if(!previewAc||previewAc.state==="closed")previewAc=new(window.AudioContext||window.webkitAudioContext)();
previewAc.resume();
return previewAc;
}
if(!gameAc||gameAc.state==="closed")gameAc=new(window.AudioContext||window.webkitAudioContext)();
gameAc.resume();
return gameAc;
}
// Unlock audio on ANY user interaction — click, touch, keydown
function unlockAudio(){
[previewAc,gameAc].forEach(ac=>{if(ac&&ac.state==="suspended")ac.resume();});
// create them early so they're ready
if(!previewAc){previewAc=new(window.AudioContext||window.webkitAudioContext)();previewAc.resume();}
if(!gameAc){gameAc=new(window.AudioContext||window.webkitAudioContext)();gameAc.resume();}
}
["click","touchstart","keydown","mousedown"].forEach(ev=>document.addEventListener(ev,unlockAudio,{once:false}));
// ── PREVIEW ────────────────────────────────────────────
function togglePreview(){previewing?stopPreview():startPreview();}
function stopPreview(){
previewing=false;previewNodes.forEach(n=>{try{n.stop();}catch(e){}});previewNodes=[];
const b=document.getElementById("pvbtn");b.textContent="▶ Preview";b.classList.remove("playing");
}
function startPreview(){
stopPreview();previewing=true;
const b=document.getElementById("pvbtn");b.textContent="■ Stop";b.classList.add("playing");
const r=curGenre==="Classical"?CLASSICAL_PIECES[curIdx]:GENRES[curGenre][curIdx];
document.getElementById("pvlbl").textContent="▶ "+r.n;
loopPreview(r,0);
}
function loopPreview(r,gen){
if(!previewing||gen>200)return;
if(r.isClassical){var ac2=getAc(true);ac2.resume();var t0c=ac2.currentTime+0.15;for(var ci=0;ci<r.bg.length;ci++){var cn=r.bg[ci];playSynth(ac2,cn.f,t0c+cn.t,cn.d,cn.i,(cn.v||0.3)*0.7,previewNodes);}return;}
const ac=getAc(true),bs=60/r.bpm,bars=r.bars||8,len=bars*4*bs,t0=ac.currentTime+0.05;
for(let bar=0;bar<bars;bar++)for(const p of r.p)schedPvHit(ac,p.l,t0+bar*4*bs+p.t*bs);
setTimeout(()=>loopPreview(r,gen+1),len*1000-100);
}
function schedPvHit(ac,lane,t){playDrumAt(ac,lane,t,1.5,previewNodes);}
// ── SCORES ─────────────────────────────────────────────
function lsLoad(){try{const s=localStorage.getItem("drgame5");return s?JSON.parse(s):[]}catch(e){return[];}}
function lsSave(){try{localStorage.setItem("drgame5",JSON.stringify(savedScores));}catch(e){}}
function clearScores(){if(confirm("Clear all scores?")){savedScores=[];lsSave();renderScores();refreshMiniLB();}}
function refreshMiniLB(){
const top=[...savedScores].sort((a,b)=>b.score-a.score).slice(0,3);
document.getElementById("mlb").innerHTML=top.length?top.map((s,i)=>`${["🥇","🥈","🥉"][i]} <b>${s.player}</b> — ${s.rhythm}${s.score.toLocaleString()} (${s.acc}%)`).join("<br>"):"No scores yet.";
}
function renderScores(){
const all=[...savedScores].sort((a,b)=>b.score-a.score);
document.getElementById("stbody").innerHTML=all.map((s,i)=>`<tr><td>${["🥇","🥈","🥉"][i]||""}</td><td>${s.player}</td><td>${s.rhythm}</td><td><b>${s.score.toLocaleString()}</b></td><td>${s.acc}%</td><td>${s.streak}</td><td>${s.date}</td></tr>`).join("");
}
refreshMiniLB();
// ── GAME START ─────────────────────────────────────────
function startGame(){
stopPreview();
// Pre-warm AudioContext on Linux — resume early so it's ready after countdown
var warmAc=getAc();warmAc.resume();
gamePlayers=players.map(p=>({...p,score:0,streak:0,maxStreak:0,total:0,good:0}));
activePIdx=0;resetStats();show("sg");resizeCv();
if(mode==="builtin"){
gameRhythm=curGenre==="Classical"?CLASSICAL_PIECES[curIdx]:GENRES[curGenre][curIdx];
document.getElementById("hud-title").textContent=curGenre+" — "+gameRhythm.n+" ("+gameRhythm.bpm+" bpm)";
notes=gameRhythm.isClassical?buildClassicalNotes(gameRhythm):buildNotes(gameRhythm);
countdown(()=>gameRhythm.isClassical?beginClassical():beginBuiltin());
} else {
gameRhythm=null;notes=[];
document.getElementById("hud-title").textContent=mp3Buffer?"MP3 Jam":"Jam";
countdown(()=>beginMp3());
}
updateHud();
}
function stopGame(){playing=false;if(mp3Src){try{mp3Src.stop();}catch(e){}mp3Src=null;}gameSynthNodes.forEach(function(n){try{n.stop();}catch(e){}});gameSynthNodes=[];}
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=[];
for(let bar=0;bar<bars;bar++)
for(const p of r.p)
if(activePads[p.l]) // only include active lanes
res.push({time:(bar*4+p.t)*bs,lane:p.l,hit:false,missed:false});
return res.sort((a,b)=>a.time-b.time);
}
function countdown(cb){
const el=document.getElementById("cdown");let n=3;
el.style.opacity=1;el.textContent=n;
const iv=setInterval(()=>{n--;if(n<=0){clearInterval(iv);el.style.opacity=0;cb();}else el.textContent=n;},1000);
}
function beginBuiltin(){
playing=true;
const ac=getAc(),r=gameRhythm,bs=60/r.bpm,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,[]);
const totalSec=bars*4*bs;
setTimeout(()=>{if(playing)endTurn();},totalSec*1000+800);
}
function beginMp3(){
playing=true;gameStartTime=getAc().currentTime;
if(mp3Buffer){
mp3Src=getAc().createBufferSource();mp3Src.buffer=mp3Buffer;
mp3Src.connect(getAc().destination);mp3Src.start();
mp3Src.onended=()=>{if(playing)endTurn();};
} else setTimeout(()=>{if(playing)endTurn();},30000);
}
function endTurn(){
playing=false;
const p=gamePlayers[activePIdx];
p.score=cScore;p.streak=cMaxStreak;p.total=cTotal;p.good=cGood;
const acc=cTotal?Math.round(cGood/cTotal*100):0;
savedScores.push({player:p.name,rhythm:gameRhythm?curGenre+" — "+gameRhythm.n:"MP3 Jam",score:cScore,acc,streak:cMaxStreak,date:new Date().toLocaleDateString()});
lsSave();
activePIdx++;
if(activePIdx<gamePlayers.length){
resetStats();notes=gameRhythm?(gameRhythm.isClassical?buildClassicalNotes(gameRhythm):buildNotes(gameRhythm)):[];updateHud();
const banner=document.getElementById("pbanner");
banner.textContent="Next: "+gamePlayers[activePIdx].name+"!";banner.style.opacity=1;
setTimeout(()=>{banner.style.opacity=0;setTimeout(()=>countdown(()=>gameRhythm.isClassical?beginClassical():beginBuiltin()),400);},2200);
} else {
const sorted=[...gamePlayers].sort((a,b)=>b.score-a.score);
const lines=sorted.map((p,i)=>`${["🥇","🥈","🥉"][i]||" "} ${p.name}: ${p.score.toLocaleString()} (${p.total?Math.round(p.good/p.total*100):0}%)`);
setTimeout(()=>{alert("Results:\n\n"+lines.join("\n"));goMenu();},500);
}
}
// ── HIT LOGIC ──────────────────────────────────────────
function triggerPad(lane){
var isClassicalOrch=gameRhythm&&gameRhythm.isClassical&&classicalHitMode==='orchestra';
if(!isClassicalOrch){
playDrumAt(getAc(),lane,getAc().currentTime,8,[]);
}
laneFlash[lane]=1;setTimeout(()=>laneFlash[lane]=0,120);
if(!playing)return;
if(mode==="mp3"){showMsg("♪",LANES[lane].color);return;}
if(!activePads[lane])return; // inactive pad — sound only
const now=getAc().currentTime-gameStartTime;
let best=null,bestD=HIT_WIN;
for(const n of notes){
if(n.lane!==lane||n.hit||n.missed)continue;
const d=Math.abs(n.time-now);
if(d<bestD){bestD=d;best=n;}
}
cTotal++;
if(best){
best.hit=true;cStreak++;cGood++;cScore+=100*Math.min(cStreak,10);
if(cStreak>cMaxStreak)cMaxStreak=cStreak;
// Play orchestral hit sound for classical mode
if(isClassicalOrch&&best.freq){
var ac=getAc(),ct=ac.currentTime;
playSynth(ac,best.freq,ct,0.3,'strings',0.5,[]);
playSynth(ac,best.freq,ct,0.3,'brass',0.3,[]);
} else if(isClassicalOrch){
playDrumAt(getAc(),lane,getAc().currentTime,8,[]);
}
showMsg(bestD<0.05?"PERFECT!":"GOOD",bestD<0.05?"#68d391":"#d69e2e");
} else {
// Miss — play drum sound as feedback even in orchestra mode
if(isClassicalOrch)playDrumAt(getAc(),lane,getAc().currentTime,4,[]);
cStreak=0;showMsg("MISS","#fc8181");
}
updateHud();
}
function updateHud(){
document.getElementById("hs").textContent=cScore.toLocaleString();
document.getElementById("hst").textContent=cStreak;
document.getElementById("ha").textContent=cTotal?Math.round(cGood/cTotal*100)+"%":"-";
document.getElementById("hp").textContent=gamePlayers[activePIdx]?.name||"-";
}
function showMsg(txt,color){
const el=document.getElementById("msg");el.textContent=txt;el.style.color=color;el.style.opacity=1;
clearTimeout(msgTmr);msgTmr=setTimeout(()=>el.style.opacity=0,500);
}
// ── CANVAS ─────────────────────────────────────────────
const canvas=document.getElementById("c");
const wrap=document.getElementById("cwrap");
const cx=canvas.getContext("2d");
function resizeCv(){canvas.width=wrap.clientWidth;canvas.height=wrap.clientHeight;}
resizeCv();window.addEventListener("resize",resizeCv);
function draw(){
requestAnimationFrame(draw);
const W=canvas.width,H=canvas.height;
if(!W||!H)return;
cx.clearRect(0,0,W,H);
// Active pad lanes (only those toggled on, excluding kick)
const activeLanes=LANES.slice(0,4).map((l,i)=>i).filter(i=>activePads[i]);
const hasKick=activePads[4];
const PAD_H=hasKick?Math.floor(H*0.78):H;
const KICK_H=H-PAD_H;
const nLanes=activeLanes.length;
const PAD_W=nLanes>0?W/nLanes:W;
const HIT_Y=Math.floor(PAD_H*0.76);
const KICK_HIT_Y=PAD_H+Math.floor(KICK_H*0.55);
const now=(playing&&gameStartTime!=null)?getAc().currentTime-gameStartTime:-9999;
// Lane positions map: lane index -> x center
const laneX={};
activeLanes.forEach((li,col)=>{laneX[li]=col*PAD_W+PAD_W/2;});
// Draw pad lanes
activeLanes.forEach((li,col)=>{
const x=col*PAD_W;
cx.fillStyle=laneFlash[li]?LANES[li].color+"33":"#0d0d16";
cx.fillRect(x,0,PAD_W,PAD_H);
if(col>0){cx.strokeStyle="#1e1e2e";cx.lineWidth=1;cx.beginPath();cx.moveTo(x,0);cx.lineTo(x,PAD_H);cx.stroke();}
cx.fillStyle=LANES[li].color;cx.font="bold 12px sans-serif";cx.textAlign="center";
cx.fillText(LANES[li].label,x+PAD_W/2,PAD_H-20);
cx.fillStyle="#999";cx.font="10px sans-serif";
cx.fillText(LANES[li].key.toUpperCase(),x+PAD_W/2,PAD_H-7);
});
// Kick zone
if(hasKick){
cx.fillStyle=laneFlash[4]?LANES[4].color+"33":"#0a0a12";
cx.fillRect(0,PAD_H,W,KICK_H);
cx.strokeStyle="#1e1e2e";cx.lineWidth=1;cx.beginPath();cx.moveTo(0,PAD_H);cx.lineTo(W,PAD_H);cx.stroke();
cx.fillStyle=LANES[4].color;cx.font="bold 13px sans-serif";cx.textAlign="center";
cx.fillText("Kick",W/2,PAD_H+KICK_H*0.42);
cx.fillStyle="#999";cx.font="10px sans-serif";
cx.fillText("SPACE",W/2,PAD_H+KICK_H*0.72);
}
if(nLanes===0&&!hasKick)return;
// Hit line
if(nLanes>0){
cx.strokeStyle="rgba(255,255,255,0.5)";cx.lineWidth=2;
cx.beginPath();cx.moveTo(0,HIT_Y);cx.lineTo(W,HIT_Y);cx.stroke();
cx.fillStyle="rgba(255,255,255,0.03)";
cx.fillRect(0,HIT_Y-SPEED*HIT_WIN,W,SPEED*HIT_WIN*2);
}
// Kick hit line
if(hasKick){
cx.strokeStyle=LANES[4].color+"88";cx.lineWidth=2;
cx.beginPath();cx.moveTo(W*0.15,KICK_HIT_Y);cx.lineTo(W*0.85,KICK_HIT_Y);cx.stroke();
}
// Target rings — pads
activeLanes.forEach(li=>{
const x=laneX[li],r=Math.min(PAD_W*0.33,26);
cx.beginPath();cx.arc(x,HIT_Y,r,0,Math.PI*2);
cx.strokeStyle=laneFlash[li]?LANES[li].color:LANES[li].color+"55";
cx.lineWidth=laneFlash[li]?3:2;cx.stroke();
if(laneFlash[li]){cx.beginPath();cx.arc(x,HIT_Y,r,0,Math.PI*2);cx.fillStyle=LANES[li].color+"44";cx.fill();}
});
// Kick ring
if(hasKick){
const kr=Math.min(W*0.08,34);
cx.beginPath();cx.arc(W/2,KICK_HIT_Y,kr,0,Math.PI*2);
cx.strokeStyle=laneFlash[4]?LANES[4].color:LANES[4].color+"55";
cx.lineWidth=laneFlash[4]?3:2;cx.stroke();
if(laneFlash[4]){cx.beginPath();cx.arc(W/2,KICK_HIT_Y,kr,0,Math.PI*2);cx.fillStyle=LANES[4].color+"44";cx.fill();}
}
// Notes
if(mode==="builtin"&&now>-9000){
for(const n of notes){
if(n.hit)continue;
const dt=n.time-now;
if(n.lane<4){
if(!laneX.hasOwnProperty(n.lane))continue;
const ny=HIT_Y-dt*SPEED;
if(ny<-40||ny>PAD_H+40){
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
continue;
}
const x=laneX[n.lane],r=Math.min(PAD_W*0.33,26);
cx.globalAlpha=n.missed?0.2:1;
const g=cx.createRadialGradient(x,ny,0,x,ny,r*2);
g.addColorStop(0,LANES[n.lane].color+"aa");g.addColorStop(1,LANES[n.lane].color+"00");
cx.fillStyle=g;cx.beginPath();cx.arc(x,ny,r*2,0,Math.PI*2);cx.fill();
cx.fillStyle=LANES[n.lane].color;cx.beginPath();cx.arc(x,ny,r,0,Math.PI*2);cx.fill();
cx.strokeStyle="#fff";cx.lineWidth=2;cx.beginPath();cx.arc(x,ny,r,0,Math.PI*2);cx.stroke();
cx.globalAlpha=1;
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
} else if(hasKick){
const ky=KICK_HIT_Y-dt*SPEED*0.5;
if(ky<PAD_H-30||ky>H+30){
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
continue;
}
const kr=Math.min(W*0.08,34);
cx.globalAlpha=n.missed?0.2:1;
cx.fillStyle=LANES[4].color;cx.beginPath();cx.arc(W/2,ky,kr,0,Math.PI*2);cx.fill();
cx.strokeStyle="#fff";cx.lineWidth=2;cx.beginPath();cx.arc(W/2,ky,kr,0,Math.PI*2);cx.stroke();
cx.globalAlpha=1;
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
}
}
}
}
draw();
// ── DRUM SOUNDS ────────────────────────────────────────
function playDrumAt(ac,lane,t,vol,arr){
const type=LANES[lane].type;
function mk(n){if(arr)arr.push(n);}
if(type==="kick"||type==="bass"){
const o=ac.createOscillator(),g=ac.createGain();
o.connect(g);g.connect(ac.destination);
o.frequency.setValueAtTime(180,t);o.frequency.exponentialRampToValueAtTime(40,t+0.15);
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
o.start(t);o.stop(t+0.35);mk(o);
} else if(type==="snare"){
const sz=ac.sampleRate*0.12,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
f.type="highpass";f.frequency.value=1500;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(vol*.75,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
s.start(t);s.stop(t+0.15);mk(s);
const o=ac.createOscillator(),g2=ac.createGain();
o.connect(g2);g2.connect(ac.destination);o.frequency.value=180;
g2.gain.setValueAtTime(vol*.35,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.07);
o.start(t);o.stop(t+0.08);mk(o);
} else if(type==="hihat"){
const sz=ac.sampleRate*0.05,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
f.type="highpass";f.frequency.value=7000;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(vol*.5,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.05);
s.start(t);s.stop(t+0.06);mk(s);
} else { // tom / tom2
const freq=type==="tom"?160:120;
const o=ac.createOscillator(),g=ac.createGain();
o.connect(g);g.connect(ac.destination);
o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(freq*0.4,t+0.2);
g.gain.setValueAtTime(vol*.85,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.25);
o.start(t);o.stop(t+0.28);mk(o);
}
}
// ── INPUT ──────────────────────────────────────────────
window.addEventListener("keydown",e=>{
const i=LANES.findIndex(l=>l.key===e.key.toLowerCase()||(l.key===" "&&e.key===" "));
if(i>=0){e.preventDefault();triggerPad(i);}
});
function pollGP(){
const gps=navigator.getGamepads?navigator.getGamepads():[];
let found=false;
for(const gp of gps){
if(!gp)continue;found=true;
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]);
}
prevBtns[bi]=btn.pressed;
});
}
if(!found)document.getElementById("gpd").textContent="No gamepad";
requestAnimationFrame(pollGP);
}
pollGP();
</script>
</body>
</html>