Files
drum-rhythm-game/index.html
T
Claude 8fca8cacd7 Fix game input handlers intercepting keystrokes in editor input fields
Two additional window-level keydown handlers were also capturing keys when
the pitch input (or any input/select/textarea) was focused:

1. The key remap handler (capture phase) was eating all keydown events
2. The game lane input handler was calling preventDefault() on keys like
   f, g, h, j, d, k, l, space — which are needed for typing pitch names

Both now skip when activeElement is an INPUT, TEXTAREA, or SELECT.

https://claude.ai/code/session_01TLvDyKzUQekd9hzFuEJ5YX
2026-04-02 16:20:04 +00:00

6570 lines
287 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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;}
.sticky-start{position:sticky;bottom:0;padding:12px 0 4px;background:linear-gradient(transparent,#0a0a0f 35%);z-index:10;display:flex;gap:8px;justify-content:center;align-items:center;width:100%;max-width:700px;}
#game-controls{display:flex;gap:0;background:#0d0d18;border-bottom:1px solid #1e1e2e;padding:5px 14px;flex-shrink:0;width:100%;align-items:center;justify-content:center;gap:28px;}
.gctrl-label{display:flex;align-items:center;gap:8px;cursor:default;}
.gctrl-icon{font-size:16px;}
.gctrl-name{font-size:11px;color:#aaa;min-width:44px;}
.gctrl-slider{width:140px;height:6px;accent-color:#805ad5;cursor:pointer;}
.gctrl-val{font-size:13px;font-weight:bold;color:#b794f4;min-width:40px;text-align:right;}
#pause-overlay{display:none;position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);z-index:20;flex-direction:column;align-items:center;justify-content:center;gap:16px;}
#pause-overlay.show{display:flex;}
#pause-overlay h2{font-size:28px;color:#fff;}
#remap-overlay,#keyremap-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:30;flex-direction:column;align-items:center;justify-content:center;gap:16px;}
#remap-overlay.show,#keyremap-overlay.show{display:flex;}
#remap-prompt,#keyremap-prompt{font-size:20px;font-weight:bold;text-align:center;}
#remap-status,#keyremap-status{font-size:13px;opacity:.5;text-align:center;}
</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 style="margin-top:10px;padding-top:8px;border-top:1px solid #222;">
<span style="font-size:11px;opacity:.5;margin-right:8px;">Note density:</span>
<button class="smbtn" id="density-full" onclick="setSimpleMode(false)">Full</button>
<button class="smbtn" id="density-simple" onclick="setSimpleMode(true)">Simple</button>
<span style="font-size:10px;opacity:.35;margin-left:6px;" id="density-hint">Fewer notes for beginners</span>
</div>
<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>
<div style="display:flex;gap:6px;margin-top:8px">
<button class="smbtn" onclick="goScores()">View All</button>
<button class="smbtn red" onclick="confirmReset()">Reset All Data</button>
</div>
</div>
<div class="card">
<h2>Settings</h2>
<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:center;">
<label style="font-size:12px;display:flex;align-items:center;gap:6px;">Speed: <input type="range" min="50" max="150" value="100" step="5" id="speed-slider" oninput="setSpeed(this.value)"><span id="speed-val" style="font-size:11px;min-width:32px">100%</span></label>
<label style="font-size:12px;display:flex;align-items:center;gap:6px;">Volume: <input type="range" min="0" max="150" value="100" step="5" id="vol-slider" oninput="setVolume(this.value)"><span id="vol-val" style="font-size:11px;min-width:32px">100%</span></label>
<button class="smbtn" onclick="resetSettings()" style="font-size:11px;">Reset</button>
</div>
</div>
<div class="sticky-start">
<button class="smbtn" onclick="startRemap()">🎮 Remap Pads</button>
<button class="smbtn" onclick="startKeyRemap()">⌨ Remap Keys</button>
<button class="smbtn" id="edit-btn" onclick="openEditor()" style="display:none;">✏️ Edit Melody</button>
<button class="bigbtn" onclick="startGame()">▶ Start</button>
</div>
</div>
<!-- GAME -->
<div class="screen" id="sg" style="padding:0;">
<div id="hud">
<button class="smbtn" onclick="goMenu()">← Menu</button>
<button class="smbtn" onclick="togglePause()" id="pause-btn"></button>
<div id="hud-title">-</div>
<div class="hstat"><span>Score</span><b id="hs">0</b></div>
<div class="hstat"><span>Streak</span><b id="hst">0</b></div>
<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;margin-left:auto">No gamepad</span>
</div>
<div id="game-controls">
<label class="gctrl-label">
<span class="gctrl-icon">🔊</span>
<span class="gctrl-name">Volume</span>
<input type="range" min="0" max="150" value="100" step="5" id="hud-vol" class="gctrl-slider" oninput="setVolume(this.value);document.getElementById('vol-slider').value=this.value">
<span class="gctrl-val" id="hud-vol-val">100%</span>
</label>
</div>
<div id="cwrap">
<canvas id="c"></canvas><div id="msg"></div><div id="cdown"></div><div id="pbanner"></div>
<div id="pause-overlay"><h2>PAUSED</h2><button class="bigbtn" onclick="togglePause()">▶ Resume</button><button class="smbtn" onclick="goMenu()">← Back to Menu</button></div>
</div>
</div>
<!-- REMAP (gamepad) -->
<div id="remap-overlay"><div id="remap-prompt"></div><div id="remap-status"></div><button class="smbtn red" onclick="cancelRemap()">Cancel</button></div>
<!-- REMAP (keyboard) -->
<div id="keyremap-overlay"><div id="keyremap-prompt"></div><div id="keyremap-status"></div><button class="smbtn red" onclick="cancelKeyRemap()">Cancel</button></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>
<!-- EDITOR -->
<div class="screen" id="sed" style="padding:0;overflow:hidden;">
<div id="ed-toolbar" style="display:flex;align-items:center;gap:8px;padding:6px 12px;background:#13131f;border-bottom:1px solid #333;flex-wrap:wrap;">
<button class="smbtn" onclick="closeEditor()">← Back</button>
<span id="ed-title" style="font-weight:bold;color:#ccc;margin-right:8px;">Editing: —</span>
<label style="color:#aaa;font-size:12px;">BPM: <input type="number" id="ed-bpm" value="120" min="30" max="300" style="width:55px;background:#1a1a2e;color:#fff;border:1px solid #444;padding:2px 4px;border-radius:3px;" onchange="edUpdateBpm()"></label>
<label style="color:#aaa;font-size:12px;">Time: <select id="ed-timesig" style="background:#1a1a2e;color:#fff;border:1px solid #444;padding:2px;border-radius:3px;font-size:12px;" onchange="edUpdateTimeSig()"><option>4/4</option><option>3/4</option><option>2/4</option><option>6/8</option><option>9/8</option><option>5/4</option><option>7/8</option><option>12/8</option></select></label>
<label style="color:#aaa;font-size:12px;">Zoom: <input type="range" id="ed-zoom" min="20" max="200" value="80" style="width:80px;" oninput="edDraw()"></label>
<button class="smbtn" onclick="edPreview()" id="ed-pv-btn">▶ Preview</button>
<button class="smbtn" onclick="edSave()" style="background:#38a169;">💾 Save</button>
<button class="smbtn" onclick="edExport()">📋 Copy JSON</button>
<button class="smbtn" onclick="edTogglePitch()" id="ed-pitch-btn">♪ Pitch</button>
<button class="smbtn" onclick="edToggleCode()" id="ed-code-btn">{} Code</button>
<button class="smbtn" onclick="edToggleImport()">📥 Import</button>
<button class="smbtn red" onclick="edReset()">↺ Reset</button>
<button class="smbtn" onclick="edDownloadAllEdits()" title="Download all edits as a JSON file">⬇ Edits File</button>
<button class="smbtn" onclick="document.getElementById('ed-load-file').click()" title="Load edits from a JSON file">⬆ Load Edits</button>
<input type="file" id="ed-load-file" accept=".json" style="display:none" onchange="edLoadEditsFile(event)">
<button class="smbtn" onclick="edBakeIntoHtml()" style="background:#805ad5;" title="Download index.html with all edits embedded">📦 Bake into HTML</button>
<span id="ed-status" style="color:#888;font-size:11px;margin-left:auto;"></span>
</div>
<div id="ed-help" style="padding:4px 12px;background:#0d0d18;color:#666;font-size:11px;border-bottom:1px solid #222;">
Click=select | Dbl-click=add note | Drag=move | Drag edge=resize | 1-5=note value | Shift+↑↓=pitch | ↑↓=lane | ←→=nudge time | D=+dur | Shift+D=-dur | Shift+drag=select range | Ctrl+C/V=copy/paste | Del=delete | Scroll=pan | Click header=set start | Dbl-click header=play from there
<span id="ed-mode-label" style="margin-left:12px;color:#805ad5;"></span>
</div>
<!-- Note Properties Panel (appears when a note is selected) -->
<div id="ed-note-panel" style="display:none;padding:6px 12px;background:#0d0d18;border-bottom:1px solid #333;flex-shrink:0;">
<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;">
<span style="color:#aaa;font-size:12px;font-weight:bold;">Selected Note:</span>
<label style="color:#aaa;font-size:12px;">Pitch:
<input type="text" id="ed-note-pitch" placeholder="e.g. C4, D#5, Bb3" style="width:70px;background:#1a1a2e;color:#ff0;border:1px solid #444;padding:2px 4px;border-radius:3px;font-family:monospace;font-size:12px;" onchange="edSetPitch(this.value)">
</label>
<label style="color:#aaa;font-size:12px;">Duration:
<select id="ed-note-dur" style="background:#1a1a2e;color:#fff;border:1px solid #444;padding:2px;border-radius:3px;font-size:12px;" onchange="edSetDuration(this.value)">
<option value="0">— (instant)</option>
<option value="whole">𝅝 Whole</option>
<option value="dotted-half">𝅗𝅥. Dotted Half</option>
<option value="half">𝅗𝅥 Half</option>
<option value="dotted-quarter">♩. Dotted Quarter</option>
<option value="quarter">♩ Quarter</option>
<option value="dotted-eighth">♪. Dotted Eighth</option>
<option value="eighth">♪ Eighth</option>
<option value="sixteenth">♬ Sixteenth</option>
<option value="32nd">♬♬ 32nd</option>
</select>
</label>
<label style="color:#aaa;font-size:12px;">Lane:
<select id="ed-note-lane" style="background:#1a1a2e;color:#fff;border:1px solid #444;padding:2px;border-radius:3px;font-size:12px;" onchange="edSetLane(this.value)">
<option value="0" style="color:#e53e3e">0 - Red</option>
<option value="1" style="color:#d69e2e">1 - Yellow</option>
<option value="2" style="color:#3182ce">2 - Blue</option>
<option value="3" style="color:#38a169">3 - Green</option>
<option value="4" style="color:#805ad5">4 - Kick</option>
</select>
</label>
<span id="ed-note-info" style="color:#666;font-size:11px;font-family:monospace;"></span>
</div>
</div>
<div id="ed-code-panel" style="display:none;padding:8px 12px;background:#0a0a14;border-bottom:1px solid #333;overflow-y:auto;flex-shrink:0;max-height:45vh;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
<span style="color:#888;font-size:11px;">JavaScript — edit and click "Apply Code" to update the timeline. Drag bottom edge to resize.</span>
<button class="smbtn" onclick="edApplyCode()" style="font-size:11px;">Apply Code</button>
</div>
<textarea id="ed-code" style="width:100%;height:180px;min-height:60px;background:#111;color:#8f8;font-family:monospace;font-size:11px;border:1px solid #333;padding:4px;resize:vertical;" spellcheck="false"></textarea>
</div>
<div id="ed-import-panel" style="display:none;padding:8px 12px;background:#0a0a14;border-bottom:1px solid #333;">
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<span style="color:#888;font-size:11px;">Paste JSON array of {t,l} notes or drag a .json file:</span>
<textarea id="ed-import-text" style="width:100%;height:60px;background:#111;color:#ff8;font-family:monospace;font-size:11px;border:1px solid #333;padding:4px;resize:vertical;" placeholder='[{"t":0,"l":0},{"t":0.5,"l":1}]'></textarea>
<button class="smbtn" onclick="edDoImport()" style="background:#3182ce;">Import</button>
<button class="smbtn" onclick="edImportMIDI()">MIDI File</button>
<input type="file" id="ed-midi-file" accept=".mid,.midi,.json" style="display:none;" onchange="edHandleFile(event)">
<span id="ed-import-status" style="color:#888;font-size:11px;"></span>
</div>
</div>
<canvas id="ed-canvas" style="display:block;width:100%;flex:1;min-height:200px;cursor:crosshair;align-self:stretch;"></canvas>
</div>
<script>
// ── BAKED EDITS (auto-populated by "Bake into HTML") ──
var BAKED_EDITS = {};
// ── END BAKED EDITS ──
// ── 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"},
{label:"Crash", color:"#ed8936",key:"d", type:"crash"},
{label:"Ride", color:"#4fd1c5",key:"k", type:"ride"},
{label:"Tom 3", color:"#d53f8c",key:"l", type:"tom3"},
];
// which pads are active (show notes) — all on by default
let activePads=[true,true,true,true,true,false,false,false];
// Simple mode — thins out notes, keeping only strong beats
let simpleMode=false;
// ── 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,Ab5:830.61,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*masterVolume;
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,Ab5=NT.Ab5,
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) ──
// ~87 seconds to the natural break at recapitulation
// Continuous orchestral bed plays throughout; only strong melody accents are hittable
var b5=(function(){
var q=0.375,e=q/2,bg=[],mel=[];
// === CONTINUOUS HARMONIC BED (plays throughout, not hittable) ===
// Sustained string/woodwind chords that change with the harmony
var harmony=[
[0,5.5,[C4,Eb4,G4],0.15], // Cm - opening
[5.5,11,[C4,Eb4,G4],0.12], // Cm - echo
[11,18,[C4,Eb4,G4],0.18], // Cm - development
[18,26,[Ab3,C4,Eb4],0.2], // Ab - power chords
[26,32,[C4,Eb4,G4],0.15], // Cm - transition
[32,40,[G3,Bb3,D4],0.18], // Gm - rising tension
[40,50,[C4,Eb4,G4],0.22], // Cm - recapitulation
[50,60,[Ab3,C4,Eb4],0.2], // Ab - chords
[60,72,[C4,Eb4,G4],0.25], // Cm - building
[72,82,[G3,B3,D4],0.22], // G - dominant tension
[82,90,[C4,Eb4,G4],0.2] // Cm - resolution
];
harmony.forEach(function(h){
for(var t=h[0];t<h[1];t+=2.0){
h[2].forEach(function(f){
bg.push({t:t,f:f,d:1.9,i:'strings',v:h[3]});
});
bg.push({t:t,f:h[2][0]/2,d:1.9,i:'bass',v:h[3]*0.6});
if(h[3]>0.18)bg.push({t:t,f:h[2][1],d:1.9,i:'woodwind',v:h[3]*0.5});
}
});
// === SECTION 1 (0-5.5s): THE OPENING — G-G-G-Eb, F-F-F-D ===
function tutti(t,freq,dur,v){
bg.push({t:t,f:freq,d:dur,i:'strings',v:v});
bg.push({t:t,f:freq,d:dur,i:'brass',v:v*0.6});
bg.push({t:t,f:freq*0.75,d:dur,i:'strings',v:v*0.35});
bg.push({t:t,f:freq*0.6,d:dur,i:'woodwind',v:v*0.3});
if(freq>200)bg.push({t:t,f:freq/2,d:dur,i:'bass',v:v*0.5});
}
// G-G-G-Eb (ff)
tutti(0,G4,0.18,0.55);tutti(q,G4,0.18,0.55);tutti(q*2,G4,0.18,0.55);
tutti(q*3,Eb4,1.8,0.6);
bg.push({t:0,f:G3,d:0.18,i:'timpani',v:0.45});
mel.push({t:0,l:0},{t:q,l:0},{t:q*2,l:0},{t:q*3,l:3});
// F-F-F-D
var s=3.2;
tutti(s,F4,0.18,0.55);tutti(s+q,F4,0.18,0.55);tutti(s+q*2,F4,0.18,0.55);
tutti(s+q*3,D4,1.8,0.6);
mel.push({t:s,l:1},{t:s+q,l:1},{t:s+q*2,l:1},{t:s+q*3,l:3});
// === SECTION 2 (5.5-11s): SOFT RESTATEMENT — strings only, woodwind color ===
s=6.0;
// Running eighth notes in 2nd violins (background texture, not hittable)
for(var ri=0;ri<20;ri++){
var rf=[G4,Eb4,F4,D4,G4,F4,Eb4,D4,C4,Eb4,G4,F4,Eb4,D4,C4,B3,C4,D4,Eb4,F4][ri];
bg.push({t:s+ri*e,f:rf,d:e*0.8,i:'strings',v:0.18});
}
// Melody accents (hittable) — just the motif peaks
bg.push({t:6.0,f:G4,d:0.16,i:'woodwind',v:0.3});mel.push({t:6.0,l:0});
bg.push({t:6.0+q,f:G4,d:0.16,i:'woodwind',v:0.3});mel.push({t:6.0+q,l:0});
bg.push({t:6.0+q*2,f:G4,d:0.16,i:'woodwind',v:0.3});mel.push({t:6.0+q*2,l:0});
bg.push({t:6.0+q*3,f:Eb4,d:1.0,i:'woodwind',v:0.35});mel.push({t:6.0+q*3,l:3});
s=8.5;
bg.push({t:s,f:F4,d:0.16,i:'woodwind',v:0.3});mel.push({t:s,l:1});
bg.push({t:s+q,f:F4,d:0.16,i:'woodwind',v:0.3});mel.push({t:s+q,l:1});
bg.push({t:s+q*2,f:F4,d:0.16,i:'woodwind',v:0.3});mel.push({t:s+q*2,l:1});
bg.push({t:s+q*3,f:D4,d:1.0,i:'woodwind',v:0.35});mel.push({t:s+q*3,l:3});
// === SECTION 3 (11-18s): DEVELOPMENT — faster motifs + scalar run ===
s=11.5;
tutti(s,G4,0.15,0.5);tutti(s+e,G4,0.15,0.5);tutti(s+e*2,G4,0.15,0.5);
tutti(s+e*3,Eb4,0.5,0.55);
mel.push({t:s,l:0},{t:s+e,l:0},{t:s+e*2,l:0},{t:s+e*3,l:3});
s=12.5;
tutti(s,F4,0.15,0.5);tutti(s+e,F4,0.15,0.5);tutti(s+e*2,F4,0.15,0.5);
tutti(s+e*3,D4,0.5,0.55);
mel.push({t:s,l:1},{t:s+e,l:1},{t:s+e*2,l:1},{t:s+e*3,l:3});
// Scalar run — violins play fast, you hit the accent notes
s=14.0;
var scaleDown=[G4,F4,Eb4,D4,C4,B3,C4,D4,Eb4,F4,G4,Ab4,Bb4,Ab4,G4,F4,Eb4,D4,C4,B3];
scaleDown.forEach(function(f,i){
bg.push({t:s+i*e,f:f,d:e*0.85,i:'strings',v:0.4});
bg.push({t:s+i*e,f:f*0.8,d:e*0.85,i:'strings',v:0.2}); // violas
});
// Hittable: only every 4th note (the downbeat accents)
[0,4,8,12,16].forEach(function(i){
mel.push({t:s+i*e,l:[2,0,2,0,0][Math.floor(i/4)]});
});
// === SECTION 4 (18-28s): POWER CHORDS — big hits with timpani ===
s=18.5;
var chords=[[G4,Eb4,C4],[Ab4,F4,C4],[Bb4,G4,Eb4],[Ab4,F4,C4],[G4,Eb4,C4],[F4,D4,Bb3],[Eb4,C4,Ab3],[D4,B3,G3]];
chords.forEach(function(ch,i){
var t2=s+i*1.1;
ch.forEach(function(f){
bg.push({t:t2,f:f,d:1.0,i:'brass',v:0.55});
bg.push({t:t2,f:f,d:1.0,i:'strings',v:0.45});
});
bg.push({t:t2,f:ch[2],d:1.0,i:'bass',v:0.4});
bg.push({t:t2,f:ch[2]/2,d:0.8,i:'timpani',v:0.55});
mel.push({t:t2,l:4});
});
// === SECTION 5 (28-40s): MOTIF IN NEW KEYS — rising tension ===
// Motif passed between brass and strings at different pitches
s=28.0;
// Background: continuous running strings
for(var ri=0;ri<48;ri++){
var rf2=[G4,Eb4,C4,Eb4,F4,D4,Bb3,D4][ri%8];
bg.push({t:s+ri*e,f:rf2,d:e*0.8,i:'strings',v:0.15});
}
// Hittable motif fragments at rising pitches
[[s,G4,Eb4],[s+2.5,Ab4,F4],[s+5.0,Bb4,G4],[s+7.5,C5,Ab4],[s+10.0,D5,Bb4]].forEach(function(m){
tutti(m[0],m[1],0.14,0.55);tutti(m[0]+e,m[1],0.14,0.55);tutti(m[0]+e*2,m[1],0.14,0.55);
tutti(m[0]+e*3,m[2],0.6,0.6);
mel.push({t:m[0],l:0},{t:m[0]+e,l:0},{t:m[0]+e*2,l:0},{t:m[0]+e*3,l:3});
});
// === SECTION 6 (40-52s): SUSTAINED DRAMA — choir + horns, slow chords ===
s=41.0;
[[C4,Eb4,G4],[Bb3,D4,F4],[Ab3,C4,Eb4],[G3,B3,D4],[C4,Eb4,G4],[F3,Ab3,C4]].forEach(function(ch,i){
var t2=s+i*1.8;
ch.forEach(function(f){
bg.push({t:t2,f:f,d:1.6,i:'strings',v:0.5});
bg.push({t:t2,f:f,d:1.6,i:'brass',v:0.4});
bg.push({t:t2,f:f,d:1.6,i:'choir',v:0.3});
});
bg.push({t:t2,f:ch[0]/2,d:1.6,i:'bass',v:0.4});
bg.push({t:t2,f:ch[0]/2,d:1.2,i:'timpani',v:0.5});
mel.push({t:t2,l:4},{t:t2+0.9,l:i%2===0?0:1});
});
// === SECTION 7 (52-64s): RECAPITULATION — motif returns fff ===
s=53.0;
tutti(s,G4,0.18,0.7);tutti(s+q,G4,0.18,0.7);tutti(s+q*2,G4,0.18,0.7);
tutti(s+q*3,Eb4,1.8,0.75);
bg.push({t:s+q*3,f:Eb4,d:1.8,i:'choir',v:0.35});bg.push({t:s+q*3,f:G4,d:1.8,i:'choir',v:0.3});
bg.push({t:s,f:G3,d:0.2,i:'timpani',v:0.55});
mel.push({t:s,l:0},{t:s+q,l:0},{t:s+q*2,l:0},{t:s+q*3,l:3});
s=56.2;
tutti(s,F4,0.18,0.7);tutti(s+q,F4,0.18,0.7);tutti(s+q*2,F4,0.18,0.7);
tutti(s+q*3,D4,1.8,0.75);
bg.push({t:s+q*3,f:D4,d:1.8,i:'choir',v:0.35});bg.push({t:s+q*3,f:F4,d:1.8,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});
// Ascending run with full orchestra
s=59.5;
[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;
bg.push({t:t2,f:f,d:e*0.85,i:'strings',v:0.5});
bg.push({t:t2,f:f,d:e*0.85,i:'brass',v:0.3});
bg.push({t:t2,f:f*0.75,d:e*0.85,i:'strings',v:0.25});
});
[0,4,8,12].forEach(function(i){mel.push({t:s+i*e,l:[0,1,2,3][Math.floor(i/4)]});});
// === SECTION 8 (64-76s): FINAL MOTIF HAMMERING ===
s=64.0;
// Motif stated 3 times, each louder — whole orchestra
[[G4,Eb4,0.65],[G4,Eb4,0.72],[G4,Eb4,0.8]].forEach(function(m,mi){
var t2=s+mi*3.5;
tutti(t2,m[0],0.2,m[2]);tutti(t2+q,m[0],0.2,m[2]);tutti(t2+q*2,m[0],0.2,m[2]);
tutti(t2+q*3,m[1],2.0,m[2]);
bg.push({t:t2+q*3,f:m[1],d:2.0,i:'choir',v:m[2]*0.5});
bg.push({t:t2,f:G3,d:0.2,i:'timpani',v:m[2]*0.7});
mel.push({t:t2,l:0},{t:t2+q,l:0},{t:t2+q*2,l:0},{t:t2+q*3,l:3});
});
// === SECTION 9 (76-85s): FINAL CHORDS — cadence to the break ===
s=76.0;
[0,0.6,1.2,1.8,2.4,3.0,3.6,4.2].forEach(function(dt,i){
var t2=s+dt;
[C4,Eb4,G4].forEach(function(f){
bg.push({t:t2,f:f,d:0.5,i:'brass',v:0.7});
bg.push({t:t2,f:f,d:0.5,i:'strings',v:0.6});
});
bg.push({t:t2,f:C3,d:0.5,i:'bass',v:0.55});
bg.push({t:t2,f:C3,d:0.5,i:'timpani',v:0.65});
mel.push({t:t2,l:4});
});
// === SECTION 10 (81-87s): FINAL SUSTAINED CHORD — natural break ===
s=81.0;
[C4,Eb4,G4,C5].forEach(function(f){
bg.push({t:s,f:f,d:5.0,i:'brass',v:0.6});
bg.push({t:s,f:f,d:5.0,i:'strings',v:0.5});
bg.push({t:s,f:f,d:5.0,i:'choir',v:0.35});
});
bg.push({t:s,f:C3,d:4.0,i:'bass',v:0.5});
bg.push({t:s,f:C3,d:3.0,i:'timpani',v:0.55});
mel.push({t:s,l:4});
return {n:"Beethoven's 5th (Opening)",composer:"Beethoven",bpm:160,duration:88,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=[B3,Db4,D4,E4,Gb4,D4,Gb4,E4,Db4,E4,Eb4,B3,Eb4,B3];
var lanes=[0,1,2,0,1,2,1,0,3,0,1,3,1,3];
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:B3/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:B3/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:B3/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;
[B3,D4,Gb4,B4].forEach(function(f){
orch(t2,f,0.35,0.65,'brass');orch(t2,f,0.35,0.55,'strings');
});
bg.push({t:t2,f:B3/2,d:0.4,i:'timpani',v:0.65});
mel.push({t:t2,l:4});
}
// Final long chord
t=t+1.8;
[B3,D4,Gb4,B4].forEach(function(f){
orch(t,f,2.0,0.6,'brass');orch(t,f,2.0,0.5,'strings');
});
bg.push({t:t,f:B3/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) ──
// ~90 seconds, conductor style with continuous harmonic bed, SATB choir, tutti hits
// D minor, 3/4 time. Lead-sheet structure with tempo changes throughout.
var ofort=(function(){
var bg=[],mel=[],t=0;
// === HELPERS ===
// Full orchestral hit (strings + brass + bass)
function tutti(time,freq,dur,v){
bg.push({t:time,f:freq,d:dur,i:'strings',v:v});
bg.push({t:time,f:freq,d:dur,i:'brass',v:v*0.6});
bg.push({t:time,f:freq*0.75,d:dur,i:'strings',v:v*0.35});
if(freq>200)bg.push({t:time,f:freq/2,d:dur,i:'bass',v:v*0.5});
}
// SATB choir voicing (soprano + alto + tenor)
function choir4(time,sop,alt,ten,dur,v){
bg.push({t:time,f:sop,d:dur,i:'choir',v:v});
bg.push({t:time,f:alt,d:dur,i:'choir',v:v*0.85});
bg.push({t:time,f:ten,d:dur,i:'choir',v:v*0.7});
}
// Simple choir unison/octave
function choir1(time,freq,dur,v){
bg.push({t:time,f:freq,d:dur,i:'choir',v:v});
bg.push({t:time,f:freq/2,d:dur*0.9,i:'choir',v:v*0.5});
}
// Timpani hit
function timp(time,v){bg.push({t:time,f:D3,d:0.5,i:'timpani',v:v||0.5});}
// === CONTINUOUS HARMONIC BED (sustained string chords throughout) ===
var harmony=[
[0,6,[D4,F4,A4],0.18], // Dm - opening ff
[6,18,[D4,F4,A4],0.1], // Dm - pp soft verse
[18,30,[D4,F4,A4],0.1], // Dm - pp continued
[30,42,[G3,Bb3,D4],0.14], // Gm - building mp
[42,54,[D4,F4,A4],0.18], // Dm - dramatic
[54,64,[A3,D4,F4],0.2], // Dm inv - forte return
[64,76,[D4,F4,A4],0.25], // Dm - ff marcato
[76,88,[D4,F4,A4],0.3], // Dm - fff climax
[88,95,[D4,F4,A4],0.25] // Dm - finale
];
harmony.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=1.8){
h[2].forEach(function(f){
bg.push({t:ht,f:f,d:1.7,i:'strings',v:h[3]});
});
bg.push({t:ht,f:h[2][0]/2,d:1.7,i:'bass',v:h[3]*0.6});
if(h[3]>0.15)bg.push({t:ht,f:h[2][1],d:1.7,i:'woodwind',v:h[3]*0.4});
}
});
// === SECTION 1 (0-6s): "O Fortuna" — ff, half=60, bold opening ===
// 3/4 time, half note = 60 → quarter = 0.5s
var q1=0.5;
// "O For-tu-na, ve-lut Lu-na, sta-tu va-ri-a-bi-lis"
// Big unison D, then choral declamation
var s1notes=[D4,D4,D4,D4, A3,A3,Bb3,Bb3, A3,A3,G3,A3];
var s1durs= [q1,q1,q1,q1*2, q1,q1*2,q1,q1*2, q1,q1,q1,q1*2];
var s1lanes=[0,0,0,0, 1,1,2,2, 1,1,3,1];
t=0;
s1notes.forEach(function(f,i){
choir4(t,f,f>A3?D4:f,f>C4?A3:f*0.75,s1durs[i]*0.9,0.55);
tutti(t,f,s1durs[i]*0.85,0.5);
if(s1durs[i]>=q1*2)timp(t,0.55);
mel.push({t:t,l:s1lanes[i]});
t+=s1durs[i];
});
// === SECTION 2 (6-18s): "semper crescis aut decrescis" — pp, half=132, soft pairs ===
// half=132 → quarter = ~0.227s
var q2=0.227;
t=6.5;
// Running strings background texture (not hittable)
for(var ri=0;ri<50;ri++){
var rf=[D4,F4,A4,F4,D4,E4,F4,G4,A4,G4,F4,E4,D4,C4,Bb3,A3][ri%16];
bg.push({t:t+ri*q2,f:rf,d:q2*0.8,i:'strings',v:0.12});
}
// Soft choral pairs — only accents are hittable
// "sem-per cres-cis aut de-cres-cis"
var s2notes=[D4,E4, F4,E4, D4,C4, D4,D4];
var s2durs= [q2*2,q2*2, q2*2,q2*2, q2*2,q2*2, q2*2,q2*4];
var s2lanes=[0,1, 2,1, 0,3, 0,0];
s2notes.forEach(function(f,i){
choir1(t,f,s2durs[i]*0.9,0.3);
bg.push({t:t,f:f,d:s2durs[i]*0.85,i:'woodwind',v:0.18});
mel.push({t:t,l:s2lanes[i]});
t+=s2durs[i];
});
// "vi-ta de-tes-ta-bi-lis"
t+=q2;
var s2b=[A3,Bb3,A3,G3,A3,Bb3,C4,D4];
var s2bd=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s2bl=[1,2,1,3,1,2,0,0];
s2b.forEach(function(f,i){
choir1(t,f,s2bd[i]*0.9,0.3);
bg.push({t:t,f:f,d:s2bd[i]*0.85,i:'woodwind',v:0.18});
mel.push({t:t,l:s2bl[i]});
t+=s2bd[i];
});
// === SECTION 3 (18-30s): "nunc obdurat et tunc curat" — pp, same tempo ===
t=18.5;
// Running string texture
for(var ri2=0;ri2<52;ri2++){
var rf2=[A3,C4,D4,F4,E4,D4,C4,Bb3,A3,G3,A3,Bb3,C4,D4,E4,F4][ri2%16];
bg.push({t:t+ri2*q2,f:rf2,d:q2*0.8,i:'strings',v:0.13});
}
// "nunc ob-du-rat et tunc cu-rat"
var s3a=[D4,D4,E4,F4,E4,D4,C4,D4];
var s3ad=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s3al=[0,0,1,2,1,0,3,0];
s3a.forEach(function(f,i){
choir1(t,f,s3ad[i]*0.9,0.32);
bg.push({t:t,f:f,d:s3ad[i]*0.85,i:'woodwind',v:0.2});
mel.push({t:t,l:s3al[i]});
t+=s3ad[i];
});
// "lu-do men-tis a-ci-em"
t+=q2;
var s3b=[A3,Bb3,C4,Bb3,A3,G3,A3];
var s3bd=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s3bl=[1,2,0,2,1,3,1];
s3b.forEach(function(f,i){
choir1(t,f,s3bd[i]*0.9,0.32);
mel.push({t:t,l:s3bl[i]});
t+=s3bd[i];
});
// === SECTION 4 (30-42s): "egestatem potestatem" — building mp ===
t=30.5;
// Strings now more active
for(var ri3=0;ri3<52;ri3++){
var rf3=[D4,F4,A4,D5,A4,F4,D4,F4,G4,Bb4,G4,F4,E4,D4,C4,Bb3][ri3%16];
bg.push({t:t+ri3*q2,f:rf3,d:q2*0.8,i:'strings',v:0.18});
}
// "e-ges-ta-tem po-tes-ta-tem"
var s4a=[D4,E4,F4,G4,A4,G4,F4,E4];
var s4ad=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s4al=[0,1,2,3,0,3,2,1];
s4a.forEach(function(f,i){
choir4(t,f,f>D4?D4:f,f>C4?A3:f*0.75,s4ad[i]*0.9,0.4);
bg.push({t:t,f:f,d:s4ad[i]*0.85,i:'brass',v:0.15});
if(i%4===0)timp(t,0.35);
mel.push({t:t,l:s4al[i]});
t+=s4ad[i];
});
// "dis-sol-vit ut gla-ci-em"
t+=q2;
var s4b=[F4,E4,D4,C4,D4,E4,D4];
var s4bd=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s4bl=[2,1,0,3,0,1,0];
s4b.forEach(function(f,i){
choir4(t,f,D4,A3,s4bd[i]*0.9,0.42);
bg.push({t:t,f:f/2,d:s4bd[i]*0.85,i:'bass',v:0.25});
if(s4bd[i]>=q2*4)timp(t,0.4);
mel.push({t:t,l:s4bl[i]});
t+=s4bd[i];
});
// === SECTION 5 (42-54s): "Sors immanis et inanis" — dramatic ===
t=42.5;
// Running strings more intense
for(var ri4=0;ri4<52;ri4++){
var rf4=[D4,A4,F4,D4,G4,Bb4,G4,D4,A4,D5,A4,F4,E4,D4,C4,D4][ri4%16];
bg.push({t:t+ri4*q2,f:rf4,d:q2*0.8,i:'strings',v:0.22});
}
// "Sors im-ma-nis et in-a-nis"
var s5a=[A4,A4,G4,F4,E4,F4,G4,A4];
var s5ad=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s5al=[0,0,3,2,1,2,3,0];
s5a.forEach(function(f,i){
choir4(t,f,D4,A3,s5ad[i]*0.9,0.5);
tutti(t,f,s5ad[i]*0.85,0.3);
if(i%2===0)timp(t,0.45);
mel.push({t:t,l:s5al[i]});
t+=s5ad[i];
});
// "ro-ta tu vo-lu-bi-lis"
t+=q2;
var s5b=[D5,C5,Bb4,A4,G4,F4,E4,D4];
var s5bd=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s5bl=[4,0,2,1,3,2,1,0];
s5b.forEach(function(f,i){
choir4(t,f,F4,D4,s5bd[i]*0.9,0.52);
tutti(t,f,s5bd[i]*0.85,0.32);
if(i===0||i===4)timp(t,0.5);
mel.push({t:t,l:s5bl[i]});
t+=s5bd[i];
});
// === SECTION 6 (54-64s): More verses — "status malus" continuing pattern ===
t=54.5;
for(var ri5=0;ri5<44;ri5++){
var rf5=[D4,F4,A4,D5,C5,Bb4,A4,G4,F4,E4,D4,C4,Bb3,A3,C4,D4][ri5%16];
bg.push({t:t+ri5*q2,f:rf5,d:q2*0.8,i:'strings',v:0.2});
}
// Rising phrase "ve-rum est quod le-gi-tur"
var s6a=[D4,F4,A4,Bb4,A4,G4,F4,G4,A4,Bb4,C5,D5];
var s6ad=[q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*2,q2*4];
var s6al=[0,2,0,2,1,3,2,3,1,2,0,4];
s6a.forEach(function(f,i){
choir4(t,f,f>D4?D4:f,A3,s6ad[i]*0.9,0.48);
bg.push({t:t,f:f,d:s6ad[i]*0.85,i:'brass',v:0.22});
bg.push({t:t,f:f/2,d:s6ad[i]*0.85,i:'bass',v:0.3});
if(i%3===0)timp(t,0.42);
mel.push({t:t,l:s6al[i]});
t+=s6ad[i];
});
// === SECTION 7 (64-76s): "Sors salutis et virtutis" — f, half=144, marcato ===
// half=144 → quarter = ~0.208s
var q3=0.208;
t=64.5;
// Driving string ostinato
for(var ri6=0;ri6<56;ri6++){
var rf6=[D4,A4,D4,A4,F4,A4,F4,A4][ri6%8];
bg.push({t:t+ri6*q3,f:rf6,d:q3*0.75,i:'strings',v:0.25});
}
// Marcato choral accents
// "Sors sa-lu-tis et vir-tu-tis mi-chi nunc con-tra-ri-a"
var s7a=[D4,D4,E4,F4, E4,D4,C4,D4, A4,G4,F4,E4, D4,E4,F4,D4];
var s7ad=[q3*2,q3*2,q3*2,q3*2, q3*2,q3*2,q3*2,q3*4, q3*2,q3*2,q3*2,q3*2, q3*2,q3*2,q3*2,q3*4];
var s7al=[0,0,1,2, 1,0,3,0, 0,3,2,1, 0,1,2,4];
s7a.forEach(function(f,i){
choir4(t,f,f>D4?D4:f,f>C4?A3:D3,s7ad[i]*0.85,0.55);
tutti(t,f,s7ad[i]*0.8,0.45);
if(i%4===0)timp(t,0.5);
mel.push({t:t,l:s7al[i]});
t+=s7ad[i];
});
// "est af-fec-tus et de-fec-tus"
t+=q3;
var s7b=[A4,Bb4,A4,G4,F4,G4,A4,D4];
var s7bd=[q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*4];
var s7bl=[0,2,1,3,2,3,1,4];
s7b.forEach(function(f,i){
choir4(t,f,D4,A3,s7bd[i]*0.85,0.58);
tutti(t,f,s7bd[i]*0.8,0.48);
if(i%2===0)timp(t,0.52);
mel.push({t:t,l:s7bl[i]});
t+=s7bd[i];
});
// === SECTION 8 (76-88s): "Hac in hora sine mora" — ff, climax ===
// "corde pulsum tangite; quod per sortem sternit fortem mecum omnes plangite!"
t=76.5;
// Furious string tremolo
for(var ri7=0;ri7<60;ri7++){
var rf7=[D4,F4,A4,D5,A4,F4,D4,F4,A4,D5,F5,D5,A4,F4,D4,A3][ri7%16];
bg.push({t:t+ri7*q3,f:rf7,d:q3*0.7,i:'strings',v:0.3});
if(ri7%4===0)bg.push({t:t+ri7*q3,f:rf7*0.5,d:q3*2,i:'bass',v:0.25});
}
// "Hac in ho-ra si-ne mo-ra cor-de pul-sum tan-gi-te"
var s8a=[D5,D5,C5,Bb4,A4,G4,A4,Bb4,A4,G4,F4,E4,D4];
var s8ad=[q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*4];
var s8al=[4,4,0,2,1,3,1,2,1,3,2,1,0];
s8a.forEach(function(f,i){
choir4(t,f,f>D4?F4:f,f>C4?D4:A3,s8ad[i]*0.85,0.65);
tutti(t,f,s8ad[i]*0.8,0.55);
timp(t,0.55);
mel.push({t:t,l:s8al[i]});
t+=s8ad[i];
});
// "quod per sor-tem ster-nit for-tem"
t+=q3;
var s8b=[A4,Bb4,C5,D5,C5,Bb4,A4,G4];
var s8bd=[q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*2,q3*4];
var s8bl=[1,2,0,4,0,2,1,3];
s8b.forEach(function(f,i){
choir4(t,f,D4,A3,s8bd[i]*0.85,0.68);
tutti(t,f,s8bd[i]*0.8,0.58);
timp(t,0.58);
mel.push({t:t,l:s8bl[i]});
t+=s8bd[i];
});
// "me-cum om-nes plan-gi-te!"
t+=q3;
var s8c=[A4,A4,Bb4,A4,D5,D5,D4];
var s8cd=[q3*2,q3*2,q3*2,q3*2,q3*3,q3*3,q3*6];
var s8cl=[0,1,2,1,4,4,0];
s8c.forEach(function(f,i){
choir4(t,f,f>D4?F4:f,f>C4?D4:A3,s8cd[i]*0.85,0.72);
tutti(t,f,s8cd[i]*0.8,0.62);
timp(t,0.62);
mel.push({t:t,l:s8cl[i]});
t+=s8cd[i];
});
// === SECTION 9 (88-95s): Final sustained chords — fff, half=160 ===
t=88.5;
// Massive repeated D minor chords
var finQ=0.4;
for(var fc=0;fc<8;fc++){
var ft=t+fc*finQ;
[D4,F4,A4,D5].forEach(function(f){
bg.push({t:ft,f:f,d:finQ*0.85,i:'choir',v:0.7});
bg.push({t:ft,f:f,d:finQ*0.85,i:'brass',v:0.6});
bg.push({t:ft,f:f,d:finQ*0.85,i:'strings',v:0.55});
});
bg.push({t:ft,f:D3,d:finQ*0.85,i:'bass',v:0.55});
timp(ft,0.65);
mel.push({t:ft,l:4});
}
// Final sustained Dm chord
t=t+8*finQ+0.3;
[D4,F4,A4,D5].forEach(function(f){
bg.push({t:t,f:f,d:4.0,i:'choir',v:0.65});
bg.push({t:t,f:f,d:4.0,i:'brass',v:0.55});
bg.push({t:t,f:f,d:4.0,i:'strings',v:0.5});
});
bg.push({t:t,f:D3,d:3.5,i:'bass',v:0.55});
bg.push({t:t,f:D3,d:3.0,i:'timpani',v:0.6});
mel.push({t:t,l:4});
return {n:"O Fortuna",composer:"Orff",bpm:120,duration:Math.ceil(t+4.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 ──
// Conductor-style: ~85s, Bb major, 9/8 galloping compound meter
// Famous ascending brass arpeggio Bb-D-F as hittable melody
var valk=(function(){
var bg=[],mel=[];
function tutti(time,freq,dur,v){
bg.push({t:time,f:freq,d:dur,i:'strings',v:v});
bg.push({t:time,f:freq,d:dur,i:'brass',v:v*0.6});
bg.push({t:time,f:freq*0.75,d:dur,i:'strings',v:v*0.35});
if(freq>200)bg.push({t:time,f:freq/2,d:dur,i:'bass',v:v*0.5});
}
function timp(time,v){bg.push({t:time,f:Bb3,d:0.5,i:'timpani',v:v||0.5});}
// Galloping rhythm constants (9/8 compound: groups of 3 eighths)
var e8=0.22,q=0.44,dq=0.66; // eighth, quarter, dotted-quarter
// === CONTINUOUS HARMONIC BED ===
var harmony=[
[0,5,[Bb3,D4,F4],0.06], // Bb - intro tremolo
[5,17,[Bb3,D4,F4],0.12], // Bb - first horn call
[17,29,[Bb3,D4,F4],0.16], // Bb - second statement
[29,41,[Bb3,Eb4,G4],0.14], // Eb - development
[41,53,[Bb3,D4,F4],0.2], // Bb - recapitulation
[53,65,[Bb3,D4,F4],0.25], // Bb - climax
[65,78,[Bb3,D4,F4],0.18], // Bb - final
[78,88,[Bb3,D4,F4],0.12] // Bb - coda
];
harmony.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=1.8){
h[2].forEach(function(f){
bg.push({t:ht,f:f,d:1.7,i:'strings',v:h[3]});
});
bg.push({t:ht,f:h[2][0]/2,d:1.7,i:'bass',v:h[3]*0.5});
if(h[3]>0.15)bg.push({t:ht,f:h[2][1],d:1.7,i:'brass',v:h[3]*0.35});
}
});
// Helper: play the galloping arpeggio (ascending Bb-D-F-Bb)
// notes/lanes/mode control what plays and how loud
function gallop(t0,notes,lanes,v,withTutti){
var gt=t0;
notes.forEach(function(n,i){
var d=(i===notes.length-1)?dq:((i%3===0)?q:e8);
if(withTutti){
tutti(gt,n,d*0.85,v);
bg.push({t:gt,f:n,d:d*0.85,i:'brass',v:v*0.7});
}else{
bg.push({t:gt,f:n,d:d*0.85,i:'brass',v:v});
bg.push({t:gt,f:n,d:d*0.85,i:'strings',v:v*0.5});
}
mel.push({t:gt,l:lanes[i]});
gt+=d;
});
return gt;
}
// === SECTION 1 (0-5s): INTRODUCTION — string tremolo building ===
var t=0;
// Low string tremolo (background, not hittable)
for(var ri=0;ri<40;ri++){
var rf=[Bb3,F3,D3,F3,Bb3,F3,D3,F3][ri%8];
bg.push({t:ri*0.12,f:rf,d:0.11,i:'strings',v:0.06+ri*0.003});
}
// Low brass pedal tones
for(var bi=0;bi<3;bi++){
bg.push({t:bi*1.8,f:Bb3,d:1.6,i:'brass',v:0.06+bi*0.02});
}
// Timpani roll building
for(bi=0;bi<6;bi++){
timp(0.5+bi*0.7,0.15+bi*0.04);
}
// Sparse hittable timpani hits — building anticipation
mel.push({t:1.5,l:4});mel.push({t:3.0,l:4});mel.push({t:4.2,l:4});
t=5.0;
// === SECTION 2 (5-17s): FIRST HORN CALL — p to mf ===
// The famous galloping ascending arpeggio: Bb-Bb-D, D-D-F, F
// String tremolo continues underneath
var s2s=t;
for(ri=0;ri<55;ri++){
rf=[Bb3,D4,Bb3,F3,Bb3,D4,F4,D4][ri%8];
bg.push({t:s2s+ri*e8,f:rf,d:e8*0.8,i:'strings',v:0.1});
}
// First statement: ascending Bb-D-F (soft, horns entering)
// Bb(q) Bb(e8) D(e8) | D(q) D(e8) F(e8) | F(dq)
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.3,false);
t+=0.3;
// Descending: Bb(q) F(e8) D(e8) | Bb(q) D(e8) F(e8) | Bb(dq)
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.32,false);
t+=0.4;
// Second statement: louder, with woodwind doubling
var s2b=t;
for(ri=0;ri<55;ri++){
rf=[F4,D4,Bb3,D4,F4,Bb4,F4,D4][ri%8];
bg.push({t:s2b+ri*e8,f:rf,d:e8*0.8,i:'woodwind',v:0.08});
}
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.38,false);
t+=0.3;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.4,false);
t+=0.5;
// === SECTION 3 (17-29s): SECOND STATEMENT — mf to f, full brass ===
var s3s=t;
for(ri=0;ri<55;ri++){
rf=[Bb3,F4,D4,Bb3,F3,Bb3,D4,F4][ri%8];
bg.push({t:s3s+ri*e8,f:rf,d:e8*0.8,i:'strings',v:0.14});
}
// Woodwind sustained chords (background)
for(bi=0;bi<6;bi++){
[Bb3,D4,F4].forEach(function(f){
bg.push({t:s3s+bi*2.0,f:f,d:1.8,i:'woodwind',v:0.12});
});
}
// Horn call with tutti orchestration now
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.45,true);
t+=0.3;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.48,true);
t+=0.4;
// Repeat with kick accents on downbeats
timp(t,0.5);mel.push({t:t,l:4});t+=dq;
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.5,true);
t+=0.3;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.52,true);
t+=0.5;
// === SECTION 4 (29-41s): DEVELOPMENT — new key, Eb major ===
// Modulation to Eb, secondary melodic material
var s4s=t;
for(ri=0;ri<55;ri++){
rf=[Eb4,G4,Bb4,G4,Eb4,Bb3,Eb4,G4][ri%8];
bg.push({t:s4s+ri*e8,f:rf,d:e8*0.8,i:'strings',v:0.14});
}
for(bi=0;bi<6;bi++){
[Eb4,G4,Bb4].forEach(function(f){
bg.push({t:s4s+bi*2.0,f:f,d:1.8,i:'brass',v:0.12});
});
bg.push({t:s4s+bi*2.0,f:Eb4/2,d:1.8,i:'bass',v:0.15});
}
// Eb major arpeggio: Eb-Eb-G, G-G-Bb, Bb
t=gallop(t,[Eb4,Eb4,G4, G4,G4,Bb4, Bb4],[2,2,3, 3,3,0, 0],0.45,true);
t+=0.3;
t=gallop(t,[Bb4,G4,Eb4, G4,Eb4,Bb3, Eb4],[0,3,2, 3,2,1, 2],0.48,true);
t+=0.4;
// Back to Bb major with added power
timp(t,0.55);mel.push({t:t,l:4});t+=dq;
t=gallop(t,[Bb3,D4,F4, Bb4,F4,D4, Bb3],[0,1,2, 3,2,1, 0],0.5,true);
t+=0.3;
// Chromatic passage — building tension
[[C4,0],[D4,1],[Eb4,2],[E4,2],[F4,3]].forEach(function(n){
tutti(t,n[0],q*0.85,0.48);
bg.push({t:t,f:n[0],d:q*0.85,i:'brass',v:0.45});
mel.push({t:t,l:n[1]});t+=q;
});
tutti(t,F4,dq*0.85,0.52);mel.push({t:t,l:4});timp(t,0.55);t+=dq+0.5;
// === SECTION 5 (41-53s): RECAPITULATION — ff, full orchestra ===
var s5s=t;
for(ri=0;ri<55;ri++){
rf=[Bb3,D4,F4,Bb4,F4,D4,Bb3,F3][ri%8];
bg.push({t:s5s+ri*e8,f:rf,d:e8*0.8,i:'strings',v:0.18});
if(ri%4===0)bg.push({t:s5s+ri*e8,f:rf*0.5,d:e8*2,i:'brass',v:0.1});
}
for(bi=0;bi<6;bi++){
bg.push({t:s5s+bi*2.0,f:Bb3/2,d:1.8,i:'bass',v:0.22});
timp(s5s+bi*2.0,0.4);
}
// Full fff horn call — massive tutti
timp(t,0.6);mel.push({t:t,l:4});t+=dq;
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.6,true);
t+=0.25;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.62,true);
t+=0.35;
// Repeat — even louder
timp(t,0.65);mel.push({t:t,l:4});t+=dq;
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.65,true);
t+=0.25;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.68,true);
t+=0.5;
// === SECTION 6 (53-65s): CLIMAX — fff, maximum intensity ===
var s6s=t;
// Massive tremolo
for(ri=0;ri<60;ri++){
rf=[Bb3,D4,F4,Bb4,D5,Bb4,F4,D4][ri%8];
bg.push({t:s6s+ri*e8,f:rf,d:e8*0.8,i:'strings',v:0.2});
if(ri%3===0)bg.push({t:s6s+ri*e8,f:rf*0.5,d:e8*1.5,i:'brass',v:0.15});
}
for(bi=0;bi<7;bi++){
bg.push({t:s6s+bi*1.6,f:Bb3/2,d:1.4,i:'bass',v:0.28});
timp(s6s+bi*1.6,0.5);
}
// Rapid repeated gallops — faster, more intense
var fe8=0.18,fq=0.36,fdq=0.54; // faster tempo
// Ascending blast
[Bb3,D4,F4,Bb4].forEach(function(f,i){
tutti(t,f,fq*0.85,0.7);
bg.push({t:t,f:f,d:fq*0.85,i:'brass',v:0.65});
mel.push({t:t,l:i});t+=fq;
});
timp(t,0.65);mel.push({t:t,l:4});t+=fdq;
// Descending
[Bb4,F4,D4,Bb3].forEach(function(f,i){
tutti(t,f,fq*0.85,0.7);
bg.push({t:t,f:f,d:fq*0.85,i:'brass',v:0.65});
mel.push({t:t,l:[3,2,1,0][i]});t+=fq;
});
timp(t,0.65);mel.push({t:t,l:4});t+=fdq;
// Full speed gallop x2
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.7,true);
t+=0.2;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.72,true);
t+=0.3;
// One more ascending blast
[Bb3,D4,F4,Bb4,D5].forEach(function(f,i){
tutti(t,f,fe8*0.85,0.72);
bg.push({t:t,f:f,d:fe8*0.85,i:'brass',v:0.7});
mel.push({t:t,l:[0,1,2,3,4][i]});t+=fe8;
});
t+=0.5;
// === SECTION 7 (65-78s): SUSTAINED POWER — massive held chords ===
// Big chord hits with space between them
var chords=[[Bb3,D4,F4,Bb4],[Eb4,G4,Bb4,Eb5],[F4,A4,C5,F5],[Bb3,D4,F4,Bb4],
[Bb3,D4,F4,Bb4],[Eb4,G4,Bb4,Eb5],[F4,A4,C5,F5],[Bb3,D4,F4,D5]];
chords.forEach(function(ch,ci){
var ct=t+ci*1.5;
ch.forEach(function(f){
bg.push({t:ct,f:f,d:1.3,i:'brass',v:0.6});
bg.push({t:ct,f:f,d:1.3,i:'strings',v:0.5});
});
bg.push({t:ct,f:ch[0]/2,d:1.3,i:'bass',v:0.45});
timp(ct,0.6);
mel.push({t:ct,l:4});
// Add the gallop rhythm between chords
if(ci<7){
mel.push({t:ct+0.5,l:ci%2===0?0:2});
bg.push({t:ct+0.5,f:ch[1],d:0.3,i:'brass',v:0.4});
mel.push({t:ct+0.9,l:ci%2===0?1:3});
bg.push({t:ct+0.9,f:ch[2],d:0.3,i:'brass',v:0.4});
}
});
t+=chords.length*1.5+0.5;
// === SECTION 8 (78-88s): FINAL CODA ===
// Last galloping statement — triumphant
timp(t,0.7);mel.push({t:t,l:4});t+=dq;
t=gallop(t,[Bb3,Bb3,D4, D4,D4,F4, F4],[0,0,1, 1,1,2, 2],0.7,true);
t+=0.2;
t=gallop(t,[Bb4,F4,D4, Bb3,D4,F4, Bb4],[3,2,1, 0,1,2, 3],0.72,true);
t+=0.4;
// Final massive Bb major chord
[Bb3,D4,F4,Bb4,D5,F5].forEach(function(f){
bg.push({t:t,f:f,d:5.0,i:'brass',v:0.6});
bg.push({t:t,f:f,d:5.0,i:'strings',v:0.5});
});
bg.push({t:t,f:Bb3/2,d:4.0,i:'bass',v:0.55});
bg.push({t:t,f:Bb3,d:3.5,i:'timpani',v:0.65});
mel.push({t:t,l:4});
return {n:"Ride of the Valkyries",composer:"Wagner",bpm:140,duration:Math.ceil(t+5),isClassical:true,bg:bg,melody:mel};
})();
// ── William Tell Overture (Gallop) ──
// The famous finale gallop in G major, ~70s
// Trumpet fanfare → galloping theme → accelerating coda
var tell=(function(){
var bg=[],mel=[],t=0,q=60/152;
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});}
function tutti(t,f,d,v){str(t,f,d,v);brs(t,f,d,(v||0.4)*0.7);bg.push({t:t,f:f/2,d:d,i:'bass',v:(v||0.4)*0.4});}
// Harmonic bed
var harm=[
[0,8,[G3,B3,D4],0.12],[8,20,[G3,B3,D4],0.15],[20,32,[G3,B3,D4],0.18],
[32,44,[C4,E4,G4],0.16],[44,56,[G3,B3,D4],0.2],[56,72,[G3,B3,D4],0.22]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.0){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:1.9,i:'strings',v:h[3]});});
bg.push({t:ht,f:h[2][0]/2,d:1.9,i:'bass',v:h[3]*0.5});
}
});
// === SECTION 1 (0-8s): Trumpet fanfare intro ===
// Galloping triplet rhythm on trumpet: da-da-DUM da-da-DUM
var trip=q/3;
for(var fi=0;fi<4;fi++){
var ft=fi*q*2;
brs(ft,D5,trip*0.8,0.45);brs(ft+trip,D5,trip*0.8,0.45);brs(ft+trip*2,G4,q*0.8,0.5);
mel.push({t:ft,l:0},{t:ft+trip,l:0},{t:ft+trip*2,l:3});
}
t=q*8;
// Sustained trumpet call
brs(t,D5,q*2,0.5);brs(t,B4,q*2,0.45);mel.push({t:t,l:1});
t+=q*2.5;
brs(t,G4,q*3,0.55);brs(t,D4,q*3,0.5);bg.push({t:t,f:G3,d:q*2,i:'timpani',v:0.4});mel.push({t:t,l:3});
t=8;
// === SECTION 2 (8-20s): Main galloping theme ===
// The famous melody: G-G-B | G-G-B | D-D-B-G | E-G-B-D-B-G
var gallop1=[
[G4,trip,0],[G4,trip,0],[B4,q,3],
[G4,trip,0],[G4,trip,0],[B4,q,3],
[D5,trip,1],[D5,trip,1],[B4,q*0.7,3],[G4,q*0.7,0],
[E4,q*0.7,2],[G4,trip,0],[B4,trip,3],[D5,q,1],[B4,trip,3],[G4,q,0]
];
for(var rep=0;rep<3;rep++){
var v1=0.35+rep*0.08;
gallop1.forEach(function(n){
tutti(t,n[0],n[1]*0.85,v1);
mel.push({t:t,l:n[2]});
t+=n[1];
});
t+=q*0.5;
}
// === SECTION 3 (20-32s): Contrasting lyrical phrase ===
// Strings take over with smoother melody
var lyric=[[B4,q*2,3],[A4,q,1],[G4,q,0],[A4,q*2,1],[B4,q*2,3],
[D5,q*2,1],[C5,q,2],[B4,q,3],[A4,q*2,1],[G4,q*2,0]];
for(var lr=0;lr<2;lr++){
var vl=0.4+lr*0.08;
lyric.forEach(function(n){
str(t,n[0],n[1]*0.9,vl);
if(lr>0)brs(t,n[0],n[1]*0.9,vl*0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 4 (32-44s): Theme returns with full orchestra ===
for(var rep2=0;rep2<3;rep2++){
var v2=0.45+rep2*0.08;
gallop1.forEach(function(n){
tutti(t,n[0],n[1]*0.85,v2);
if(rep2>0)bg.push({t:t,f:n[0],d:n[1]*0.85,i:'brass',v:v2*0.5});
if(rep2>1&&n[2]===0)bg.push({t:t,f:G3,d:0.15,i:'timpani',v:v2*0.5});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.5;
}
// === SECTION 5 (44-56s): Ascending fanfare + gallop intensifies ===
// Brass fanfare ascending
var fanfare=[[G4,0],[B4,3],[D5,1],[G5,3],[D5,1],[B4,3],[G4,0]];
for(var ff=0;ff<2;ff++){
fanfare.forEach(function(n){
brs(t,n[0],q*0.85,0.55);str(t,n[0],q*0.85,0.45);
mel.push({t:t,l:n[1]});t+=q;
});
bg.push({t:t,f:G3,d:0.4,i:'timpani',v:0.5});mel.push({t:t,l:4});t+=q*1.5;
}
// Double-time gallop
var dTrip=trip*0.8;
for(var dg=0;dg<4;dg++){
var vd=0.5+dg*0.04;
gallop1.slice(0,10).forEach(function(n){
tutti(t,n[0],n[1]*0.7,vd);mel.push({t:t,l:n[2]});t+=n[1]*0.8;
});
t+=q*0.3;
}
// === SECTION 6 (56-68s): Final gallop — full speed ===
for(var fg=0;fg<4;fg++){
var vf=0.55+fg*0.05;
gallop1.forEach(function(n){
tutti(t,n[0],n[1]*0.7,vf);
brs(t,n[0],n[1]*0.7,vf*0.6);
if(fg>1)bg.push({t:t,f:n[0],d:n[1]*0.7,i:'choir',v:vf*0.25});
mel.push({t:t,l:n[2]});t+=n[1]*0.75;
});
bg.push({t:t,f:G3,d:0.3,i:'timpani',v:vf*0.7});mel.push({t:t,l:4});t+=q*0.5;
}
// === FINALE: Big G major chords ===
for(var c=0;c<6;c++){
[G3,B3,D4,G4,B4,D5].forEach(function(f){brs(t,f,0.4,0.65);str(t,f,0.4,0.55);});
bg.push({t:t,f:G3/2,d:0.4,i:'timpani',v:0.65});mel.push({t:t,l:4});t+=0.5;
}
[G3,B3,D4,G4,B4,D5].forEach(function(f){brs(t,f,3.0,0.7);str(t,f,3.0,0.6);});
bg.push({t:t,f:G3/2,d:2.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"William Tell Overture (Gallop)",composer:"Rossini",bpm:152,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Bolero ──
// Ravel's Bolero in C major, ~90s
// Built around an endlessly repeating snare drum rhythm that crescendos
// The melody is stated by different instruments, each time louder
var bolero=(function(){
var bg=[],mel=[],t=0,q=60/72;
// The iconic Bolero snare rhythm: 3+3+3+3+2+2 pattern per bar
// ta-ta-ta ta-ta-ta ta-ta-ta ta-ta-ta ta-ta ta-ta
function snareBar(time,v){
var s=q/3;
for(var g=0;g<4;g++){
for(var n=0;n<3;n++){bg.push({t:time+g*q+n*s,f:300,d:0.05,i:'woodwind',v:v*0.3});}
}
// The two duplets at end
bg.push({t:time+q*4,f:300,d:0.05,i:'woodwind',v:v*0.35});
bg.push({t:time+q*4+q/2,f:300,d:0.05,i:'woodwind',v:v*0.35});
bg.push({t:time+q*5,f:300,d:0.05,i:'woodwind',v:v*0.35});
bg.push({t:time+q*5+q/2,f:300,d:0.05,i:'woodwind',v:v*0.35});
}
// Bolero Theme A (C major): C-D-E-F-E-D-E-C | E-C-D-C-B3-C-D-E-F-E-D-C
var themeA=[[C4,q,0],[D4,q,1],[E4,q*0.5,2],[F4,q*0.5,3],[E4,q*0.5,2],[D4,q*0.5,1],
[E4,q,2],[C4,q*2,0],
[E4,q*0.5,2],[C4,q*0.5,0],[D4,q*0.5,1],[C4,q*0.5,0],[B3,q*0.5,3],
[C4,q*0.5,0],[D4,q,1],[E4,q*0.5,2],[F4,q*0.5,3],[E4,q*0.5,2],[D4,q*0.5,1],[C4,q*2,0]];
// Bolero Theme B (chromatic inflection): C-D-Eb-D-C-D-Eb-F-Eb-D-C-Bb3-C
var themeB=[[C4,q,0],[D4,q*0.5,1],[Eb4,q*0.5,2],[D4,q*0.5,1],[C4,q*0.5,0],
[D4,q*0.5,1],[Eb4,q*0.5,2],[F4,q,3],[Eb4,q*0.5,2],[D4,q*0.5,1],
[C4,q,0],[Bb3,q,3],[C4,q*2,0]];
// Continuous harmonic bed — very sparse at first, grows
var harm=[
[0,20,[C3,G3,C4],0.06],[20,40,[C3,G3,C4],0.1],[40,60,[C3,G3,C4],0.15],
[60,80,[C3,G3,C4],0.2],[80,95,[C3,G3,E4],0.25]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=q*4){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:q*3.8,i:'strings',v:h[3]});});
}
});
// 7 statements of the theme, each louder with different instruments
var statements=[
{inst:'woodwind',v:0.18,theme:'A'}, // flute solo
{inst:'woodwind',v:0.22,theme:'A'}, // clarinet
{inst:'woodwind',v:0.28,theme:'B'}, // bassoon
{inst:'strings',v:0.35,theme:'A'}, // strings pizz
{inst:'strings',v:0.42,theme:'B'}, // strings arco
{inst:'brass',v:0.5,theme:'A'}, // brass
{inst:'brass',v:0.6,theme:'A'} // full orchestra
];
statements.forEach(function(st,si){
var barLen=q*6;
// Snare drum rhythm underneath
snareBar(t,0.1+si*0.06);
snareBar(t+barLen,0.1+si*0.06);
var theme=st.theme==='A'?themeA:themeB;
theme.forEach(function(n){
bg.push({t:t,f:n[0],d:n[1]*0.9,i:st.inst,v:st.v});
// Add harmony voices in later statements
if(si>=3)bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:st.v*0.4});
if(si>=4)bg.push({t:t,f:n[0]*1.5,d:n[1]*0.9,i:'strings',v:st.v*0.3});
if(si>=5){
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'strings',v:st.v*0.5});
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:st.v*0.25});
}
if(si>=6&&n[2]===0)bg.push({t:t,f:C3,d:0.2,i:'timpani',v:st.v*0.5});
mel.push({t:t,l:n[2]});
t+=n[1];
});
// Kick hit at end of each statement
bg.push({t:t,f:C3,d:0.3,i:'timpani',v:0.15+si*0.06});
mel.push({t:t,l:4});
t+=q*2;
});
// === FINALE: Modulation up + massive crescendo ===
// Theme in Db (up a half step) — the famous key change
var finaleTheme=[[Db4,q*0.8,0],[Eb4,q*0.4,1],[F4,q*0.4,3],[Eb4,q*0.4,1],[Db4,q*0.4,0],
[Eb4,q*0.8,1],[Db4,q*1.5,0]];
for(var fc=0;fc<2;fc++){
var fv=0.6+fc*0.08;
snareBar(t,0.4+fc*0.05);
finaleTheme.forEach(function(n){
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'brass',v:fv});
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'strings',v:fv*0.8});
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:fv*0.5});
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:fv*0.3});
mel.push({t:t,l:n[2]});t+=n[1];
});
bg.push({t:t,f:Db4/2,d:0.3,i:'timpani',v:fv*0.7});mel.push({t:t,l:4});t+=q;
}
// Final massive C chord
t+=q;
[C3,G3,C4,E4,G4,C5].forEach(function(f){
bg.push({t:t,f:f,d:3.5,i:'brass',v:0.7});bg.push({t:t,f:f,d:3.5,i:'strings',v:0.6});
bg.push({t:t,f:f,d:3.5,i:'choir',v:0.35});
});
bg.push({t:t,f:C3/2,d:3.0,i:'timpani',v:0.75});mel.push({t:t,l:4});
return {n:"Bolero",composer:"Ravel",bpm:72,duration:Math.ceil(t+4),isClassical:true,bg:bg,melody:mel};
})();
// ── Barber of Seville Overture ──
// Rossini's famous overture in E major, ~75s
// Quiet opening → crescendo string runs → the "Rossini crescendo"
var barber=(function(){
var bg=[],mel=[],t=0,q=60/144;
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});}
// Harmonic bed
var harm=[
[0,12,[E3,G3,B3],0.08],[12,28,[E3,G3,B3],0.14],[28,44,[E3,G3,B3],0.18],
[44,56,[A3,C4,E4],0.16],[56,72,[E3,G3,B3],0.22],[72,80,[E3,G3,B3],0.25]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.0){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:1.9,i:'strings',v:h[3]});});
}
});
// === SECTION 1 (0-12s): Quiet pizzicato opening ===
// Tip-toeing melody, very soft
var pizz=[[E4,q,2],[E4,q*0.5,2],[G4,q*0.5,3],[B4,q,0],[G4,q*0.5,3],[E4,q*0.5,2],
[D4,q,1],[E4,q,2],[G4,q*0.5,3],[E4,q*0.5,2],[D4,q,1],[E4,q*2,2]];
for(var pr=0;pr<2;pr++){
var pv=0.2+pr*0.05;
pizz.forEach(function(n){
str(t,n[0],n[1]*0.4,pv); // short staccato for pizzicato feel
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 2 (12-28s): The famous "Rossini crescendo" ===
// Repeated motif that gets louder and louder
var crescMotif=[[E4,q*0.5,2],[E4,q*0.5,2],[G4,q*0.5,3],[G4,q*0.5,3],
[B4,q*0.5,0],[B4,q*0.5,0],[E5,q*0.5,2],[D5,q*0.5,1],
[B4,q*0.5,0],[G4,q*0.5,3],[E4,q,2]];
for(var cr=0;cr<4;cr++){
var cv=0.22+cr*0.08;
var ci=cr<2?'strings':cr<3?'brass':'brass';
crescMotif.forEach(function(n){
bg.push({t:t,f:n[0],d:n[1]*0.85,i:ci,v:cv});
if(cr>=1)str(t,n[0],n[1]*0.85,cv*0.6);
if(cr>=2)bg.push({t:t,f:n[0]/2,d:n[1]*0.85,i:'bass',v:cv*0.4});
if(cr>=3){brs(t,n[0],n[1]*0.85,cv*0.5);bg.push({t:t,f:n[0],d:n[1]*0.85,i:'choir',v:cv*0.2});}
mel.push({t:t,l:n[2]});t+=n[1];
});
// Punctuating chord
if(cr>=2){bg.push({t:t,f:E3,d:0.3,i:'timpani',v:cv*0.5});}
mel.push({t:t,l:4});t+=q*1.5;
}
// === SECTION 3 (28-44s): Running string passages ===
// Fast scalar runs — the hallmark of Rossini
var run1=[E4,G4,B4,E5,D5,B4,G4,E4,D4,E4,G4,B4,D5,E5,D5,B4];
var r1Ln=[2,3,0,2,1,0,3,2,1,2,3,0,1,2,1,0];
for(var rr=0;rr<3;rr++){
var rv=0.4+rr*0.06;
var rq=q*(0.5-rr*0.05); // speeds up each time
run1.forEach(function(f,i){
str(t,f,rq*0.85,rv);
if(rr>0)brs(t,f,rq*0.85,rv*0.4);
if(rr>1)bg.push({t:t,f:f/2,d:rq*0.85,i:'bass',v:rv*0.3});
mel.push({t:t,l:r1Ln[i]});t+=rq;
});
// Big chord punctuation
[E3,B3,E4,G4].forEach(function(f){brs(t,f,0.4,rv);str(t,f,0.4,rv*0.8);});
bg.push({t:t,f:E3/2,d:0.3,i:'timpani',v:rv*0.6});mel.push({t:t,l:4});t+=q*2;
}
// === SECTION 4 (44-56s): Lyrical contrasting theme ===
var lyric=[[B4,q*1.5,0],[A4,q*0.5,1],[G4,q,3],[E4,q,2],[G4,q*1.5,3],[A4,q*0.5,1],
[B4,q*2,0],[E5,q*1.5,2],[D5,q*0.5,1],[B4,q,0],[G4,q,3],[E4,q*2,2]];
for(var ll=0;ll<2;ll++){
var lv=0.4+ll*0.1;
lyric.forEach(function(n){
str(t,n[0],n[1]*0.9,lv);brs(t,n[0],n[1]*0.9,lv*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:lv*0.35});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 5 (56-72s): Final Rossini crescendo + runs ===
for(var fc=0;fc<3;fc++){
var fv=0.5+fc*0.06;
var fq=q*(0.45-fc*0.04);
run1.forEach(function(f,i){
str(t,f,fq*0.85,fv);brs(t,f,fq*0.85,fv*0.6);
bg.push({t:t,f:f/2,d:fq*0.85,i:'bass',v:fv*0.4});
if(fc>0&&i%4===0)bg.push({t:t,f:E3,d:0.15,i:'timpani',v:fv*0.5});
mel.push({t:t,l:r1Ln[i]});t+=fq;
});
t+=q*0.5;
}
// === FINALE: Chords ===
for(var c=0;c<6;c++){
[E3,B3,E4,G4,B4].forEach(function(f){brs(t,f,0.35,0.65);str(t,f,0.35,0.55);});
bg.push({t:t,f:E3/2,d:0.35,i:'timpani',v:0.6});mel.push({t:t,l:4});t+=0.45;
}
[E3,B3,E4,G4,B4,E5].forEach(function(f){brs(t,f,3.0,0.7);str(t,f,3.0,0.6);});
bg.push({t:t,f:E3/2,d:2.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Barber of Seville Overture",composer:"Rossini",bpm:144,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Sabre Dance ──
// Khachaturian's frenetic Sabre Dance in D minor, ~75s
// Famous for its relentless, driving ostinato
var sabre=(function(){
var bg=[],mel=[],t=0,q=60/168;
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});}
function tutti(t,f,d,v){str(t,f,d,v);brs(t,f,d,(v||0.4)*0.6);bg.push({t:t,f:f/2,d:d,i:'bass',v:(v||0.4)*0.4});}
// Harmonic bed
var harm=[
[0,15,[D3,A3,D4],0.1],[15,30,[D3,A3,D4],0.15],[30,50,[D3,F3,A3],0.18],
[50,65,[D3,A3,D4],0.2],[65,80,[D3,A3,D4],0.25]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=1.5){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:1.4,i:'strings',v:h[3]});});
}
});
// === SECTION 1 (0-15s): The famous ostinato — D-D-D-Eb-D repeated ===
// Frantic repeated notes that define the piece
var ostinato=[[D4,q*0.5,0],[D4,q*0.5,0],[D4,q*0.5,0],[Eb4,q*0.5,1],[D4,q*0.5,0],
[D4,q*0.5,0],[D4,q*0.5,0],[Eb4,q*0.5,1]];
for(var os=0;os<6;os++){
var ov=0.3+os*0.04;
ostinato.forEach(function(n){
str(t,n[0],n[1]*0.7,ov);
if(os>=3)brs(t,n[0],n[1]*0.7,ov*0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
// Ascending burst: D-E-F-G-A
[D4,E4,F4,G4,A4].forEach(function(f,i){
str(t,f,q*0.4,ov);mel.push({t:t,l:[0,2,3,0,1][i]});t+=q*0.5;
});
bg.push({t:t,f:D3,d:0.2,i:'timpani',v:ov*0.6});mel.push({t:t,l:4});t+=q;
}
// === SECTION 2 (15-30s): Contrasting lyrical middle section ===
// Slower, sweeping melody in strings over sustained chords
var lyric=[[A4,q*2,1],[G4,q,0],[F4,q,3],[E4,q*2,2],[D4,q,0],
[F4,q*2,3],[E4,q,2],[D4,q,0],[A3,q*2,1],[D4,q*3,0]];
for(var lr=0;lr<3;lr++){
var lv=0.35+lr*0.08;
lyric.forEach(function(n){
str(t,n[0],n[1]*0.9,lv);
if(lr>0)brs(t,n[0],n[1]*0.9,lv*0.5);
if(lr>1)bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:lv*0.4});
mel.push({t:t,l:n[2]});t+=n[1];
});
if(lr>0){bg.push({t:t,f:D3,d:0.3,i:'timpani',v:lv*0.5});mel.push({t:t,l:4});t+=q;}
t+=q;
}
// === SECTION 3 (30-50s): Ostinato returns — faster, louder ===
var fastOst=[[D4,q*0.4,0],[D4,q*0.4,0],[D4,q*0.4,0],[Eb4,q*0.4,1],[D4,q*0.4,0],
[D4,q*0.4,0],[Eb4,q*0.4,1],[F4,q*0.4,3]];
for(var fo=0;fo<6;fo++){
var fv=0.45+fo*0.04;
fastOst.forEach(function(n){
tutti(t,n[0],n[1]*0.7,fv);
mel.push({t:t,l:n[2]});t+=n[1];
});
// Ascending chromatic burst
[D4,Eb4,E4,F4,G4,Ab4,A4,Bb4].forEach(function(f,i){
str(t,f,q*0.35,fv);brs(t,f,q*0.35,fv*0.5);
mel.push({t:t,l:[0,1,2,3,0,1,1,2][i]});t+=q*0.4;
});
// Slam
[D4,A4,D5].forEach(function(f){brs(t,f,0.3,fv);});
bg.push({t:t,f:D3,d:0.3,i:'timpani',v:fv*0.7});mel.push({t:t,l:4});t+=q;
}
// === SECTION 4 (50-65s): Wild descending runs + ostinato ===
var desc=[A4,G4,F4,E4,D4,E4,F4,G4,A4,Bb4,A4,G4,F4,E4,D4,A3];
var dLn=[1,0,3,2,0,2,3,0,1,2,1,0,3,2,0,4];
for(var dr=0;dr<3;dr++){
var dv=0.55+dr*0.05;
var dq=q*(0.45-dr*0.04);
desc.forEach(function(f,i){
tutti(t,f,dq*0.8,dv);
if(dr>0)bg.push({t:t,f:f,d:dq*0.8,i:'brass',v:dv*0.5});
if(dr>1&&i%4===0)bg.push({t:t,f:D3,d:0.15,i:'timpani',v:dv*0.6});
mel.push({t:t,l:dLn[i]});t+=dq;
});
t+=q*0.5;
// Ostinato fragment
fastOst.forEach(function(n){
tutti(t,n[0],n[1]*0.65,dv);mel.push({t:t,l:n[2]});t+=n[1]*0.85;
});
t+=q*0.5;
}
// === FINALE: Hammered D minor chords ===
for(var c=0;c<8;c++){
[D3,A3,D4,F4,A4].forEach(function(f){brs(t,f,0.3,0.65);str(t,f,0.3,0.55);});
bg.push({t:t,f:D3/2,d:0.3,i:'timpani',v:0.65});mel.push({t:t,l:4});t+=0.38;
}
[D3,A3,D4,F4,A4,D5].forEach(function(f){brs(t,f,2.5,0.7);str(t,f,2.5,0.6);});
bg.push({t:t,f:D3/2,d:2.0,i:'timpani',v:0.75});mel.push({t:t,l:4});
return {n:"Sabre Dance",composer:"Khachaturian",bpm:168,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Sorcerer's Apprentice ──
// Dukas's magical march in F minor, ~80s
// Famous bassoon theme → march builds → magical chaos
var sorcerer=(function(){
var bg=[],mel=[],t=0,q=60/132;
function ww(t,f,d,v){bg.push({t:t,f:f,d:d,i:'woodwind',v:v||0.3});}
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});}
// Harmonic bed — mysterious
var harm=[
[0,16,[F3,Ab3,C4],0.06],[16,32,[F3,Ab3,C4],0.1],[32,48,[F3,Ab3,C4],0.15],
[48,64,[Bb3,Db4,F4],0.18],[64,82,[F3,Ab3,C4],0.22]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.5){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:2.3,i:'strings',v:h[3]});});
}
});
// === SECTION 1 (0-16s): The famous bassoon theme ===
// Creeping march: F-Ab-C-Ab-F then Ab-C-Eb-C-Ab
var bsnTheme=[[F3,q,0],[Ab3,q*0.5,1],[C4,q*0.5,2],[Ab3,q*0.5,1],[F3,q*0.5,0],
[Ab3,q,1],[C4,q*0.5,2],[Eb4,q*0.5,3],[C4,q*0.5,2],[Ab3,q*0.5,1],
[F3,q*1.5,0],[C4,q*0.5,2],[Ab3,q,1],[F3,q*2,0]];
for(var br=0;br<3;br++){
var bv=0.2+br*0.06;
bsnTheme.forEach(function(n){
ww(t,n[0],n[1]*0.85,bv);
if(br>0)str(t,n[0]*2,n[1]*0.85,bv*0.3); // octave higher strings echo
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 2 (16-32s): March begins — walking brooms ===
// Steady march rhythm with the theme on top
var march=[[C4,q,2],[C4,q*0.5,2],[Ab3,q*0.5,1],[F3,q,0],[Ab3,q*0.5,1],[C4,q*0.5,2],
[Eb4,q,3],[Db4,q*0.5,0],[C4,q*0.5,2],[Ab3,q,1],[F3,q*1.5,0]];
for(var mr=0;mr<4;mr++){
var mv=0.3+mr*0.06;
var mInst=mr<2?'woodwind':'strings';
// March bass: oom-pah
for(var mb=0;mb<6;mb++){
bg.push({t:t+mb*q*2,f:F3,d:q*0.8,i:'bass',v:mv*0.5});
bg.push({t:t+mb*q*2+q,f:C3,d:q*0.8,i:'bass',v:mv*0.4});
if(mr>=2)bg.push({t:t+mb*q*2,f:F3,d:0.15,i:'timpani',v:mv*0.4});
}
march.forEach(function(n){
bg.push({t:t,f:n[0],d:n[1]*0.85,i:mInst,v:mv});
if(mr>=2)brs(t,n[0],n[1]*0.85,mv*0.4);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 3 (32-48s): Chaos builds — theme fragments overlap ===
// Faster fragments of the theme, like multiple brooms marching
var frag1=[[F3,q*0.5,0],[Ab3,q*0.5,1],[C4,q*0.5,2],[Eb4,q*0.5,3],[C4,q*0.5,2],[Ab3,q*0.5,1]];
var frag2=[[C4,q*0.5,2],[Db4,q*0.5,0],[C4,q*0.5,2],[Ab3,q*0.5,1],[F3,q*0.5,0],[Ab3,q*0.5,1]];
for(var ch=0;ch<4;ch++){
var cv=0.4+ch*0.05;
(ch%2===0?frag1:frag2).forEach(function(n){
str(t,n[0],n[1]*0.8,cv);brs(t,n[0],n[1]*0.8,cv*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.8,i:'bass',v:cv*0.4});
mel.push({t:t,l:n[2]});t+=n[1];
});
// Ascending rush
[F3,Ab3,C4,Eb4,F4,Ab4].forEach(function(f,i){
str(t,f,q*0.35,cv);mel.push({t:t,l:[0,1,2,3,0,1][i]});t+=q*0.4;
});
bg.push({t:t,f:F3,d:0.3,i:'timpani',v:cv*0.6});mel.push({t:t,l:4});t+=q;
}
// === SECTION 4 (48-64s): Full orchestra — magical pandemonium ===
// Full theme at double speed with brass
var fullTheme=[[F4,q*0.4,0],[Ab4,q*0.4,1],[C5,q*0.4,2],[Ab4,q*0.4,1],[F4,q*0.4,0],
[Ab4,q*0.4,1],[C5,q*0.4,2],[Eb5,q*0.4,3],[C5,q*0.4,2],[Ab4,q*0.4,1],
[F4,q*0.8,0],[C5,q*0.4,2],[Ab4,q*0.4,1],[F4,q*0.8,0]];
for(var ft=0;ft<4;ft++){
var fv=0.5+ft*0.05;
fullTheme.forEach(function(n){
str(t,n[0],n[1]*0.8,fv);brs(t,n[0],n[1]*0.8,fv*0.6);
bg.push({t:t,f:n[0]/2,d:n[1]*0.8,i:'bass',v:fv*0.4});
if(ft>1)bg.push({t:t,f:n[0],d:n[1]*0.8,i:'choir',v:fv*0.2});
if(ft>2&&n[2]===0)bg.push({t:t,f:F3,d:0.15,i:'timpani',v:fv*0.5});
mel.push({t:t,l:n[2]});t+=n[1];
});
bg.push({t:t,f:F3,d:0.3,i:'timpani',v:fv*0.6});mel.push({t:t,l:4});t+=q;
}
// === SECTION 5 (64-78s): Wild ending ===
// Descending cascades
[C5,Bb4,Ab4,G4,F4,Eb4,Db4,C4,Bb3,Ab3,G3,F3].forEach(function(f,i){
str(t,f,q*0.3,0.55);brs(t,f,q*0.3,0.45);
mel.push({t:t,l:[2,3,1,0,3,2,0,1,3,1,0,4][i]});t+=q*0.35;
});
t+=q;
// Final chords
for(var c=0;c<6;c++){
[F3,C4,F4,Ab4].forEach(function(f){brs(t,f,0.35,0.65);str(t,f,0.35,0.55);});
bg.push({t:t,f:F3/2,d:0.35,i:'timpani',v:0.6});mel.push({t:t,l:4});t+=0.45;
}
[F3,C4,F4,Ab4,C5].forEach(function(f){brs(t,f,3.0,0.65);str(t,f,3.0,0.55);});
bg.push({t:t,f:F3/2,d:2.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Sorcerer's Apprentice",composer:"Dukas",bpm:132,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Morning Mood ──
// Grieg's Peer Gynt — gentle sunrise in E major, ~80s
// Famous flute melody: G#-A-B-C#-B-A, then G#-A-B-C#-E-D#-C#-B-A-G#
var morning=(function(){
var bg=[],mel=[],t=0,q=60/60; // slow, pastoral tempo
function ww(t,f,d,v){bg.push({t:t,f:f,d:d,i:'woodwind',v:v||0.25});}
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.3});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.35});}
// Gentle harmonic bed — E major sustained strings
var harm=[
[0,18,[E3,Ab3,B3],0.06],[18,36,[E3,Ab3,B3],0.1],[36,54,[E3,Ab3,B3],0.15],
[54,70,[A3,Db4,E4],0.14],[70,85,[E3,Ab3,B3],0.18]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=3.0){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:2.8,i:'strings',v:h[3]});});
bg.push({t:ht,f:h[2][0]/2,d:2.8,i:'bass',v:h[3]*0.4});
}
});
// === The actual Grieg melody in E major ===
// G#(Ab4)-A-B-C#(Db5)-B-A | first phrase (ascending then descending)
var fluteMel1=[[Ab4,q*0.75,1],[A4,q*0.75,0],[B4,q*0.75,3],[Db5,q*0.75,2],
[B4,q*0.75,3],[A4,q*0.75,0]];
// G#-A-B-C#-E-D#(Eb5)-C#-B-A-G# | second phrase (up higher then back)
var fluteMel2=[[Ab4,q*0.75,1],[A4,q*0.75,0],[B4,q*0.75,3],[Db5,q*0.75,2],
[E5,q*0.75,0],[Eb5,q*0.75,1],[Db5,q*0.75,2],[B4,q*0.75,3],
[A4,q*0.75,0],[Ab4,q*1.5,1]];
// First statement — solo flute, very quiet
fluteMel1.forEach(function(n){ww(t,n[0],n[1]*0.9,0.2);mel.push({t:t,l:n[2]});t+=n[1];});
t+=q*0.5;
fluteMel2.forEach(function(n){ww(t,n[0],n[1]*0.9,0.22);mel.push({t:t,l:n[2]});t+=n[1];});
t+=q;
// Second statement — flute + light strings
fluteMel1.forEach(function(n){ww(t,n[0],n[1]*0.9,0.25);str(t,n[0],n[1]*0.9,0.1);mel.push({t:t,l:n[2]});t+=n[1];});
t+=q*0.5;
fluteMel2.forEach(function(n){ww(t,n[0],n[1]*0.9,0.28);str(t,n[0],n[1]*0.9,0.12);mel.push({t:t,l:n[2]});t+=n[1];});
t+=q;
// === SECTION 2 (18-36s): Melody grows — oboe joins, then strings ===
// Third statement — woodwind + strings
fluteMel1.forEach(function(n){
ww(t,n[0],n[1]*0.9,0.3);str(t,n[0],n[1]*0.9,0.2);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.5;
fluteMel2.forEach(function(n){
ww(t,n[0],n[1]*0.9,0.32);str(t,n[0],n[1]*0.9,0.22);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.12});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// Fourth statement — strings lead, richer
fluteMel1.forEach(function(n){
str(t,n[0],n[1]*0.9,0.35);ww(t,n[0],n[1]*0.9,0.2);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.5;
fluteMel2.forEach(function(n){
str(t,n[0],n[1]*0.9,0.38);ww(t,n[0],n[1]*0.9,0.22);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.15});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// === SECTION 3 (36-54s): Full sunrise — brass enters, melody soars ===
// Expanded melody reaching up to high E
var soarMel=[[Ab4,q*0.65,1],[A4,q*0.65,0],[B4,q*0.65,3],[Db5,q*0.65,2],
[E5,q*0.65,0],[Db5,q*0.65,2],[B4,q*0.65,3],[A4,q*0.65,0],
[Ab4,q*0.65,1],[A4,q*0.65,0],[B4,q*0.65,3],[Db5,q*0.65,2],
[E5,q*0.65,0],[Eb5,q*0.65,1],[Db5,q*1.3,2]];
for(var sr=0;sr<2;sr++){
var sv=0.4+sr*0.08;
soarMel.forEach(function(n){
str(t,n[0],n[1]*0.9,sv);brs(t,n[0],n[1]*0.9,sv*0.4);
ww(t,n[0],n[1]*0.9,sv*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:sv*0.3});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 4 (54-70s): Warm development — horns + full strings ===
// Broader phrasing with E major harmonies
var warmMel=[[Db5,q*1.3,2],[B4,q*0.65,3],[A4,q*0.65,0],[Ab4,q*1.3,1],
[A4,q*0.65,0],[B4,q*0.65,3],[Db5,q*1.3,2],[E5,q*1.3,0],
[Db5,q*0.65,2],[B4,q*0.65,3],[A4,q*0.65,0],[Ab4,q*2,1]];
for(var wr=0;wr<2;wr++){
var wv=0.48+wr*0.06;
warmMel.forEach(function(n){
str(t,n[0],n[1]*0.9,wv);brs(t,n[0],n[1]*0.9,wv*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:wv*0.35});
if(wr>0)bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:wv*0.2});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 5 (70-82s): Final sunrise — melody one last time, full and warm ===
fluteMel1.forEach(function(n){
str(t,n[0],n[1]*0.9,0.5);brs(t,n[0],n[1]*0.9,0.35);ww(t,n[0],n[1]*0.9,0.3);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.25});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.5;
fluteMel2.forEach(function(n){
str(t,n[0],n[1]*0.9,0.5);brs(t,n[0],n[1]*0.9,0.35);ww(t,n[0],n[1]*0.9,0.3);
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:0.2});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// Gentle ending — sustained E major chord fading
[E3,Ab3,B3,E4,Ab4,B4,E5].forEach(function(f){
str(t,f,4.0,0.4);ww(t,f,4.0,0.25);
});
bg.push({t:t,f:E3/2,d:3.0,i:'bass',v:0.2});mel.push({t:t,l:4});
return {n:"Morning Mood",composer:"Grieg",bpm:60,duration:Math.ceil(t+4),isClassical:true,bg:bg,melody:mel};
})();
// ── 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 mordent: A-G-A (whole step, per standard editions)
org(0,A4,0.12,0.55);org(0.15,G4,0.12,0.55);org(0.3,A4,0.9,0.6);
mel.push({t:0,l:0},{t:0.15,l:3},{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,G3,0.12,0.55);org(3.5,A3,0.9,0.6);
mel.push({t:3.2,l:0},{t:3.35,l:3},{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;
// === Toccata continuation: dramatic recitative-like passages ===
// Rapid ascending arpeggios (organ virtuosity)
var arp1=[D4,F4,A4,D5,F5,D5,A4,F4];
var a1Ln=[0,2,0,1,2,1,0,2];
[0.18,0.16,0.14].forEach(function(spd,si){
var av=0.4+si*0.06;
arp1.forEach(function(f,i){org(t,f,spd*0.8,av);mel.push({t:t,l:a1Ln[i]});t+=spd;});
t+=0.2;
});
// Sustained D minor chord with pedal point
[D3,A3,D4,F4,A4].forEach(function(f){org(t,f,2.5,0.6);});
bg.push({t:t,f:D3/2,d:2.0,i:'timpani',v:0.55});mel.push({t:t,l:4});t+=3.0;
// === Second toccata section: sequence descending through keys ===
// D minor → C major → Bb major → A major (dominant)
var seqKeys=[[D4,F4,A4,D5],[C4,E4,G4,C5],[Bb3,D4,F4,Bb4],[A3,Db4,E4,A4]];
seqKeys.forEach(function(ch,ki){
var sv=0.45+ki*0.04;
// Rapid broken chord
ch.forEach(function(f,i){org(t,f,0.15,sv);mel.push({t:t,l:[0,1,2,3][i]});t+=0.16;});
// Sustained chord
ch.forEach(function(f){org(t,f,1.2,sv);});
mel.push({t:t,l:4});t+=1.5;
});
t+=0.5;
// === Dramatic ascending chromatic passage ===
[D4,Eb4,E4,F4,Gb4,G4,Ab4,A4,Bb4,B4,C5,Db5,D5].forEach(function(f,i){
org(t,f,0.14,0.5+i*0.01);
bg.push({t:t,f:f/2,d:0.14,i:'bass',v:0.3});
mel.push({t:t,l:[0,1,2,3][i%4]});t+=0.15;
});
// Massive Neapolitan chord → V → i cadence
[Eb4,G4,Bb4,Eb5].forEach(function(f){org(t,f,1.5,0.6);});mel.push({t:t,l:4});t+=1.8;
[A3,Db4,E4,A4].forEach(function(f){org(t,f,1.2,0.6);});mel.push({t:t,l:4});t+=1.5;
// === Fugue: subject entries building in intensity ===
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];
// 8 entries from slow to fast, quiet to loud
var speeds=[0.32,0.28,0.25,0.22,0.20,0.18,0.16,0.14];
var vols=[0.25,0.3,0.35,0.4,0.45,0.5,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(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.35;
});
// === Stretto: subject entries overlapping, maximum intensity ===
// Two interleaved subjects at breakneck speed
var strSpd=0.12;
subj.forEach(function(f,i){
org(t,f,strSpd*0.85,0.65);org(t,f*2,strSpd*0.85,0.4);
bg.push({t:t,f:f/2,d:strSpd*0.85,i:'bass',v:0.5});
bg.push({t:t,f:f,d:strSpd*0.85,i:'strings',v:0.45});
if(i%2===0)bg.push({t:t,f:D3,d:0.2,i:'timpani',v:0.6});
mel.push({t:t,l:sLn[i]});t+=strSpd;
});
t+=0.3;
// === Grand finale: cascading scales and massive chords ===
// Descending cascade
[D5,C5,Bb4,A4,G4,F4,E4,D4,C4,Bb3,A3,G3].forEach(function(f,i){
org(t,f,0.12,0.6);bg.push({t:t,f:f,d:0.12,i:'strings',v:0.4});mel.push({t:t,l:[0,2,3,1][i%4]});t+=0.13;
});
t+=0.15;
// Ascending cascade
[D3,E3,F3,G3,A3,Bb3,C4,D4,E4,F4,G4,A4,Bb4,C5,D5].forEach(function(f,i){
org(t,f,0.11,0.6);bg.push({t:t,f:f,d:0.11,i:'strings',v:0.45});mel.push({t:t,l:[0,1,2,3][i%4]});t+=0.12;
});
t+=0.3;
// Final massive chords: iv-V-i (plagal approach)
for(var fc=0;fc<3;fc++){
var fch=[[G3,Bb3,D4,G4],[A3,Db4,E4,A4],[D3,A3,D4,F4,A4,D5]][fc];
fch.forEach(function(f){org(t,f,0.8,0.65);bg.push({t:t,f:f,d:0.8,i:'strings',v:0.5});});
bg.push({t:t,f:D3/2,d:0.7,i:'timpani',v:0.6});mel.push({t:t,l:4});t+=1.0;
}
// Sustained final D minor
[D3,A3,D4,F4,A4,D5].forEach(function(f){org(t,f,4.0,0.7);bg.push({t:t,f:f,d:4.0,i:'strings',v:0.5});});
bg.push({t:t,f:D3/2,d:3.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 ──
// J. Strauss II waltz in D major (arr. C major), ~75s
// The most famous waltz: ascending thirds C-E-G, G-B-D with oom-pah-pah bass
var danube=(function(){
var bg=[],mel=[],t=0,q=60/180; // waltz tempo, q = one beat of 3/4
// Waltz oom-pah-pah accompaniment helper
function waltz(time,root,third,v){
bg.push({t:time,f:root,d:q*0.8,i:'bass',v:v*0.5}); // oom
bg.push({t:time+q,f:third,d:q*0.6,i:'strings',v:v*0.25}); // pah
bg.push({t:time+q*2,f:third,d:q*0.6,i:'strings',v:v*0.2}); // pah
}
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.35});}
// Harmonic bed
var harm=[
[0,12,[C3,E3,G3],0.06],[12,30,[C3,E3,G3],0.1],[30,50,[C3,E3,G3],0.14],
[50,65,[G3,B3,D4],0.12],[65,80,[C3,E3,G3],0.18]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=3.0){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:2.8,i:'strings',v:h[3]});});
}
});
// === SECTION 1 (0-12s): Tremolo intro + rising anticipation ===
// Soft tremolo strings, then the famous ascending pickup
for(var tr=0;tr<8;tr++){
bg.push({t:tr*q*3,f:C4,d:q*2.5,i:'strings',v:0.12+tr*0.01});
bg.push({t:tr*q*3,f:E4,d:q*2.5,i:'strings',v:0.1+tr*0.01});
}
t=6;
// Pickup notes before the waltz: ascending B-C
str(t,B3,q*0.8,0.25);mel.push({t:t,l:3});t+=q;
// === SECTION 2 (7-30s): First waltz — the famous melody ===
// Real melody (transposed to C): C-C-(rest) | C-E-G | G-G-(rest) | G-B-D5
// Pattern: note on beat 1, sustained or leap on beat 1 of next bar
var waltz1=[
[C4,q*3,0],[C4,q*3,0],[C4,q,0],[E4,q,2],[G4,q,3],
[G4,q*3,3],[G4,q*3,3],[G4,q,3],[B4,q,0],[D5,q,1],
[D5,q*3,1],[C5,q*3,2],[C5,q,2],[E4,q,2],[G4,q,3],
[G4,q*2,3],[E4,q,2],[C4,q*3,0]
];
for(var w1=0;w1<2;w1++){
var wv=0.3+w1*0.08;
var bi=0;
waltz1.forEach(function(n){
str(t,n[0],n[1]*0.9,wv);
if(w1>0)brs(t,n[0],n[1]*0.9,wv*0.4);
// Add waltz bass on beat 1 of each 3-beat group
if(n[1]>=q*2)waltz(t,C3,E3,wv);
mel.push({t:t,l:n[2]});t+=n[1];bi++;
});
t+=q;
}
// === SECTION 3 (30-50s): Second waltz — contrasting theme ===
// More lyrical flowing melody
var waltz2=[
[E4,q*2,2],[D4,q,1],[C4,q*3,0],[E4,q*2,2],[G4,q,3],
[G4,q*2,3],[F4,q,1],[E4,q*3,2],[D4,q*2,1],[E4,q,2],
[F4,q*2,1],[E4,q,2],[D4,q*3,1],[C4,q*3,0]
];
for(var w2=0;w2<2;w2++){
var wv2=0.38+w2*0.08;
waltz2.forEach(function(n){
str(t,n[0],n[1]*0.9,wv2);
if(w2>0)brs(t,n[0],n[1]*0.9,wv2*0.4);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:wv2*0.3});
if(n[1]>=q*2)waltz(t,C3,E3,wv2);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 4 (50-65s): Waltz 1 returns — full orchestra ===
waltz1.forEach(function(n){
str(t,n[0],n[1]*0.9,0.52);brs(t,n[0],n[1]*0.9,0.4);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.3});
if(n[1]>=q*2)waltz(t,C3,E3,0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// Faster, more energetic repeat
waltz1.forEach(function(n){
str(t,n[0],n[1]*0.85,0.55);brs(t,n[0],n[1]*0.85,0.45);
bg.push({t:t,f:n[0],d:n[1]*0.85,i:'choir',v:0.2});
mel.push({t:t,l:n[2]});t+=n[1]*0.9;
});
// === FINALE: Big waltz chords ===
t+=q;
for(var c=0;c<6;c++){
[C4,E4,G4,C5].forEach(function(f){brs(t,f,q*2.5,0.6);str(t,f,q*2.5,0.5);});
bg.push({t:t,f:C3,d:q*2,i:'timpani',v:0.5});
mel.push({t:t,l:4});t+=q*3;
}
[C4,E4,G4,C5].forEach(function(f){brs(t,f,2.5,0.65);str(t,f,2.5,0.55);});
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=[B4,Db5,D5,Db5,B4,B4,Db5,D5,E5,D5,Db5,B4,A4,Gb4,B4,A4,G4,Gb4,E4,Gb4,G4,Gb4,B4];
var tLn=[1,0,0,0,1,1,0,0,0,0,0,1,2,3,1,2,2,3,3,3,2,3,1];
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:B3,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++){
[B3,D4,Gb4,B4].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;
}
[B3,D4,Gb4,B4].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'strings',v:0.55});});
bg.push({t:t,f:B3/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 ──
// Conductor-style: ~80s, A minor, famous chromatic runs
// Fast chromatic passages play in background; player hits accent peaks/valleys
var bumblebee=(function(){
var bg=[],mel=[];
function tutti(time,freq,dur,v){
bg.push({t:time,f:freq,d:dur,i:'strings',v:v});
bg.push({t:time,f:freq,d:dur,i:'brass',v:v*0.5});
if(freq>200)bg.push({t:time,f:freq/2,d:dur,i:'bass',v:v*0.4});
}
function timp(time,v){bg.push({t:time,f:A3,d:0.4,i:'timpani',v:v||0.5});}
// Chromatic scale note arrays
var chromDown=[E5,Eb5,D5,Db5,C5,B4,Bb4,A4,Ab4,G4,Gb4,F4,E4];
var chromUp=[E4,F4,Gb4,G4,Ab4,A4,Bb4,B4,C5,Db5,D5,Eb5,E5];
// Helper: play a fast chromatic run (background) with accent hits
// Every 4th note is hittable — player conducts the peaks
function chromRun(t0,notes,lanes,v,speed){
var ct=t0;
notes.forEach(function(f,i){
bg.push({t:ct,f:f,d:speed*0.85,i:'strings',v:v*0.6});
bg.push({t:ct,f:f,d:speed*0.85,i:'woodwind',v:v*0.3});
if(i%4===0){
mel.push({t:ct,l:lanes[Math.floor(i/4)%lanes.length]});
bg.push({t:ct,f:f,d:speed*2,i:'strings',v:v}); // accent
}
ct+=speed;
});
return ct;
}
// === CONTINUOUS HARMONIC BED ===
var harmony=[
[0,5,[A3,C4,E4],0.06], // Am - intro
[5,20,[A3,C4,E4],0.1], // Am - first runs
[20,35,[A3,C4,E4],0.14], // Am - development
[35,50,[E4,G4,B4],0.12], // Em - modulation
[50,65,[A3,C4,E4],0.18], // Am - recapitulation
[65,80,[A3,C4,E4],0.22], // Am - climax
[80,88,[A3,C4,E4],0.14] // Am - coda
];
harmony.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.0){
h[2].forEach(function(f){
bg.push({t:ht,f:f,d:1.9,i:'strings',v:h[3]});
});
bg.push({t:ht,f:h[2][0]/2,d:1.9,i:'bass',v:h[3]*0.5});
}
});
var t=0;
// === SECTION 1 (0-5s): INTRODUCTION — orchestra sets the scene ===
// Sustained Am chord builds, then a quick scale tease
[A3,C4,E4].forEach(function(f){
bg.push({t:0,f:f,d:3.0,i:'strings',v:0.15});
bg.push({t:0,f:f,d:3.0,i:'woodwind',v:0.08});
});
bg.push({t:0,f:A3/2,d:3.0,i:'bass',v:0.12});
// Quick chromatic tease (background only)
for(var ri=0;ri<8;ri++){
bg.push({t:3.0+ri*0.08,f:chromDown[ri],d:0.07,i:'strings',v:0.15+ri*0.02});
}
// Sparse hits to start
timp(1.5,0.3);mel.push({t:1.5,l:4});
timp(3.5,0.35);mel.push({t:3.5,l:4});
t=5.0;
// === SECTION 2 (5-20s): FIRST CHROMATIC RUNS — the famous buzzing ===
// Speed: 16th notes at ~0.09s each (very fast background)
var sp1=0.09;
// Run 1: descending E5→E4 (background buzzing, accents hittable)
t=chromRun(t,chromDown,[2,0,3,1],0.3,sp1);
t+=0.2;
// Run 2: ascending E4→E5
t=chromRun(t,chromUp,[1,3,0,2],0.32,sp1);
t+=0.25;
// Run 3: descending again, louder
t=chromRun(t,chromDown,[0,2,1,3],0.38,sp1);
t+=0.2;
// Run 4: ascending with strings thickening
var s2a=t;
for(ri=0;ri<30;ri++){
bg.push({t:s2a+ri*0.12,f:[A3,E4,A3,C4,E4,A3][ri%6],d:0.1,i:'strings',v:0.1});
}
t=chromRun(t,chromUp,[3,1,2,0],0.4,sp1);
t+=0.25;
// Repeated descending — building
t=chromRun(t,chromDown,[2,0,3,1],0.42,sp1);
t+=0.2;
t=chromRun(t,chromUp,[1,3,0,2],0.44,sp1);
t+=0.3;
// Orchestral accent between phrases
tutti(t,A4,0.4,0.45);timp(t,0.45);mel.push({t:t,l:4});t+=0.6;
// === SECTION 3 (20-35s): DEVELOPMENT — new patterns, thicker texture ===
var s3s=t;
// Woodwind sustained chords underneath
for(var bi=0;bi<8;bi++){
[A3,C4,E4].forEach(function(f){
bg.push({t:s3s+bi*1.8,f:f,d:1.6,i:'woodwind',v:0.1});
});
}
// Partial runs — just the top half, then bottom half
var chromTopDown=[E5,Eb5,D5,Db5,C5,B4,Bb4,A4];
var chromBotUp=[A4,Bb4,B4,C5,Db5,D5,Eb5,E5];
t=chromRun(t,chromTopDown,[2,0,3,1],0.42,sp1);
t+=0.15;
t=chromRun(t,chromBotUp,[1,3,0,2],0.44,sp1);
t+=0.2;
// Full descending with brass accents
chromDown.forEach(function(f,i){
bg.push({t:t+i*sp1,f:f,d:sp1*0.85,i:'strings',v:0.45});
if(i%4===0){
bg.push({t:t+i*sp1,f:f,d:sp1*3,i:'brass',v:0.3});
mel.push({t:t+i*sp1,l:[0,2,1,3][Math.floor(i/4)]});
}
});
t+=chromDown.length*sp1+0.2;
// Full ascending
t=chromRun(t,chromUp,[3,1,0,2],0.48,sp1);
t+=0.25;
// Double-speed burst (even faster!)
var sp2=0.065;
t=chromRun(t,chromDown,[2,0,3,1],0.5,sp2);
t+=0.15;
t=chromRun(t,chromUp,[1,3,0,2],0.52,sp2);
t+=0.3;
tutti(t,A4,0.4,0.5);timp(t,0.5);mel.push({t:t,l:4});t+=0.6;
// === SECTION 4 (35-50s): MODULATION TO E MINOR — new color ===
var chromDownE=[B4,Bb4,A4,Ab4,G4,Gb4,F4,E4,Eb4,D4,Db4,C4,B3];
var chromUpE=[B3,C4,Db4,D4,Eb4,E4,F4,Gb4,G4,Ab4,A4,Bb4,B4];
var s4s=t;
for(bi=0;bi<8;bi++){
[E4,G4,B4].forEach(function(f){
bg.push({t:s4s+bi*1.8,f:f,d:1.6,i:'strings',v:0.12});
});
bg.push({t:s4s+bi*1.8,f:E3,d:1.6,i:'bass',v:0.15});
}
t=chromRun(t,chromDownE,[3,1,0,2],0.45,sp1);
t+=0.2;
t=chromRun(t,chromUpE,[2,0,3,1],0.48,sp1);
t+=0.25;
t=chromRun(t,chromDownE,[0,2,1,3],0.5,sp1);
t+=0.2;
t=chromRun(t,chromUpE,[1,3,2,0],0.52,sp1);
t+=0.25;
// Fast burst in E minor
t=chromRun(t,chromDownE,[3,1,0,2],0.55,sp2);
t+=0.15;
t=chromRun(t,chromUpE,[2,0,3,1],0.55,sp2);
t+=0.3;
// Transition back to A minor
[[E4,2],[F4,1],[G4,3],[A4,0]].forEach(function(n){
tutti(t,n[0],0.25,0.5);mel.push({t:t,l:n[1]});t+=0.3;
});
timp(t,0.55);mel.push({t:t,l:4});t+=0.5;
// === SECTION 5 (50-65s): RECAPITULATION — full orchestra, louder ===
var s5s=t;
for(ri=0;ri<70;ri++){
var rf5=[A3,C4,E4,A4,E4,C4][ri%6];
bg.push({t:s5s+ri*0.1,f:rf5,d:0.09,i:'strings',v:0.12});
}
for(bi=0;bi<8;bi++){
bg.push({t:s5s+bi*1.8,f:A3/2,d:1.6,i:'bass',v:0.2});
if(bi%2===0)timp(s5s+bi*1.8,0.35);
}
// Louder runs with brass doubling accents
t=chromRun(t,chromDown,[0,2,3,1],0.55,sp1);
t+=0.15;
t=chromRun(t,chromUp,[1,3,0,2],0.58,sp1);
t+=0.2;
// Fast burst
t=chromRun(t,chromDown,[2,0,1,3],0.6,sp2);
t+=0.12;
t=chromRun(t,chromUp,[3,1,2,0],0.62,sp2);
t+=0.2;
// Repeated descending — relentless buzzing
t=chromRun(t,chromDown,[0,2,3,1],0.6,sp2);
t+=0.12;
t=chromRun(t,chromUp,[1,3,0,2],0.62,sp2);
t+=0.15;
t=chromRun(t,chromDown,[2,0,1,3],0.64,sp2);
t+=0.2;
tutti(t,A4,0.35,0.58);timp(t,0.55);mel.push({t:t,l:4});t+=0.5;
// === SECTION 6 (65-80s): CLIMAX — maximum speed, fff ===
var s6s=t;
for(ri=0;ri<80;ri++){
var rf6=[A3,E4,A4,C5,A4,E4,C4,A3][ri%8];
bg.push({t:s6s+ri*0.08,f:rf6,d:0.07,i:'strings',v:0.15});
if(ri%6===0)bg.push({t:s6s+ri*0.08,f:rf6*0.5,d:0.2,i:'brass',v:0.1});
}
for(bi=0;bi<8;bi++){
bg.push({t:s6s+bi*1.6,f:A3/2,d:1.4,i:'bass',v:0.25});
timp(s6s+bi*1.6,0.45);
}
// Fastest runs — sp3 speed
var sp3=0.055;
t=chromRun(t,chromDown,[0,2,3,1],0.65,sp3);
t+=0.1;
t=chromRun(t,chromUp,[1,3,0,2],0.68,sp3);
t+=0.1;
t=chromRun(t,chromDown,[2,0,1,3],0.7,sp3);
t+=0.1;
t=chromRun(t,chromUp,[3,1,2,0],0.7,sp3);
t+=0.15;
// Repeated rapid descents — the bumblebee is frantic
t=chromRun(t,chromDown,[0,2,3,1],0.72,sp3);
t+=0.08;
t=chromRun(t,chromDown,[2,0,1,3],0.72,sp3);
t+=0.08;
t=chromRun(t,chromUp,[1,3,0,2],0.74,sp3);
t+=0.1;
// Final ascending blast to peak
[E4,F4,G4,A4,B4,C5,D5,E5].forEach(function(f,i){
bg.push({t:t,f:f,d:0.06,i:'strings',v:0.7});
bg.push({t:t,f:f,d:0.06,i:'brass',v:0.5});
if(i%2===0)mel.push({t:t,l:[0,1,2,3][Math.floor(i/2)]});
t+=0.07;
});
t+=0.2;
// === SECTION 7 (80-88s): CODA — final chords ===
// Big orchestral chords — the bumblebee lands
var chords=[[A3,C4,E4,A4],[E4,G4,B4,E5],[A3,C4,E4,A4],[A3,C4,E4,A4]];
chords.forEach(function(ch,ci){
var ct=t+ci*1.5;
ch.forEach(function(f){
bg.push({t:ct,f:f,d:1.3,i:'strings',v:0.55});
bg.push({t:ct,f:f,d:1.3,i:'brass',v:0.45});
});
bg.push({t:ct,f:ch[0]/2,d:1.3,i:'bass',v:0.45});
timp(ct,0.6);mel.push({t:ct,l:4});
// Quick chromatic fill between chords
if(ci<3){
for(var fi=0;fi<4;fi++){
bg.push({t:ct+0.6+fi*0.08,f:chromDown[fi*3],d:0.07,i:'strings',v:0.4});
}
mel.push({t:ct+0.6,l:ci%2===0?0:2});
}
});
t+=chords.length*1.5;
// Final sustained Am chord
[A3,C4,E4,A4,E5].forEach(function(f){
bg.push({t:t,f:f,d:4.0,i:'strings',v:0.5});
bg.push({t:t,f:f,d:4.0,i:'brass',v:0.4});
});
bg.push({t:t,f:A3/2,d:3.5,i:'bass',v:0.5});
bg.push({t:t,f:A3,d:3.0,i:'timpani',v:0.6});
mel.push({t:t,l:4});
return {n:"Flight of the Bumblebee",composer:"Rimsky-Korsakov",bpm:180,duration:Math.ceil(t+4.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Dance of the Sugar Plum Fairy ──
var sugarplum=(function(){
var bg=[],mel=[],t=0,q=0.4;
// Celesta melody from The Nutcracker — staccato descending/ascending pattern in E minor
var theme=[B4,A4,Ab4,A4,C5,B4,A4,Ab4,E4,Ab4,B4,E5,D5,C5,B4];
var tLn=[3,0,1,0,2,3,0,1,2,1,3,2,1,0,3];
var ans=[B4,A4,Ab4,A4,C5,B4,A4];
var aLn=[3,0,1,0,2,3,0];
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;
}
[E3,B3,E4,Ab4,B4].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'strings',v:0.5});});
bg.push({t:t,f:E3/2,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) ──
// Bizet's famous aria in D minor, ~80s
// Descending chromatic melody with the habanera rhythm (dotted + sixteenth)
var habanera=(function(){
var bg=[],mel=[],t=0,q=60/108;
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.35});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.35});}
function ww(t,f,d,v){bg.push({t:t,f:f,d:d,i:'woodwind',v:v||0.3});}
// Harmonic bed
var harm=[
[0,18,[D3,F3,A3],0.08],[18,36,[D3,F3,A3],0.12],[36,54,[D3,F3,A3],0.16],
[54,68,[Bb3,D4,F4],0.14],[68,84,[D3,F3,A3],0.18]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.0){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:1.9,i:'strings',v:h[3]});});
}
});
// The habanera bass rhythm: dotted-eighth + sixteenth + quarter + quarter
// DUM da-DUM DUM (characteristic Cuban rhythm)
function habBass(time,root,v){
bg.push({t:time,f:root,d:q*0.7,i:'bass',v:v*0.5});
bg.push({t:time+q*0.75,f:root*1.5,d:q*0.2,i:'bass',v:v*0.35});
bg.push({t:time+q,f:root,d:q*0.7,i:'bass',v:v*0.45});
bg.push({t:time+q*2,f:root,d:q*0.7,i:'bass',v:v*0.4});
}
// === SECTION 1 (0-18s): The famous descending chromatic melody ===
// D-Db-C-B-Bb-A-Ab-G — each note slightly longer, seductive descent
var desc1=[[D5,q*0.7,0],[Db5,q*0.7,1],[C5,q*0.7,2],[B4,q*0.7,3],
[Bb4,q*0.7,2],[A4,q*0.7,1],[Ab4,q*0.7,0],[G4,q*1.4,3]];
// Answer phrase: bouncing back up
var ans1=[[A4,q*0.5,1],[Bb4,q*0.5,2],[A4,q,1],[G4,q*0.5,3],[A4,q*0.5,1],[D4,q*1.5,0]];
for(var s1=0;s1<3;s1++){
var v1=0.2+s1*0.05;
habBass(t,D3,v1);habBass(t+q*3,D3,v1);
desc1.forEach(function(n){
ww(t,n[0],n[1]*0.9,v1);
if(s1>0)str(t,n[0],n[1]*0.9,v1*0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
ans1.forEach(function(n){
ww(t,n[0],n[1]*0.9,v1);
if(s1>0)str(t,n[0],n[1]*0.9,v1*0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 2 (18-36s): Second verse — strings take melody ===
// Same chromatic descent but with richer orchestration
var desc2=[[D5,q*0.65,0],[Db5,q*0.65,1],[C5,q*0.65,2],[B4,q*0.65,3],
[Bb4,q*0.65,2],[A4,q*0.65,1],[Ab4,q*0.65,0],[G4,q*1.3,3]];
var ans2=[[A4,q*0.5,1],[Bb4,q*0.5,2],[C5,q*0.5,2],[A4,q,1],[F4,q*0.5,3],[D4,q*1.5,0]];
for(var s2=0;s2<3;s2++){
var v2=0.32+s2*0.06;
habBass(t,D3,v2);habBass(t+q*3,A3,v2);
desc2.forEach(function(n){
str(t,n[0],n[1]*0.9,v2);ww(t,n[0],n[1]*0.9,v2*0.5);
if(s2>0)bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:v2*0.3});
mel.push({t:t,l:n[2]});t+=n[1];
});
ans2.forEach(function(n){
str(t,n[0],n[1]*0.9,v2);ww(t,n[0],n[1]*0.9,v2*0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
bg.push({t:t,f:D3,d:0.2,i:'timpani',v:v2*0.4});mel.push({t:t,l:4});t+=q;
}
// === SECTION 3 (36-54s): Major key contrast — "L'amour" refrain ===
// Shifts to D major — bright, passionate
var refrain=[[D4,q,0],[F4,q*0.5,3],[A4,q*0.5,1],[D5,q*1.5,0],
[C5,q*0.5,2],[Bb4,q*0.5,2],[A4,q,1],[G4,q*0.5,3],[A4,q*0.5,1],
[D4,q*2,0]];
for(var s3=0;s3<3;s3++){
var v3=0.4+s3*0.06;
habBass(t,D3,v3);habBass(t+q*3,A3,v3);
refrain.forEach(function(n){
str(t,n[0],n[1]*0.9,v3);brs(t,n[0],n[1]*0.9,v3*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:v3*0.35});
if(s3>1)bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:v3*0.2});
mel.push({t:t,l:n[2]});t+=n[1];
});
if(s3>0){bg.push({t:t,f:D3,d:0.3,i:'timpani',v:v3*0.5});mel.push({t:t,l:4});}
t+=q;
}
// === SECTION 4 (54-68s): Return of chromatic descent — full orchestra ===
for(var s4=0;s4<3;s4++){
var v4=0.5+s4*0.05;
habBass(t,D3,v4);habBass(t+q*3,D3,v4);
desc1.forEach(function(n){
str(t,n[0],n[1]*0.9,v4);brs(t,n[0],n[1]*0.9,v4*0.6);
ww(t,n[0],n[1]*0.9,v4*0.4);
if(s4>0)bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:v4*0.2});
mel.push({t:t,l:n[2]});t+=n[1];
});
ans1.forEach(function(n){
str(t,n[0],n[1]*0.9,v4);brs(t,n[0],n[1]*0.9,v4*0.6);
mel.push({t:t,l:n[2]});t+=n[1];
});
bg.push({t:t,f:D3,d:0.25,i:'timpani',v:v4*0.6});mel.push({t:t,l:4});t+=q;
}
// === FINALE: Passionate ending ===
// Final descent + big D minor chord
desc1.forEach(function(n){
str(t,n[0],n[1]*0.8,0.6);brs(t,n[0],n[1]*0.8,0.5);
bg.push({t:t,f:n[0],d:n[1]*0.8,i:'choir',v:0.3});
mel.push({t:t,l:n[2]});t+=n[1]*0.85;
});
t+=q;
for(var c=0;c<4;c++){
[D3,A3,D4,F4].forEach(function(f){brs(t,f,0.4,0.6);str(t,f,0.4,0.5);});
bg.push({t:t,f:D3/2,d:0.4,i:'timpani',v:0.6});mel.push({t:t,l:4});t+=0.5;
}
[D3,A3,D4,F4,A4].forEach(function(f){brs(t,f,3.0,0.6);str(t,f,3.0,0.5);});
bg.push({t:t,f:D3/2,d:2.5,i:'timpani',v:0.65});mel.push({t:t,l:4});
return {n:"Habanera (Carmen)",composer:"Bizet",bpm:108,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── Spring (Four Seasons) ──
// Vivaldi's Spring from The Four Seasons in E major, ~75s
// Joyful opening ritornello → birdsong trills → pastoral dance
var spring=(function(){
var bg=[],mel=[],t=0,q=60/120;
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.35});}
// Harmonic bed
var harm=[
[0,14,[E3,G3,B3],0.1],[14,28,[E3,G3,B3],0.14],[28,44,[A3,C4,E4],0.13],
[44,58,[E3,G3,B3],0.16],[58,78,[E3,G3,B3],0.2]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.0){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:1.9,i:'strings',v:h[3]});});
bg.push({t:ht,f:h[2][0]/2,d:1.9,i:'bass',v:h[3]*0.5});
}
});
// === SECTION 1 (0-14s): The famous opening ritornello ===
// Bright, joyful E major: E-E-E-G#-B-B-B-G#-E
var rit=[[E4,q*0.5,2],[E4,q*0.5,2],[E4,q*0.5,2],[Ab4,q*0.5,1],[B4,q,0],
[B4,q*0.5,0],[B4,q*0.5,0],[Ab4,q*0.5,1],[E4,q*1,2]];
// Answer: ascending scale burst
var ritAns=[[E4,q*0.5,2],[Ab4,q*0.5,1],[B4,q*0.5,0],[E5,q,2],
[D5,q*0.5,1],[B4,q*0.5,0],[Ab4,q*0.5,1],[E4,q*1,2]];
for(var r1=0;r1<2;r1++){
var rv=0.35+r1*0.08;
rit.forEach(function(n){str(t,n[0],n[1]*0.85,rv);mel.push({t:t,l:n[2]});t+=n[1];});
t+=q*0.3;
ritAns.forEach(function(n){str(t,n[0],n[1]*0.85,rv);mel.push({t:t,l:n[2]});t+=n[1];});
t+=q;
}
// === SECTION 2 (14-28s): Birdsong — trills and rapid ornaments ===
// High violin trills imitating birds
var bird1=[[E5,q*0.25,2],[D5,q*0.25,1],[E5,q*0.25,2],[D5,q*0.25,1],
[E5,q*0.5,2],[B4,q*0.5,0],[Ab4,q*0.5,1],[E4,q,2]];
var bird2=[[B4,q*0.25,0],[A4,q*0.25,1],[B4,q*0.25,0],[A4,q*0.25,1],
[B4,q*0.5,0],[Ab4,q*0.5,1],[E4,q*0.5,2],[B3,q,3]];
for(var b=0;b<3;b++){
var bv=0.3+b*0.06;
var birds=b%2===0?bird1:bird2;
birds.forEach(function(n){
str(t,n[0],n[1]*0.8,bv);
if(b>0)bg.push({t:t,f:n[0],d:n[1]*0.8,i:'woodwind',v:bv*0.4});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.5;
// Gentle bass response
[E3,B3,E4].forEach(function(f){bg.push({t:t,f:f,d:q*1.5,i:'bass',v:bv*0.3});});
mel.push({t:t,l:4});t+=q*2;
}
// === SECTION 3 (28-44s): Ritornello returns — fuller ===
for(var r2=0;r2<2;r2++){
var rv2=0.42+r2*0.08;
rit.forEach(function(n){
str(t,n[0],n[1]*0.85,rv2);brs(t,n[0],n[1]*0.85,rv2*0.4);
bg.push({t:t,f:n[0]/2,d:n[1]*0.85,i:'bass',v:rv2*0.3});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.3;
ritAns.forEach(function(n){
str(t,n[0],n[1]*0.85,rv2);brs(t,n[0],n[1]*0.85,rv2*0.4);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// Running scales
[E4,Ab4,B4,E5,D5,B4,Ab4,E4,D4,E4,Ab4,B4,D5,E5,Ab5,E5].forEach(function(f,i){
str(t,f,q*0.35,0.5);mel.push({t:t,l:[2,1,0,2,1,0,1,2,0,2,1,0,1,2,0,2][i]});t+=q*0.4;
});
t+=q;
// === SECTION 4 (44-58s): Pastoral dance — lilting 12/8 feel ===
var dance=[[E4,q,2],[Ab4,q*0.5,1],[B4,q*0.5,0],[E4,q*0.5,2],[Ab4,q*0.5,1],
[B4,q,0],[E5,q*0.5,2],[D5,q*0.5,1],[B4,q,0],[Ab4,q*0.5,1],[E4,q*1,2]];
for(var d=0;d<3;d++){
var dv=0.45+d*0.06;
dance.forEach(function(n){
str(t,n[0],n[1]*0.85,dv);
if(d>0)brs(t,n[0],n[1]*0.85,dv*0.4);
if(d>1)bg.push({t:t,f:n[0]/2,d:n[1]*0.85,i:'bass',v:dv*0.35});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
}
// === SECTION 5 (58-72s): Final ritornello — full orchestra ===
for(var r3=0;r3<3;r3++){
var rv3=0.52+r3*0.06;
rit.forEach(function(n){
str(t,n[0],n[1]*0.85,rv3);brs(t,n[0],n[1]*0.85,rv3*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.85,i:'bass',v:rv3*0.35});
if(r3>1)bg.push({t:t,f:n[0],d:n[1]*0.85,i:'choir',v:rv3*0.2});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.3;
ritAns.forEach(function(n){
str(t,n[0],n[1]*0.85,rv3);brs(t,n[0],n[1]*0.85,rv3*0.5);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q*0.5;
}
// Finale chords
for(var c=0;c<4;c++){
[E3,B3,E4,Ab4,B4].forEach(function(f){str(t,f,0.4,0.6);brs(t,f,0.4,0.5);});
mel.push({t:t,l:4});t+=0.5;
}
[E3,B3,E4,Ab4,B4,E5].forEach(function(f){str(t,f,3.0,0.6);brs(t,f,3.0,0.5);});
bg.push({t:t,f:E3/2,d:2.5,i:'timpani',v:0.55});mel.push({t:t,l:4});
return {n:"Spring (Four Seasons)",composer:"Vivaldi",bpm:120,duration:Math.ceil(t+3),isClassical:true,bg:bg,melody:mel};
})();
// ── 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,Ab4,B4,A4];
var p2Ln=[1,2,0,3,2,1,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];
// Real Pachelbel violin melody: F#5-E5-D5-C#5-B4-A4-B4-C#5 (var 1) then D5-C#5-B4-A4-G4-F#4-G4-A4 (var 2)
var melody2=[Gb4,E4,D4,Db4,B3,A3,B3,Db4,D4,Db4,B3,A3,G3,Gb3,G3,A3];
var m2Ln=[2,1,0,1,3,0,3,1,0,1,3,0,3,2,3,0];
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,A4,A4,A4,Bb4,A4,G4,F4,F4,E4,F4,G4,A4,A4,Bb4,A4,G4,F4,E4,D4];
var fLn=[0,0,0,0,2,0,3,1,1,2,1,3,0,0,2,0,3,1,2,0];
var slow=[D4,D4,E4,F4,F4,E4,D4,E4,F4,A4];
var sLn=[0,0,2,1,1,2,0,2,1,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++){
[D3,A3,D4,F4].forEach(function(f){orch(t,f,0.35,0.65,'brass');});
bg.push({t:t,f:D3/2,d:0.35,i:'timpani',v:0.65});mel.push({t:t,l:4});t+=0.45;
}
[D3,A3,D4,F4,A4].forEach(function(f){orch(t,f,2.0,0.6,'brass');orch(t,f,2.0,0.5);});
bg.push({t:t,f:D3/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 ──
// Beethoven's 9th Symphony finale in D major, ~85s
// The most famous melody in classical music, builds from solo to full chorus
var odeToJoy=(function(){
var bg=[],mel=[],t=0,q=60/100;
function str(t,f,d,v){bg.push({t:t,f:f,d:d,i:'strings',v:v||0.4});}
function brs(t,f,d,v){bg.push({t:t,f:f,d:d,i:'brass',v:v||0.4});}
function ww(t,f,d,v){bg.push({t:t,f:f,d:d,i:'woodwind',v:v||0.3});}
// Harmonic bed
var harm=[
[0,16,[D3,A3,D4],0.08],[16,32,[D3,A3,D4],0.12],[32,48,[D3,A3,D4],0.16],
[48,64,[G3,B3,D4],0.15],[64,88,[D3,A3,D4],0.2]
];
harm.forEach(function(h){
for(var ht=h[0];ht<h[1];ht+=2.5){
h[2].forEach(function(f){bg.push({t:ht,f:f,d:2.3,i:'strings',v:h[3]});});
bg.push({t:ht,f:h[2][0]/2,d:2.3,i:'bass',v:h[3]*0.5});
}
});
// The Ode to Joy melody (A section):
// E-E-F-G | G-F-E-D | C-C-D-E | E-D-D
var melA=[[Gb4,q,2],[Gb4,q,2],[G4,q,3],[A4,q,1],
[A4,q,1],[G4,q,3],[Gb4,q,2],[E4,q,0],
[D4,q,0],[D4,q,0],[E4,q,2],[Gb4,q,2],
[Gb4,q*1.5,2],[E4,q*0.5,0],[E4,q*2,0]];
// B section (contrasting middle):
// E-E-F-G | G-F-E-D | C-C-D-E | D-C-C
var melB=[[Gb4,q,2],[Gb4,q,2],[G4,q,3],[A4,q,1],
[A4,q,1],[G4,q,3],[Gb4,q,2],[E4,q,0],
[D4,q,0],[D4,q,0],[E4,q,2],[Gb4,q,2],
[E4,q*1.5,0],[D4,q*0.5,1],[D4,q*2,1]];
// === SECTION 1 (0-16s): Quiet cellos — theme stated simply ===
melA.forEach(function(n){
str(t,n[0]/2,n[1]*0.9,0.2); // cello register
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
melB.forEach(function(n){
str(t,n[0]/2,n[1]*0.9,0.22);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// === SECTION 2 (16-32s): Violas + woodwinds join ===
melA.forEach(function(n){
str(t,n[0],n[1]*0.9,0.3);ww(t,n[0],n[1]*0.9,0.18);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
melB.forEach(function(n){
str(t,n[0],n[1]*0.9,0.32);ww(t,n[0],n[1]*0.9,0.2);
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// === SECTION 3 (32-48s): Full strings + brass hint ===
melA.forEach(function(n){
str(t,n[0],n[1]*0.9,0.42);brs(t,n[0],n[1]*0.9,0.2);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.2});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
melB.forEach(function(n){
str(t,n[0],n[1]*0.9,0.44);brs(t,n[0],n[1]*0.9,0.22);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.22});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// === SECTION 4 (48-64s): Development — contrasting bridge ===
// Middle section with different harmonies
var bridge=[[E4,q,0],[E4,q*0.5,0],[Gb4,q*0.5,2],[A4,q,1],[A4,q*0.5,1],[G4,q*0.5,3],
[Gb4,q,2],[E4,q*0.5,0],[D4,q*0.5,1],[E4,q,2],[D4,q,1],[A3,q*2,0]];
for(var br=0;br<2;br++){
var bv=0.4+br*0.08;
bridge.forEach(function(n){
str(t,n[0],n[1]*0.9,bv);brs(t,n[0],n[1]*0.9,bv*0.5);
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:bv*0.4});
if(br>0)bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:bv*0.25});
mel.push({t:t,l:n[2]});t+=n[1];
});
bg.push({t:t,f:D3,d:0.3,i:'timpani',v:bv*0.5});mel.push({t:t,l:4});t+=q;
}
// === SECTION 5 (64-80s): Grand finale — full orchestra + choir ===
// "Freude, schöner Götterfunken" at full power
melA.forEach(function(n){
str(t,n[0],n[1]*0.9,0.55);brs(t,n[0],n[1]*0.9,0.45);
ww(t,n[0],n[1]*0.9,0.3);
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:0.35});
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.3});
if(n[2]===1||n[2]===0)bg.push({t:t,f:D3,d:0.15,i:'timpani',v:0.35});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
melB.forEach(function(n){
str(t,n[0],n[1]*0.9,0.58);brs(t,n[0],n[1]*0.9,0.48);
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:0.38});
bg.push({t:t,f:n[0]/2,d:n[1]*0.9,i:'bass',v:0.3});
mel.push({t:t,l:n[2]});t+=n[1];
});
t+=q;
// Final triumphant repeat of A
melA.forEach(function(n){
str(t,n[0],n[1]*0.9,0.6);brs(t,n[0],n[1]*0.9,0.5);
bg.push({t:t,f:n[0],d:n[1]*0.9,i:'choir',v:0.4});
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];
});
// === CODA: Big D major chords ===
t+=q;
for(var c=0;c<6;c++){
[D3,A3,D4,Gb4,A4].forEach(function(f){brs(t,f,0.45,0.65);str(t,f,0.45,0.55);});
bg.push({t:t,f:D3/2,d:0.45,i:'timpani',v:0.6});
bg.push({t:t,f:D4,d:0.45,i:'choir',v:0.4});
mel.push({t:t,l:4});t+=0.55;
}
[D3,A3,D4,Gb4,A4,D5].forEach(function(f){
brs(t,f,4.0,0.7);str(t,f,4.0,0.6);bg.push({t:t,f:f,d:4.0,i:'choir',v:0.4});
});
bg.push({t:t,f:D3/2,d:3.0,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Ode to Joy",composer:"Beethoven",bpm:100,duration:Math.ceil(t+4),isClassical:true,bg:bg,melody:mel};
})();
// ── 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];
})();
// ── VIDEO GAMES (public domain pieces associated with games) ──
const VIDEOGAME_PIECES=(function(){
var C3=NT.C3,D3=NT.D3,E3=NT.E3,G3=NT.G3,Ab3=NT.Ab3,A3=NT.A3,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,F5=NT.F5,G5=NT.G5;
// ── Korobeiniki (Tetris Theme) ──
var tetris=(function(){
var bg=[],mel=[],t=0;
// The iconic Tetris melody in A minor
var theme=[E4,B3,C4,D4,C4,B3,A3,A3,C4,E4,D4,C4,B3,B3,C4,D4,E4,C4,A3,A3];
var tLn= [2,3,0,1,0,3,0,0,1,2,1,0,3,3,0,1,2,0,0,0];
var durations=[2,1,1,1,1,1,2,1,1,2,1,1,2,1,1,2,2,2,2,2];
var q=0.22;
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){
var d=durations[i]*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>1)bg.push({t:t,f:f/2,d:d*0.85,i:'bass',v:v*0.4});
if(rep>3)bg.push({t:t,f:f,d:d*0.85,i:'brass',v:v*0.4});
mel.push({t:t,l:tLn[i]});t+=d;
});
t+=0.4;
}
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'brass',v:0.55});bg.push({t:t,f:f,d:2.0,i:'strings',v:0.45});});
bg.push({t:t,f:A3/2,d:1.5,i:'timpani',v:0.6});mel.push({t:t,l:4});
return {n:"Korobeiniki (Tetris Theme)",composer:"Russian Folk",bpm:140,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Turkish March (Mozart) ──
var turkish=(function(){
var bg=[],mel=[],t=0,q=0.227;
var theme=[B3,A3,Ab3,A3,C4,D4,C4,B3,A3,B3,C4,D4,E4,D4,C4,B3,A3,B3,A3,A3];
var tLn= [3,0,1,0,2,1,2,3,0,3,0,1,2,1,0,3,0,3,0,0];
for(var rep=0;rep<6;rep++){
var v=0.22+rep*0.06;
var inst=rep<2?'woodwind':rep<4?'strings':'brass';
theme.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.85,i:inst,v:v});
if(rep>2)bg.push({t:t,f:f/2,d:q*0.85,i:'bass',v:v*0.4});
if(rep>4)bg.push({t:t,f:f,d:q*0.85,i:'brass',v:v*0.4});
mel.push({t:t,l:tLn[i]});t+=q;
});
t+=0.35;
}
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'brass',v:0.6});});
bg.push({t:t,f:A3/2,d:1.5,i:'timpani',v:0.6});mel.push({t:t,l:4});
return {n:"Turkish March",composer:"Mozart",bpm:132,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Hungarian Rhapsody No. 2 (Liszt) ──
var rhapsody=(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});}
// Slow dramatic opening then fast friska
var slow=[C4,D4,Eb4,D4,C4,D4,Eb4,F4,G4,F4,Eb4,D4];
var sLn= [0,1,2,1,0,1,2,3,0,3,2,1];
// Slow section
[0.45,0.42].forEach(function(qs,qi){
var v=0.35+qi*0.1;
slow.forEach(function(f,i){
orch(t,f,qs*0.85,v,'strings');
if(qi>0)orch(t,f,qs*0.85,v*0.5,'brass');
bg.push({t:t,f:f/2,d:qs*0.85,i:'bass',v:v*0.4});
mel.push({t:t,l:sLn[i]});t+=qs;
});
t+=0.5;
});
// Fast friska
var fast=[C4,E4,G4,C5,G4,E4,C4,D4,F4,A4,F4,D4,C4,E4,G4,C5,B4,A4,G4,E4];
var fLn= [0,2,3,0,3,2,0,1,2,1,2,1,0,2,3,0,3,1,3,2];
[0.16,0.14,0.12,0.10].forEach(function(qf,qi){
var v=0.4+qi*0.06;
fast.forEach(function(f,i){
orch(t,f,qf*0.85,v);
if(qi>0)orch(t,f,qf*0.85,v*0.5,'brass');
orch(t,f/2,qf*0.85,v*0.4,'bass');
if(qi>1&&i%4===0)bg.push({t:t,f:C3,d:0.2,i:'timpani',v:v*0.6});
mel.push({t:t,l:fLn[i]});t+=qf;
});
t+=0.3;
});
for(var c=0;c<4;c++){
[C4,E4,G4,C5].forEach(function(f){orch(t,f,0.35,0.65,'brass');});
bg.push({t:t,f:C3,d:0.35,i:'timpani',v:0.65});mel.push({t:t,l:4});t+=0.45;
}
[C4,E4,G4,C5].forEach(function(f){orch(t,f,2.0,0.6,'brass');orch(t,f,2.0,0.5);});
bg.push({t:t,f:C3,d:1.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Hungarian Rhapsody No. 2",composer:"Liszt",bpm:140,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Can-Can (Offenbach) ──
var cancan=(function(){
var bg=[],mel=[],t=0,q=0.197;
var theme=[C4,D4,E4,C4,E4,C4,E4,D4,E4,F4,E4,D4,C4,D4,E4,F4,G4,A4,G4,E4,C4,D4,E4,C4];
var tLn= [0,1,2,0,2,0,2,1,2,3,2,1,0,1,2,3,0,1,0,2,0,1,2,0];
for(var rep=0;rep<6;rep++){
var v=0.25+rep*0.06;
var inst=rep<2?'strings':rep<4?'strings':'brass';
theme.forEach(function(f,i){
bg.push({t:t,f:f,d:q*0.85,i:inst,v:v});
if(rep>2)bg.push({t:t,f:f/2,d:q*0.85,i:'bass',v:v*0.4});
if(rep>3)bg.push({t:t,f:f,d:q*0.85,i:'brass',v:v*0.4});
if(rep>4&&i%4===0)bg.push({t:t,f:C3,d:0.2,i:'timpani',v:v*0.6});
mel.push({t:t,l:tLn[i]});t+=q;
});
t+=0.35;
}
[C4,E4,G4,C5].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'brass',v:0.6});});
bg.push({t:t,f:C3,d:1.5,i:'timpani',v:0.65});mel.push({t:t,l:4});
return {n:"Can-Can",composer:"Offenbach",bpm:152,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
// ── Kalinka (Russian Folk) ──
var kalinka=(function(){
var bg=[],mel=[],t=0;
// Kalinka starts slow then gets very fast
var theme=[E4,E4,D4,C4,B3,A3,B3,C4,D4,E4,F4,E4,D4,C4,B3,A3];
var tLn= [2,2,0,1,3,0,3,1,0,2,1,2,0,1,3,0];
var speeds=[0.35,0.30,0.25,0.20,0.16,0.13,0.10];
var vols= [0.2, 0.26,0.32,0.38,0.45,0.52,0.6];
var insts= ['woodwind','woodwind','strings','strings','brass','brass','brass'];
speeds.forEach(function(spd,si){
theme.forEach(function(f,i){
bg.push({t:t,f:f,d:spd*0.85,i:insts[si],v:vols[si]});
if(si>2)bg.push({t:t,f:f/2,d:spd*0.85,i:'bass',v:vols[si]*0.4});
if(si>4&&i%3===0)bg.push({t:t,f:A3/2,d:0.2,i:'timpani',v:vols[si]*0.6});
mel.push({t:t,l:tLn[i]});t+=spd;
});
t+=0.35;
});
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.0,i:'brass',v:0.6});});
bg.push({t:t,f:A3/2,d:1.5,i:'timpani',v:0.7});mel.push({t:t,l:4});
return {n:"Kalinka",composer:"Russian Folk",bpm:140,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
})();
return [tetris,turkish,rhapsody,cancan,kalinka];
})();
// ── CHIPTUNE ORIGINALS (Claude.ai compositions using square waves) ──
const CHIPTUNE_PIECES=(function(){
var 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,B4=NT.B4,
C5=NT.C5,D5=NT.D5,E5=NT.E5,G3=NT.G3,A3=NT.A3,B3=NT.B3,C3=NT.C3,D3=NT.D3,E3=NT.E3,F3=NT.F3,
Gb4=NT.Gb4,F5=NT.F5,G5=NT.G5;
// Chiptune helper: square wave + simple pulse bass
function chip(name,bpm,theme,lanes,passes,root){
var bg=[],mel=[],t=0;
passes.forEach(function(p,pi){
theme.forEach(function(f,ni){
// Square wave lead (chiptune character)
bg.push({t:t,f:f,d:p.d*0.8,i:'brass',v:p.v}); // square-ish
// Pulse bass on beat
if(ni%4===0)bg.push({t:t,f:root||theme[0],d:p.d*3,i:'bass',v:p.v*0.35});
// Add arpeggiated accompaniment in later passes
if(pi>=Math.floor(passes.length*0.4)){
bg.push({t:t,f:f*1.5,d:p.d*0.4,i:'woodwind',v:p.v*0.25});
}
if(pi>=Math.floor(passes.length*0.7)&&ni%2===0){
bg.push({t:t,f:root||theme[0],d:p.d*0.5,i:'timpani',v:p.v*0.4});
}
mel.push({t:t,l:lanes[ni%lanes.length]});t+=p.d;
});
t+=0.3;
});
var r=root||theme[0];
for(var c=0;c<3;c++){bg.push({t:t,f:r,d:0.3,i:'brass',v:0.6});bg.push({t:t,f:r*2,d:0.3,i:'brass',v:0.5});bg.push({t:t,f:r/2,d:0.3,i:'timpani',v:0.55});mel.push({t:t,l:4});t+=0.4;}
bg.push({t:t,f:r,d:1.5,i:'brass',v:0.55});bg.push({t:t,f:r*2,d:1.5,i:'brass',v:0.45});mel.push({t:t,l:4});
return {n:name,composer:"Claude.ai",bpm:bpm,duration:Math.ceil(t+2),isClassical:true,bg:bg,melody:mel};
}
// ── Pixel Quest (Platformer Theme) ──
var platformer=chip("Pixel Quest",150,
[C4,E4,G4,C5,G4,E4,C4,D4,F4,A4,F4,D4,E4,G4,B4,G4,C4,E4,G4,E4,C4,G4,E4,C4],
[0,2,3,0,3,2,0,1,2,1,2,1,2,3,0,3,0,2,3,2,0,3,2,0],
[{d:0.20,v:0.22,i:'brass'},{d:0.18,v:0.28,i:'brass'},{d:0.16,v:0.35,i:'brass'},{d:0.15,v:0.42,i:'brass'},{d:0.14,v:0.48,i:'brass'},{d:0.13,v:0.55,i:'brass'}],
C4);
// ── Starfield (Space Shooter Theme) ──
var spaceshooter=chip("Starfield",160,
[E4,E4,G4,A4,G4,E4,D4,E4,G4,A4,B4,A4,G4,E4,D4,C4,E4,G4,B4,D5,B4,G4,E4,D4],
[2,2,3,0,3,2,1,2,3,0,1,0,3,2,1,0,2,3,1,0,1,3,2,1],
[{d:0.18,v:0.22,i:'brass'},{d:0.16,v:0.3,i:'brass'},{d:0.15,v:0.38,i:'brass'},{d:0.14,v:0.44,i:'brass'},{d:0.13,v:0.5,i:'brass'},{d:0.12,v:0.56,i:'brass'}],
E4);
// ── Dragon's Keep (RPG Overworld) ──
var rpg=chip("Dragon's Keep",110,
[A3,C4,E4,A4,G4,F4,E4,D4,C4,E4,G4,C5,B4,A4,G4,E4,F4,A4,C5,A4,F4,D4,E4,A3],
[0,1,2,0,3,1,2,0,1,2,3,0,3,0,3,2,1,0,2,0,1,0,2,0],
[{d:0.30,v:0.18,i:'brass'},{d:0.28,v:0.24,i:'brass'},{d:0.26,v:0.32,i:'brass'},{d:0.24,v:0.4,i:'brass'},{d:0.22,v:0.48,i:'brass'},{d:0.20,v:0.55,i:'brass'}],
A3);
// ── Turbo Dash (Racing Theme) ──
var racing=chip("Turbo Dash",170,
[G4,B4,D5,G5,D5,B4,G4,A4,C5,E5,C5,A4,G4,B4,D5,B4,G4,D4,G4,B4,D5,G4,B4,D5],
[0,3,1,0,1,3,0,1,2,0,2,1,0,3,1,3,0,1,0,3,1,0,3,1],
[{d:0.16,v:0.25,i:'brass'},{d:0.14,v:0.32,i:'brass'},{d:0.13,v:0.4,i:'brass'},{d:0.12,v:0.46,i:'brass'},{d:0.11,v:0.52,i:'brass'},{d:0.10,v:0.58,i:'brass'}],
G4);
// ── Dungeon Crawl (Dark Adventure) ──
var dungeon=chip("Dungeon Crawl",100,
[D4,F4,A4,D4,Eb4,G4,Bb4,Eb4,D4,F4,Ab4,D4,C4,Eb4,G4,C4,D4,F4,A4,F4,D4,A3,D4,F4],
[0,2,0,0,1,3,2,1,0,2,0,0,1,2,3,1,0,2,0,2,0,4,0,2],
[{d:0.32,v:0.2,i:'brass'},{d:0.30,v:0.28,i:'brass'},{d:0.28,v:0.35,i:'brass'},{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);
// ── Victory Fanfare (Boss Defeated) ──
var victory=chip("Victory Fanfare",130,
[C4,C4,C4,E4,G4,E4,G4,A4,G4,E4,C4,E4,G4,C5,G4,E4,C5,D5,E5,C5,G4,E4,C4,C5],
[0,0,0,2,3,2,3,0,3,2,0,2,3,0,3,2,0,1,2,0,3,2,0,0],
[{d:0.22,v:0.25,i:'brass'},{d:0.20,v:0.32,i:'brass'},{d:0.18,v:0.4,i:'brass'},{d:0.16,v:0.48,i:'brass'},{d:0.14,v:0.55,i:'brass'},{d:0.12,v:0.62,i:'brass'}],
C4);
return [platformer,spaceshooter,rpg,racing,dungeon,victory];
})();
// ── FOLK (public domain traditional songs) ──
const FOLK_PIECES=(function(){
var C3=NT.C3,D3=NT.D3,E3=NT.E3,F3=NT.F3,G3=NT.G3,Ab3=NT.Ab3,A3=NT.A3,B3=NT.B3,
C4=NT.C4,D4=NT.D4,Eb4=NT.Eb4,E4=NT.E4,F4=NT.F4,Gb4=NT.Gb4,G4=NT.G4,A4=NT.A4,Bb4=NT.Bb4,B4=NT.B4,
C5=NT.C5,D5=NT.D5,E5=NT.E5,F5=NT.F5,G5=NT.G5;
// Folk piece helper
function folk(name,origin,bpm,theme,lanes,durations,passes,root){
var bg=[],mel=[],t=0,q=30/bpm;
for(var rep=0;rep<passes;rep++){
var v=0.2+rep*0.08;
var inst=rep<Math.floor(passes*0.3)?'woodwind':rep<Math.floor(passes*0.7)?'strings':'brass';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>1)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:'strings',v:v*0.4});
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:'strings',v:0.5});bg.push({t:t,f:f,d:2.0,i:'brass',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};
}
// ── Oh Susanna ──
var ohsusanna=folk("Oh! Susanna","Stephen Foster (1848)",120,
[C4,D4,E4,G4,G4,A4,G4,E4,C4,D4,E4,E4,D4,C4,D4,C4,D4,E4,G4,G4,A4,G4,E4,C4,D4,E4,E4,D4,D4,C4],
[0,1,2,3,3,0,3,2,0,1,2,2,1,0,1,0,1,2,3,3,0,3,2,0,1,2,2,1,1,0],
[2,2,4,4,2,2,4,4,2,2,4,2,2,4,4,2,2,4,4,2,2,4,4,2,2,4,2,2,2,6],
5,C4);
// ── Greensleeves ──
var greensleeves=folk("Greensleeves","English Traditional (1580)",100,
[A3,C4,D4,E4,F4,E4,D4,B3,G3,A3,B3,C4,A3,A3,Ab3,A3,B3,Ab3,A3,C4,D4,E4,F4,E4,D4,B3,G3,A3,B3,C4,B3,A3],
[0,1,0,2,1,2,0,3,3,0,3,1,0,0,1,0,3,3,0,1,0,2,1,2,0,3,3,0,3,1,3,0],
[2,3,1,2,3,1,2,3,1,2,1,2,6,2,1,2,3,6,2,3,1,2,3,1,2,3,1,2,1,2,2,6],
4,A3);
// ── Camptown Races ──
var camptown=folk("Camptown Races","Stephen Foster (1850)",140,
[G4,G4,E4,G4,A4,G4,E4,C4,D4,D4,E4,E4,F4,E4,D4,C4,C4,E4,G4,G4,G4,E4,G4,A4,G4,E4,C4,G4,A4,B4,A4,G4,E4,D4,D4,C4],
[3,3,2,3,0,3,2,0,1,1,2,2,1,2,1,0,0,2,3,3,3,2,3,0,3,2,0,3,0,3,0,3,2,1,1,0],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,8],
3,C4);
// ── Red River Valley ──
// Slow American ballad in 4/4. "From this val-ley they say you are go-ing"
// Uses dotted-quarter + eighth for the flowing melodic phrases.
var redriver=folk("Red River Valley","American Traditional (1870s)",85,
[C4,D4,E4,G4,G4,E4,D4,C4,D4,E4,F4,E4,D4,C4,E4,G4,A4,G4,E4,D4,C4,D4,E4,C4],
[0,1,2,3,3,2,1,0,1,2,1,2,1,0,2,3,0,3,2,1,0,1,2,0],
[2,3,1,4,1,3,2,4, 2,3,1,3,1,4,2,4, 2,3,1,3,1,3,1,8],
4,C4);
// ── Home on the Range ──
var homerange=folk("Home on the Range","American Traditional (1870s)",95,
[C4,E4,G4,A4,G4,E4,C4,D4,F4,A4,G4,F4,E4,D4,C4,E4,G4,C5,A4,G4,E4,D4,C4,C4],
[0,2,3,0,3,2,0,1,1,0,3,1,2,1,0,2,3,0,0,3,2,1,0,0],
[2,2,4,2,2,2,4,2,2,4,2,2,2,4,2,2,4,2,2,2,4,2,4,6],
4,C4);
// ── Yankee Doodle ──
var yankee=folk("Yankee Doodle","American Traditional (1770s)",130,
[C4,C4,D4,E4,C4,E4,D4,G3,C4,C4,D4,E4,C4,B3,C4,C4,D4,E4,F4,E4,D4,C4,B3,G3,A3,B3,C4,C4],
[0,0,1,2,0,2,1,3,0,0,1,2,0,3,0,0,1,2,1,2,1,0,3,3,0,3,0,0],
[2,2,2,2,2,2,4,4,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,4,6],
5,C4);
// ── Turkey in the Straw ──
var turkey=folk("Turkey in the Straw","American Traditional (1820s)",145,
[G4,A4,B4,G4,E4,G4,A4,B4,C5,B4,A4,G4,E4,D4,E4,G4,A4,B4,C5,D5,C5,B4,A4,G4],
[0,1,3,0,2,0,1,3,0,3,1,0,2,1,2,0,1,3,0,1,0,3,1,0],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,8],
5,G4);
// ── Danny Boy ──
// Londonderry Air: flowing Irish ballad, famous "long-short" dotted feel.
// Phrase A: "Oh Dan-ny Boy, the pipes, the pipes are call-ing" (notes 0-10)
// Phrase B: "From glen to glen and down the moun-tain side" (notes 11-24)
// Phrase C: High passage, peaks at E5 (notes 25-37)
// Phrase D: Final resolution back to tonic C (notes 38-48)
// Duration units: 1=eighth, 2=quarter, 3=dotted-quarter, 4=half, 6=dotted-half, 8=whole
var dannyboy=folk("Danny Boy","Irish Traditional (1910s)",72,
[C4,E4,F4,G4,A4,G4,A4,C5,B4,A4,G4,F4,G4,C4,E4,F4,G4,A4,G4,A4,C5,D5,C5,A4,G4,A4,A4,C5,D5,E5,D5,C5,A4,C5,B4,A4,G4,A4,G4,E4,C4,E4,F4,G4,A4,G4,F4,E4,C4],
[0,2,1,3,0,3,0,0,3,0,3,1,3,0,2,1,3,0,3,0,0,1,0,0,3,0,0,0,1,2,1,0,0,0,3,0,3,0,3,2,0,2,1,3,0,3,1,2,0],
[2,3,1,4,1,3,1,3,1,3,6, 2,3,1,3,1,4,1,3,1,3,1,3,1,6, 2,1,3,1,4,1,3,1,3,1,3,1,6, 2,3,1,3,1,4,1,3,1,3,8],
3,C4);
return [ohsusanna,greensleeves,camptown,redriver,homerange,yankee,turkey,dannyboy];
})();
// Synth genre lookup — all genres that use bg+melody synth mode
// ── CHRISTMAS (public domain carols) ──
const CHRISTMAS_PIECES=(function(){
var C3=NT.C3,D3=NT.D3,E3=NT.E3,F3=NT.F3,G3=NT.G3,A3=NT.A3,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,Eb5=NT.Eb5,E5=NT.E5,F5=NT.F5,G5=NT.G5;
function carol(name,origin,bpm,theme,lanes,durations,passes,root){
var bg=[],mel=[],t=0,q=30/bpm;
for(var rep=0;rep<passes;rep++){
var v=0.2+rep*0.08;
var inst=rep<Math.floor(passes*0.3)?'woodwind':rep<Math.floor(passes*0.7)?'strings':'brass';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>1)bg.push({t:t,f:f/2,d:d*0.85,i:'bass',v:v*0.35});
if(rep>Math.floor(passes*0.5))bg.push({t:t,f:f,d:d*0.85,i:'choir',v:v*0.3});
if(rep>Math.floor(passes*0.7))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.5,i:'choir',v:0.45});bg.push({t:t,f:f,d:2.5,i:'strings',v:0.4});});
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 jingle=carol("Jingle Bells","J. Pierpont (1857)",140,
[E4,E4,E4,E4,E4,E4,E4,G4,C4,D4,E4,F4,F4,F4,F4,F4,E4,E4,E4,E4,D4,D4,E4,D4,G4,E4,E4,E4,E4,E4,E4,E4,G4,C4,D4,E4,F4,F4,F4,F4,F4,E4,E4,G4,G4,F4,D4,C4],
[2,2,2,2,2,2,2,3,0,1,2,1,1,1,1,1,2,2,2,2,1,1,2,1,3,2,2,2,2,2,2,2,3,0,1,2,1,1,1,1,1,2,2,3,3,1,1,0],
[1,1,2,1,1,2,1,1,1,1,6,2,2,2,1,1,2,2,1,1,2,2,2,2,6,1,1,2,1,1,2,1,1,1,1,6,2,1,1,1,2,1,1,2,2,2,2,6],
3,C4);
var silent=carol("Silent Night","F. Gruber (1818)",72,
[G4,A4,G4,E4,G4,A4,G4,E4,D5,D5,B4,C5,C5,G4,A4,A4,C5,B4,A4,G4,A4,G4,E4,A4,A4,C5,B4,A4,G4,A4,G4,E4,D5,D5,F5,D5,B4,C5,E5,C5,G4,E4,G4,F4,D4,C4],
[3,0,3,2,3,0,3,2,0,0,3,0,0,3,0,0,0,3,0,3,0,3,2,0,0,0,3,0,3,0,3,2,0,0,1,0,3,0,2,0,3,2,3,1,1,0],
[3,1,2,6,3,1,2,6,2,2,6,2,2,6,2,2,3,1,2,3,1,2,6,2,2,3,1,2,3,1,2,6,2,2,3,1,6,2,2,2,2,2,3,1,2,6],
3,C4);
var deck=carol("Deck the Halls","Welsh (1862)",130,
[C5,Bb4,A4,G4,F4,G4,A4,F4,G4,A4,Bb4,G4,A4,G4,F4,E4,D4,E4,F4,G4,A4,G4,F4,E4,F4,G4,A4,Bb4,C5,A4,G4,F4],
[0,3,0,3,1,3,0,1,3,0,2,3,0,3,1,2,1,2,1,3,0,3,1,2,1,3,0,2,0,0,3,1],
[3,1,2,2,3,1,2,2,2,2,2,2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,1,2,4],
4,F4);
var wish=carol("We Wish You a Merry Christmas","English (1500s)",140,
[G4,C5,C5,D5,C5,B4,A4,A4,D5,D5,E5,D5,C5,B4,G4,E5,E5,F5,E5,D5,C5,A4,G4,G4,A4,D5,B4,C5],
[3,0,0,1,0,3,0,0,1,1,2,1,0,3,3,2,2,1,2,1,0,0,3,3,0,1,3,0],
[2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,10],
5,C4);
var ocometree=carol("O Christmas Tree","German (1824)",100,
[F4,Bb4,Bb4,Bb4,C5,D5,D5,D5,D5,Eb5,C5,A4,Bb4,F4,Bb4,Bb4,Bb4,C5,D5,D5,D5,D5,Eb5,C5,A4,Bb4],
[1,2,2,2,0,1,1,1,1,2,0,0,2,1,2,2,2,0,1,1,1,1,2,0,0,2],
[2,4,2,2,2,4,2,2,2,2,2,2,6,2,4,2,2,2,4,2,2,2,2,2,2,6],
4,Bb4);
var firstnoel=carol("The First Noel","English (1823)",95,
[E4,D4,C4,D4,E4,F4,G4,A4,B4,C5,B4,A4,G4,A4,B4,C5,B4,A4,G4,A4,B4,C5,G4,F4,E4,D4,C4,D4,C4],
[2,1,0,1,2,1,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3,0,3,1,2,1,0,1,0],
[2,2,4,2,2,2,4,2,2,4,2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,4,2,6],
4,C4);
var joy=carol("Joy to the World","G.F. Handel (1719)",120,
[C5,B4,A4,G4,F4,E4,D4,C4,G4,A4,A4,B4,B4,C5,C5,C5,B4,A4,G4,G4,F4,E4,C5,C5,B4,A4,G4,G4,F4,E4,D4,C4],
[0,3,0,3,1,2,1,0,3,0,0,3,3,0,0,0,3,0,3,3,1,2,0,0,3,0,3,3,1,2,1,0],
[3,1,2,6,2,2,2,8,4,2,4,2,4,2,2,1,1,2,4,2,2,6,2,1,1,2,4,2,2,2,2,8],
4,C4);
var away=carol("Away in a Manger","J.Murray (1885)",80,
[C4,F4,F4,F4,G4,A4,F4,F4,A4,G4,F4,G4,F4,D4,C4,C4,F4,F4,F4,G4,A4,F4,F4,A4,G4,F4,G4,F4,D4,C4,Bb4,A4,F4],
[0,1,1,1,3,0,1,1,0,3,1,3,1,1,0,0,1,1,1,3,0,1,1,0,3,1,3,1,1,0,2,0,1],
// 3/4 waltz. "A-way in a man-ger no crib for a bed":
// beat 1 of each bar gets a longer note, phrase ends get dotted-half
[2,2,2,2,2,4, 2,2,2,2,6, 2,2,2,4, 2,2,2,2,2,4, 2,2,2,2,6, 2,2,2,4, 2,4,8],
4,F4);
var twelve=carol("12 Days of Christmas","English (1780)",110,
[C4,F4,F4,F4,F4,E4,F4,G4,A4,Bb4,A4,G4,F4,G4,A4,Bb4,A4,G4,F4,G4,A4,Bb4,C5,A4,Bb4,G4,A4,F4],
[0,1,1,1,1,2,1,3,0,2,0,3,1,3,0,2,0,3,1,3,0,2,0,0,2,3,0,1],
[2,2,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,8],
5,F4);
var hark=carol("Hark the Herald Angels Sing","Mendelssohn (1840)",115,
[C4,F4,F4,E4,F4,A4,A4,G4,C5,A4,A4,G4,C5,A4,A4,G4,A4,Bb4,G4,A4,Bb4,C5,A4,F4,G4,Bb4,A4,G4,F4],
[0,1,1,2,1,0,0,3,0,0,0,3,0,0,0,3,0,2,3,0,2,0,0,1,3,2,0,3,1],
[2,4,2,2,4,2,2,4,2,2,2,4,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,8],
4,F4);
// ── O Come All Ye Faithful (G major → transposed to C) ──
var ocome=carol("O Come All Ye Faithful","J.F. Wade (1743)",100,
[C4,C4,G4,C4,D4,G4,E4,D4,E4,F4,E4,D4,C4,B4,A4,B4,C5,B4,A4,G4,A4,E4,F4,E4,D4,E4,F4,D4,C4,D4,C4,Bb4,A4,G4,F4,E4,F4,E4,D4,C4],
[0,0,3,0,1,3,2,1,2,1,2,1,0,3,0,3,0,3,0,3,0,2,1,2,1,2,1,1,0,1,0,2,0,3,1,2,1,2,1,0],
[4,2,4,2,4,6,2,2,2,2,2,2,4,2,2,2,4,2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,2,6],
3,C4);
// ── God Rest Ye Merry Gentlemen (E minor / A minor) ──
var godrest=carol("God Rest Ye Merry Gentlemen","English (1500s)",120,
[E4,E4,E4,D4,C4,D4,E4,Gb4,G4,A4,B4,A4,G4,Gb4,E4,D4,C4,D4,E4,Gb4,E4,D4,C4,B3,C4,G4,G4,Gb4,E4,Gb4,G4,A4,B4,A4,G4,Gb4,E4],
[2,2,2,1,0,1,2,1,3,0,2,0,3,1,2,1,0,1,2,1,2,1,0,3,0,3,3,1,2,1,3,0,2,0,3,1,2],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,6,2,2,2,2,2,2,2,2,2,2,2,8],
4,E4);
// ── What Child Is This (Greensleeves melody as a carol) ──
var whatchild=carol("What Child Is This","English (1865)",95,
[E4,A4,B4,C5,B4,A4,G4,E4,D4,E4,F4,E4,D4,C4,E4,A4,B4,C5,B4,A4,Ab4,B4,C5,A4,A4],
[2,0,3,0,3,0,3,2,1,2,1,2,1,0,2,0,3,0,3,0,1,3,0,0,0],
[2,3,1,2,3,1,2,3,1,3,1,2,6,2,3,1,2,3,1,2,3,1,6,2,6],
4,A4);
// ── Angels We Have Heard on High ──
var angels=carol("Angels We Have Heard on High","French (1862)",110,
[F4,F4,F4,F4,A4,G4,F4,F4,G4,A4,F4,D4,C4,D4,F4,G4,A4,G4,F4,E4,F4,G4,A4,G4,F4,E4,F4,G4,A4,G4,F4,G4,F4],
[1,1,1,1,0,3,1,1,3,0,1,1,0,1,1,3,0,3,1,2,1,3,0,3,1,2,1,3,0,3,1,3,1],
[2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,2,2,2,2,4,8],
4,F4);
// ── O Holy Night (in C major) ──
var oholynight=carol("O Holy Night","A. Adam (1847)",72,
[C4,F4,F4,F4,E4,F4,A4,A4,G4,F4,F4,Bb4,A4,G4,F4,G4,A4,G4,F4,E4,F4,C5,C5,Bb4,A4,G4,A4,Bb4,A4,G4,F4],
[0,1,1,1,2,1,0,0,3,1,1,2,0,3,1,3,0,3,1,2,1,0,0,2,0,3,0,2,0,3,1],
// 12/8 compound feel: "O ho-ly night! The stars are bright-ly shi-ning"
// Characteristic dotted-quarter+eighth pairs and long phrase-end notes
[2,3,1,4,1,3,1,3,1,3,1, 2,3,1,4,1,3,1,3,1,4, 2,3,1,3,1,2,2,3,1,4],
3,F4);
// ── Rudolph the Red-Nosed Reindeer (in C major) ──
var rudolph=carol("Rudolph the Red-Nosed Reindeer","J. Marks (1949)",130,
[G4,A4,G4,E4,C5,A4,G4,G4,A4,G4,A4,G4,C5,B4,F4,A4,G4,F4,D4,E4,F4,D4,G4,A4,G4,E4,C5,A4,G4,G4,A4,G4,A4,G4,A4,D5,C5],
[3,0,3,2,0,0,3,3,0,3,0,3,0,3,1,0,3,1,1,2,1,1,3,0,3,2,0,0,3,3,0,3,0,3,0,1,0],
[2,2,2,2,4,2,4,2,2,2,2,2,4,6,2,2,2,2,2,2,2,6,2,2,2,2,4,2,4,2,2,2,2,2,2,2,8],
3,C4);
// ── Frosty the Snowman (in C major) ──
var frosty=carol("Frosty the Snowman","S. Nelson (1950)",125,
[G4,E4,F4,G4,C5,B4,A4,G4,A4,G4,F4,E4,F4,A4,G4,F4,E4,D4,C4,C4,G4,E4,F4,G4,C5,B4,A4,G4,A4,G4,F4,E4,D4,C4],
[3,2,1,3,0,3,0,3,0,3,1,2,1,0,3,1,2,1,0,0,3,2,1,3,0,3,0,3,0,3,1,2,1,0],
[2,2,2,4,2,2,2,2,2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,2,2,2,8],
3,C4);
// ── Winter Wonderland (in C major) ──
var winter=carol("Winter Wonderland","F. Bernard (1934)",120,
[E4,F4,G4,G4,A4,G4,F4,E4,D4,E4,F4,F4,G4,F4,E4,D4,C4,E4,F4,G4,G4,A4,G4,F4,E4,D4,E4,F4,F4,G4,F4,E4,D4,C4],
[2,1,3,3,0,3,1,2,1,2,1,1,3,1,2,1,0,2,1,3,3,0,3,1,2,1,2,1,1,3,1,2,1,0],
[2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,2,6,2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,2,8],
3,C4);
// ── Good King Wenceslas (in F major) ──
var wenceslas=carol("Good King Wenceslas","Finnish (1582)",110,
[F4,F4,F4,G4,F4,F4,C4,D4,E4,F4,F4,G4,F4,F4,C4,C5,Bb4,A4,G4,A4,Bb4,A4,G4,F4,G4,A4,Bb4,C5,Bb4,A4,G4,F4],
[1,1,1,3,1,1,0,1,2,1,1,3,1,1,0,0,2,0,3,0,2,0,3,1,3,0,2,0,2,0,3,1],
[2,2,2,2,2,2,4,2,2,2,2,2,2,2,6,2,2,2,2,2,2,2,2,4,2,2,2,4,2,2,2,8],
4,F4);
// ── We Three Kings (E minor) ──
var threekings=carol("We Three Kings","J.H. Hopkins Jr. (1857)",105,
[B4,A4,G4,A4,B4,B4,B4,A4,G4,A4,B4,A4,G4,F4,E4,E4,B4,B4,C5,D5,D5,C5,B4,A4,B4,C5,B4,A4,G4,A4,G4,E4],
[3,0,3,0,3,3,3,0,3,0,3,0,3,1,2,2,3,3,0,1,1,0,3,0,3,0,3,0,3,0,3,2],
[4,2,4,2,2,2,4,2,4,2,2,2,2,2,4,6,4,2,2,4,2,2,4,2,2,2,2,2,2,2,2,8],
4,E4);
return [jingle,silent,deck,wish,ocometree,firstnoel,joy,away,twelve,hark,ocome,godrest,whatchild,angels,oholynight,rudolph,frosty,winter,wenceslas,threekings];
})();
// ── SINGALONG (campfire, sea shanties, pub songs) ──
const SINGALONG_PIECES=(function(){
var C3=NT.C3,D3=NT.D3,E3=NT.E3,F3=NT.F3,G3=NT.G3,Ab3=NT.Ab3,A3=NT.A3,B3=NT.B3,
C4=NT.C4,D4=NT.D4,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,F5=NT.F5,G5=NT.G5;
function pub(name,origin,bpm,theme,lanes,durations,passes,root,opts){
var bg=[],mel=[],t=0,q=30/bpm;
var timeSig=(opts&&opts.timeSig)||[4,4];
for(var rep=0;rep<passes;rep++){
var v=0.22+rep*0.07;
var inst=rep<Math.floor(passes*0.4)?'woodwind':rep<Math.floor(passes*0.7)?'strings':'brass';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>1)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.4});
mel.push({t:t,l:lanes[i],d:d,f:f});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,d:1.5});
return {n:name,composer:origin,bpm:bpm,duration:Math.ceil(t+2.5),isClassical:true,isSingalong:true,timeSig:timeSig,bg:bg,melody:mel};
}
var drunken=(function(){
// Accurate "What Shall We Do With A Drunken Sailor" from sheet music
// D minor, 4/4, ~140 BPM
var bg=[],mel=[],t=0,q=60/140; // quarter note duration at 140 bpm
var e=q/2; // eighth note
function note(f,dur,lane,inst,v){
bg.push({t:t,f:f,d:dur*0.85,i:inst||'strings',v:v||0.35});
mel.push({t:t,l:lane,d:dur,f:f});
t+=dur;
}
function chord(fs,dur,inst,v){
fs.forEach(function(f){bg.push({t:t,f:f,d:dur*0.85,i:inst||'strings',v:v||0.25});});
}
function rest(dur){t+=dur;}
for(var pass=0;pass<3;pass++){
var v=0.25+pass*0.1;
var inst=pass===0?'woodwind':pass===1?'strings':'brass';
// Add bass + chord backing
if(pass>0){
var ct=t;
// Am chord backing per measure (8 measures per verse+chorus)
var chords=[[A3,C4,E4],[A3,C4,E4],[G3,B3,D4],[G3,B3,D4],
[A3,C4,E4],[A3,C4,E4],[C4,E4,G4],[D4,F4,A4]];
chords.forEach(function(ch,ci){
ch.forEach(function(f){bg.push({t:ct+ci*q*4,f:f,d:q*3.5,i:'strings',v:v*0.3});});
bg.push({t:ct+ci*q*4,f:ch[0]/2,d:q*3.5,i:'bass',v:v*0.35});
});
}
// M1-2: "What shall we do with a drunken sailor?" (Am)
// Rhythm: q ee q ee | q q q q
note(A4,q,0,inst,v); note(A4,e,0,inst,v); note(A4,e,0,inst,v);
note(A4,q,0,inst,v); note(A4,e,0,inst,v); note(A4,e,0,inst,v);
note(D4,q,1,inst,v); note(F4,q,1,inst,v); note(A4,q,0,inst,v); note(A4,q,0,inst,v);
// M3-4: "What shall we do with a drunken sailor?" (G)
// Rhythm: q ee q ee | q q q q
note(G4,q,3,inst,v); note(G4,e,3,inst,v); note(G4,e,3,inst,v);
note(G4,q,3,inst,v); note(G4,e,3,inst,v); note(G4,e,3,inst,v);
note(C4,q,2,inst,v); note(E4,q,2,inst,v); note(G4,q,3,inst,v); note(G4,q,3,inst,v);
// M5-6: "What shall we do with a drunken sailor" (Am)
// Rhythm: q ee q ee | q q q q
note(A4,q,0,inst,v); note(A4,e,0,inst,v); note(A4,e,0,inst,v);
note(A4,q,0,inst,v); note(A4,e,0,inst,v); note(A4,e,0,inst,v);
note(B4,q,3,inst,v); note(C5,q,1,inst,v); note(D5,q,1,inst,v); note(D5,q,1,inst,v);
// M7-8: "early in the morning?" (C-A-G-E | D-D)
note(C5,q,0,inst,v); note(A4,q,0,inst,v);
note(G4,q,3,inst,v); note(E4,q,2,inst,v);
note(D4,q*2,1,inst,v); note(D4,q*2,1,inst,v);
// M9-10: "Way hey and up she ri-ses" (chorus)
note(A4,q*2,0,inst,v*1.2);
note(A4,q+e,0,inst,v*1.2); note(A4,e,0,inst,v*1.2);
note(A4,q,0,inst,v*1.2); note(D4,q,1,inst,v*1.2);
note(F4,q,1,inst,v*1.2); note(A4,q,0,inst,v*1.2);
// M11-12: "Way hey and up she ri-ses" (on G)
note(G4,q*2,3,inst,v*1.2);
note(G4,q+e,3,inst,v*1.2); note(G4,e,3,inst,v*1.2);
note(G4,q,3,inst,v*1.2); note(C4,q,2,inst,v*1.2);
note(E4,q,2,inst,v*1.2); note(G4,q,3,inst,v*1.2);
// M13-14: "Way hey and up she ri-ses"
note(A4,q*2,0,inst,v*1.2);
note(A4,q+e,0,inst,v*1.2); note(A4,e,0,inst,v*1.2);
note(A4,q,0,inst,v*1.2); note(B4,q,3,inst,v*1.2);
note(C5,q,1,inst,v*1.2); note(D5,q,1,inst,v*1.2);
// M15-16: "Ear-ly in the morning" (ending)
note(C5,q,0,inst,v*1.2); note(A4,q,0,inst,v*1.2);
note(G4,q,3,inst,v*1.2); note(E4,q,2,inst,v*1.2);
note(D4,q*2,1,inst,v*1.2); note(D4,q*2,1,inst,v*1.2);
t+=q; // small gap between passes
}
// Finale chord
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'brass',v:0.55});bg.push({t:t,f:f,d:2.5,i:'strings',v:0.4});});
bg.push({t:t,f:A3/2,d:2.0,i:'timpani',v:0.6});mel.push({t:t,l:4});
return {n:"Drunken Sailor",composer:"Sea Shanty (Trad.)",bpm:140,duration:Math.ceil(t+3),isClassical:true,isSingalong:true,timeSig:[4,4],bg:bg,melody:mel};
})();
var molly=pub("Molly Malone","Irish (1880s)",100,
[C4,E4,E4,E4,F4,G4,G4,G4,A4,G4,F4,E4,D4,E4,F4,G4,A4,C5,C5,B4,A4,G4,F4,E4,D4,C4,E4,G4,C5,C5,B4,A4,G4,F4,E4,D4,C4],
[0,2,2,2,1,3,3,3,0,3,1,2,1,2,1,3,0,0,0,3,0,3,1,2,1,0,2,3,0,0,3,0,3,1,2,1,0],
[2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,4,2,4,2,2,2,2,2,2,2,4,2,2,4,2,2,2,2,2,2,2,6],
4,C4);
var whiskey=pub("Whiskey in the Jar","Irish (Trad.)",125,
[G4,G4,A4,B4,B4,A4,G4,E4,D4,E4,G4,G4,A4,B4,D5,B4,A4,G4,E4,D4,E4,G4,A4,B4,A4,G4,E4,D4,E4,G4],
[3,3,0,3,3,0,3,2,1,2,3,3,0,3,1,3,0,3,2,1,2,3,0,3,0,3,2,1,2,3],
[2,2,2,4,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,6],
5,G4);
var parting=pub("The Parting Glass","Irish/Scots (Trad.)",80,
[E4,G4,A4,B4,A4,G4,E4,D4,E4,G4,A4,B4,D5,B4,A4,G4,E4,G4,A4,B4,A4,G4,E4,D4,E4,G4,A4,B4,D5,B4,A4,G4],
[2,3,0,3,0,3,2,1,2,3,0,3,1,3,0,3,2,3,0,3,0,3,2,1,2,3,0,3,1,3,0,3],
// "Of all the mon-ey that e'er I had / I spent it in good com-pa-ny"
// Flowing Irish 4/4 with dotted-quarter+eighth pairs and half-notes on stressed syllables
[2,3,1,4,1,3,2,4, 2,3,1,2,6,2,2,6, 2,3,1,4,1,3,2,4, 2,3,1,2,6,2,2,6],
4,E4);
var amazing=pub("Amazing Grace","J. Newton (1779)",80,
[G4,C5,E5,C5,E5,D5,C5,A4,G4,G4,C5,E5,C5,E5,D5,E5,E5,C5,A4,A4,G4,E4,G4,C5,E5,C5,E5,D5,C5],
[3,0,2,0,2,1,0,0,3,3,0,2,0,2,1,2,2,0,0,0,3,2,3,0,2,0,2,1,0],
[2,4,3,1,2,2,2,4,4,2,4,3,1,2,2,4,4,3,1,2,2,4,2,4,3,1,2,2,6],
4,C4);
var auld=pub("Auld Lang Syne","R. Burns (1788)",95,
[C4,F4,F4,F4,A4,G4,F4,G4,A4,F4,F4,A4,C5,D5,D5,D5,C5,A4,F4,F4,G4,A4,G4,F4,D4,C4],
[0,1,1,1,0,3,1,3,0,1,1,0,0,1,1,1,0,0,1,1,3,0,3,1,1,0],
[2,3,1,2,3,1,2,2,3,1,2,2,2,6,2,3,1,2,3,1,2,3,1,2,2,6],
4,F4);
var saints=pub("When the Saints Go Marching In","Spiritual (Trad.)",120,
[C4,E4,F4,G4,C4,E4,F4,G4,C4,E4,F4,G4,E4,C4,E4,D4,E4,E4,D4,C4,C4,E4,G4,G4,F4,E4,F4,G4,E4,C4,D4,C4],
[0,2,1,3,0,2,1,3,0,2,1,3,2,0,2,1,2,2,1,0,0,2,3,3,1,2,1,3,2,0,1,0],
[2,2,2,6,2,2,2,6,2,2,2,4,2,2,2,4,2,2,2,4,2,2,4,2,4,2,2,4,2,2,2,8],
4,C4);
var clementine=pub("Oh My Darling, Clementine","P. Montrose (1884)",110,
[C4,C4,C4,G3,E4,E4,E4,C4,C4,E4,G4,G4,F4,E4,F4,G4,G4,F4,E4,F4,E4,D4,C4,D4,E4,F4,F4,E4,D4,E4,C4],
[0,0,0,3,2,2,2,0,0,2,3,3,1,2,1,3,3,1,2,1,2,1,0,1,2,1,1,2,1,2,0],
[2,2,2,4,2,2,2,4,2,2,4,2,2,2,4,2,2,2,2,2,2,4,2,2,4,2,2,2,2,2,8],
4,C4);
var scarborough=pub("Scarborough Fair","English (Medieval)",85,
[D4,D4,A4,A4,D5,D5,C5,D5,E5,D5,C5,A4,B4,A4,G4,B4,A4,G4,F4,F4,G4,A4,F4,E4,D4,E4,F4,D4,D4,F4,G4,A4,G4,F4,E4,D4],
[0,0,0,0,1,1,0,1,2,1,0,0,3,0,3,3,0,3,1,1,3,0,1,2,0,2,1,0,0,1,3,0,3,1,2,0],
[4,4,2,2,4,2,2,2,4,2,2,2,2,2,2,2,4,4,2,2,2,2,2,2,2,2,4,4,2,2,2,4,2,2,2,6],
4,D4);
var bonnie=pub("My Bonnie Lies Over the Ocean","Scottish (1881)",120,
[G4,C5,C5,D5,E5,E5,D5,C5,A4,G4,C5,C5,D5,E5,E5,D5,C5,G4,C5,C5,D5,E5,E5,D5,C5,A4,A4,G4,F4,E4,F4,G4,E4,C4,E5,E5,E5,E5,D5,C5,C5,D5,E5,E5,E5,E5,D5,C5,A4,C5],
[3,0,0,1,2,2,1,0,0,3,0,0,1,2,2,1,0,3,0,0,1,2,2,1,0,0,0,3,1,2,1,3,2,0,2,2,2,2,1,0,0,1,2,2,2,2,1,0,0,0],
[2,2,2,2,2,4,2,2,4,2,2,2,2,2,4,2,4,2,2,2,2,2,4,2,2,4,2,2,2,2,2,4,2,8,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,8],
4,C4);
// ── Wellerman (accurate melody with driving stomp beat) ──
var wellerman=(function(){
var bg=[],mel=[],t=0,q=0.32;
function vox(t,f,d,v,inst){bg.push({t:t,f:f,d:d,i:inst||'strings',v:v||0.35});}
function stomp(t,v){bg.push({t:t,f:110,d:0.2,i:'timpani',v:v||0.4});}
// Verse melody (Am): "There once was a ship that put to sea..."
var verse1=[E4,E4,E4,E4,A4,A4,G4,E4];
var verse2=[E4,E4,E4,E4,A4,A4,G4,E4];
var verse3=[A4,A4,A4,A4,C5,C5,B4,A4];
var verse4=[E4,E4,G4,G4,E4,D4];
var vL1=[2,2,2,2,0,0,3,2],vL2=[2,2,2,2,0,0,3,2],vL3=[0,0,0,0,1,1,3,0],vL4=[2,2,3,3,2,1];
var vD1=[1,1,1,1,1,1,1,2],vD2=[1,1,1,1,1,1,1,2],vD3=[1,1,1,1,1,1,1,2],vD4=[1,1,1,1,1,3];
// Chorus melody: "Soon may the Wellerman come..."
var ch1=[A4,C5,C5,C5,D5,E5];
var ch2=[E5,D5,C5,D5,E5,C5];
var ch3=[A4,C5,C5,C5,D5,E5];
var ch4=[E5,D5,C5,B4,A4];
var cL1=[0,1,1,1,1,2],cL2=[2,1,0,1,2,0],cL3=[0,1,1,1,1,2],cL4=[2,1,0,3,0];
var cD1=[1,1,1,1,1,2],cD2=[1,1,1,1,1,2],cD3=[1,1,1,1,1,2],cD4=[1,1,1,1,4];
function playLine(notes,lanes,durs,v,inst){
notes.forEach(function(f,i){
var d=durs[i]*q;
vox(t,f,d*0.85,v,inst);
if(v>0.3)bg.push({t:t,f:f/2,d:d*0.85,i:'bass',v:v*0.4});
mel.push({t:t,l:lanes[i]});
// Driving stomp on every beat
if(durs[i]>=1)stomp(t,v*0.8);
t+=d;
});
}
// 3 full rounds: verse+chorus, each building
var rounds=[
{v:0.3,i:'woodwind'},{v:0.4,i:'strings'},{v:0.55,i:'brass'}
];
rounds.forEach(function(r,ri){
// Add chord backing from round 2
if(ri>0){
var chordT=t;
[A3,A3,A3,A3,A3,A3,E3,E3].forEach(function(f,ci){
bg.push({t:chordT+ci*q*4,f:f,d:q*3.5,i:'strings',v:r.v*0.3});
});
}
// Add choir from round 3
if(ri>1){
var choirNotes=verse1.concat(verse2,verse3,verse4,ch1,ch2,ch3,ch4);
var choirT=t;
var allDurs=vD1.concat(vD2,vD3,vD4,cD1,cD2,cD3,cD4);
choirNotes.forEach(function(f,i){bg.push({t:choirT,f:f,d:allDurs[i]*q*0.8,i:'choir',v:r.v*0.3});choirT+=allDurs[i]*q;});
}
// Verse
playLine(verse1,vL1,vD1,r.v,r.i);
playLine(verse2,vL2,vD2,r.v,r.i);
playLine(verse3,vL3,vD3,r.v,r.i);
playLine(verse4,vL4,vD4,r.v,r.i);
t+=q*0.5;
// Chorus (bigger)
playLine(ch1,cL1,cD1,r.v*1.2,r.i);
playLine(ch2,cL2,cD2,r.v*1.2,r.i);
playLine(ch3,cL3,cD3,r.v*1.2,r.i);
playLine(ch4,cL4,cD4,r.v*1.2,r.i);
t+=q;
});
// Finale: big Am chord
[A3,C4,E4,A4].forEach(function(f){bg.push({t:t,f:f,d:2.5,i:'brass',v:0.55});bg.push({t:t,f:f,d:2.5,i:'choir',v:0.4});});
bg.push({t:t,f:A3/2,d:2.0,i:'timpani',v:0.6});mel.push({t:t,l:4});
return {n:"Wellerman",composer:"NZ Sea Shanty (1860s)",bpm:125,duration:Math.ceil(t+3),isClassical:true,isSingalong:true,timeSig:[4,4],bg:bg,melody:mel};
})();
// ── Leave Her Johnny ──
var leavejohnny=pub("Leave Her Johnny","Sea Shanty (Trad.)",105,
[A3,A3,C4,D4,E4,E4,D4,C4,A3,G3,A3,C4,D4,E4,G4,E4,D4,C4,A3,G3,A3,C4,E4,D4,C4,A3],
[0,0,1,0,2,2,0,1,0,3,0,1,0,2,3,2,0,1,0,3,0,1,2,0,1,0],
[2,2,2,2,4,2,2,2,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,6],
5,A3);
// ── Blow the Man Down ──
var blowman=pub("Blow the Man Down","Sea Shanty (Trad.)",120,
[C4,E4,G4,G4,F4,E4,D4,C4,D4,E4,F4,E4,D4,C4,C4,E4,G4,C5,B4,A4,G4,F4,E4,D4,C4],
[0,2,3,3,1,2,1,0,1,2,1,2,1,0,0,2,3,0,3,0,3,1,2,1,0],
[2,2,4,2,2,2,2,4,2,2,2,2,2,6,2,2,4,2,2,2,2,2,2,2,8],
5,C4);
// ── Randy Dandy Oh ──
var randy=pub("Randy Dandy Oh","Sea Shanty (Trad.)",130,
[G4,G4,A4,B4,B4,A4,G4,E4,D4,G4,G4,A4,B4,D5,B4,A4,G4,E4,D4,G4,A4,B4,D5,B4,G4],
[3,3,0,3,3,0,3,2,1,3,3,0,3,1,3,0,3,2,1,3,0,3,1,3,3],
[2,2,2,4,2,2,2,2,4,2,2,2,4,2,2,2,2,2,4,2,2,4,2,2,8],
5,G4);
// ── South Australia ──
var southaus=pub("South Australia","Sea Shanty (Trad.)",115,
[C4,C4,D4,E4,G4,G4,E4,D4,C4,E4,G4,A4,G4,E4,C4,D4,E4,G4,E4,D4,C4,C4,E4,G4,E4,C4],
[0,0,1,2,3,3,2,1,0,2,3,0,3,2,0,1,2,3,2,1,0,0,2,3,2,0],
[2,2,2,2,4,2,2,2,4,2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,8],
5,C4);
// ── Roll the Old Chariot ──
var chariot=pub("Roll the Old Chariot Along","Sea Shanty (Trad.)",120,
[C4,E4,G4,G4,G4,A4,G4,E4,C4,D4,E4,F4,E4,D4,C4,C4,E4,G4,C5,A4,G4,E4,D4,C4],
[0,2,3,3,3,0,3,2,0,1,2,1,2,1,0,0,2,3,0,0,3,2,1,0],
[2,2,2,2,2,2,2,2,4,2,2,2,2,2,6,2,2,2,4,2,2,2,2,8],
5,C4);
// ── Haul Away Joe ──
var hauljoe=pub("Haul Away Joe","Sea Shanty (Trad.)",115,
[D4,D4,F4,A4,A4,G4,F4,D4,D4,F4,A4,C5,A4,G4,F4,D4,F4,A4,G4,F4,D4,C4,D4],
[0,0,1,0,0,3,1,0,0,1,0,0,0,3,1,0,1,0,3,1,0,1,0],
[2,2,2,4,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,4,2,8],
5,D4);
// ── Spanish Ladies ──
var spanish=pub("Spanish Ladies","Royal Navy Shanty (1790s)",110,
[E4,E4,D4,C4,D4,E4,F4,G4,A4,G4,F4,E4,D4,C4,D4,E4,F4,E4,D4,C4,B3,A3,G3,A3,B3,C4,D4,E4],
[2,2,1,0,1,2,1,3,0,3,1,2,1,0,1,2,1,2,1,0,3,0,3,0,3,0,1,2],
[2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,8],
5,C4);
// ── The Skye Boat Song ──
var skye=pub("The Skye Boat Song","Scottish (1884)",85,
[C4,F4,A4,G4,F4,E4,F4,G4,A4,C5,A4,G4,F4,E4,D4,C4,F4,A4,G4,F4,E4,F4,G4,A4,F4,D4,C4],
[0,1,0,3,1,2,1,3,0,0,0,3,1,2,1,0,1,0,3,1,2,1,3,0,1,1,0],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,6],
4,F4);
// ── Bella Ciao ──
var bella=pub("Bella Ciao","Italian Partisan Folk (1900s)",120,
[A3,B3,C4,D4,D4,C4,D4,E4,A3,B3,C4,D4,D4,C4,B3,A3,E4,E4,D4,E4,F4,F4,E4,D4,C4,B3,A3,C4,B3,A3],
[0,3,0,1,1,0,1,2,0,3,0,1,1,0,3,0,2,2,1,2,1,1,2,1,0,3,0,1,3,0],
[2,2,2,4,2,2,2,4,2,2,2,4,2,2,2,4,2,2,2,2,4,2,2,2,2,2,4,2,2,8],
5,A3);
return [drunken,wellerman,molly,whiskey,parting,leavejohnny,blowman,randy,southaus,chariot,hauljoe,spanish,skye,bella,amazing,auld,saints,clementine,scarborough,bonnie];
})();
// ── 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=30/bpm;
for(var rep=0;rep<passes;rep++){
var v=0.2+rep*0.08;
var inst=rep<Math.floor(passes*0.3)?'woodwind':rep<Math.floor(passes*0.6)?'strings':'choir';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>0)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,
[E4,C4,D4,E4,G4,G4,F4,E4,D4,C4,E4,C4,D4,E4,G4,G4,F4,E4,D4,C4,C4,C4,E4,G4,A4,G4,E4,C4,G4,F4,E4,D4,C4,C4,E4,G4,A4,G4,E4,C4,G4,F4,E4,D4,C4],
[2,0,1,2,3,3,1,2,1,0,2,0,1,2,3,3,1,2,1,0,0,0,2,3,0,3,2,0,3,1,2,1,0,0,2,3,0,3,2,0,3,1,2,1,0],
[2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,6],
3,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],
[2,4,2,2,4,2,2,4,2,2,2,4,2,2,2,4,2,2,2,4,2,2,2,2,4,6],
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],
[2,2,2,2,4,2,2,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,6],
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],
// Slow meditative spiritual. "Kum-ba-ya my Lord Kum-ba-ya"
// Dotted-quarter+eighth for the 3-syllable "Kum-ba-ya" feel, half-note on "Lord"
[3,1,2,4, 3,1,2,4, 3,1,2,4, 3,1,2,4, 3,1,2,4, 3,1,2,8],
5,C4);
var michael=spirit("Michael Row the Boat Ashore","Spiritual (1860s)",90,
[C4,E4,F4,G4,A4,A4,G4,E4,C4,C4,E4,F4,G4,A4,A4,G4,E4,C4,D4,E4,F4,E4,D4,C4,D4,C4],
[0,2,1,3,0,0,3,2,0,0,2,1,3,0,0,3,2,0,1,2,1,2,1,0,1,0],
[2,2,2,4,4,2,2,2,4,4,2,2,2,4,4,2,2,4,2,2,2,2,2,4,2,6],
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],
[2,2,2,4,2,2,2,2,2,2,4,2,2,4,2,2,2,4,2,2,2,8],
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],
// Slow gospel: "No-bo-dy knows the trou-ble I've seen" — dotted-feel on key words
[2,3,1,3,1,3,2,4, 3,1,4,2,2,2,2,6, 3,1,4,2,2,2,2,2,8],
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],
[2,2,2,2,2,2,2,2,2,2,2,2,4,2,2,4,2,2,2,2,2,2,2,2,4,2,2,8],
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=30/bpm;
for(var rep=0;rep<passes;rep++){
var v=0.2+rep*0.08;
var inst=rep<Math.floor(passes*0.3)?'woodwind':rep<Math.floor(passes*0.7)?'strings':'brass';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>1)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,E4,F4,E4,D4,E4,E4,F4,Ab4,A4,B4,B4,A4,Ab4,F4,E4,E4,F4,Ab4,A4,B4,A4,Ab4,A4,B4,C5,D5,C5,B4,A4,Ab4,A4,B4,C5,D5,C5,B4,A4,Ab4,E4],
[2,2,1,2,1,2,2,1,0,0,3,3,0,0,1,2,2,1,0,0,3,0,0,0,3,0,1,0,3,0,0,0,3,0,1,0,3,0,0,2],
[2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,8],
4,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],
[2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,4,2,2,2,2,4,2,2,2,2,2,10],
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],
[2,2,2,2,2,2,2,2,2,2,4,2,2,4,2,2,2,2,2,4,2,2,2,2,2,4,2,2,4,2,2,8],
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],
[2,2,2,2,4,2,2,2,2,2,2,2,2,6,2,2,4,2,2,2,2,2,2,2,4,2,2,8],
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],
[2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,4,2,2,2,4,2,2,2,2,2,2,8],
5,G4);
var osole=world("'O Sole Mio","Italian (1898)",90,
[C4,E4,E4,F4,E4,D4,C4,D4,E4,E4,F4,G4,A4,G4,F4,E4,E4,F4,G4,A4,Bb4,A4,G4,C5,C5,Bb4,A4,G4,F4,E4,F4,G4,A4,G4,F4,E4,D4,C4],
[0,2,2,1,2,1,0,1,2,2,1,3,0,3,1,2,2,1,3,0,2,0,3,0,0,2,0,3,1,2,1,3,0,3,1,2,1,0],
[4,2,2,2,2,2,4,2,2,2,2,4,2,2,2,4,2,2,2,4,2,2,4,4,2,2,2,2,4,2,2,2,2,2,2,2,2,6],
3,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],
[2,2,4,2,2,4,2,4,2,4,2,2,6,2,2,4,2,2,2,2,2,2,4,2,2,2,2,8],
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,2,2,2,2,4,1,1,2,2,2,4,2,2,2,2,2,4,1,1,2,2,6],
5,G4);
return [hava,cucaracha,frere,cielito,waltzing,osole,ballgame,mountain];
})();
// ── NURSERY RHYMES (very simple, beginner-friendly) ──
const NURSERY_PIECES=(function(){
var C4=NT.C4,D4=NT.D4,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,G3=NT.G3,A3=NT.A3,B3=NT.B3;
function rhyme(name,origin,bpm,theme,lanes,durations,passes,root){
var bg=[],mel=[],t=0,q=30/bpm;
for(var rep=0;rep<passes;rep++){
var v=0.25+rep*0.08;
var inst=rep<1?'woodwind':'strings';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.85,i:inst,v:v});
if(rep>0)bg.push({t:t,f:f/2,d:d*0.85,i:'bass',v:v*0.25});
mel.push({t:t,l:lanes[i]});t+=d;
});
t+=0.5;
}
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:'strings',v:0.4});});
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};
}
// ── Twinkle Twinkle Little Star (same melody as Baa Baa Black Sheep) ──
var twinkle=rhyme("Twinkle Twinkle Little Star","Traditional (1760s)",75,
[C4,C4,G4,G4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4,G4,G4,F4,F4,E4,E4,D4,C4,C4,G4,G4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4],
[0,0,3,3,0,0,3,1,1,2,2,1,1,0,3,3,1,1,2,2,1,0,0,3,3,0,0,3,1,1,2,2,1,1,0],
[2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,6],
3,C4);
// ── Mary Had a Little Lamb ──
var mary=rhyme("Mary Had a Little Lamb","S.J. Hale (1830)",80,
[E4,D4,C4,D4,E4,E4,E4,D4,D4,D4,E4,G4,G4,E4,D4,C4,D4,E4,E4,E4,D4,D4,E4,D4,C4],
[2,1,0,1,2,2,2,1,1,1,2,3,3,2,1,0,1,2,2,2,1,1,2,1,0],
[2,2,2,2,2,2,4,2,2,4,2,2,4,2,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
// ── Happy Birthday ──
var birthday=rhyme("Happy Birthday","P.S. Hill (1893)",75,
[C4,C4,D4,C4,F4,E4,C4,C4,D4,C4,G4,F4,C4,C4,C5,A4,F4,E4,D4,Bb4,Bb4,A4,F4,G4,F4],
[0,0,1,0,1,2,0,0,1,0,3,1,0,0,0,0,1,2,1,2,2,0,1,3,1],
[1,1,2,2,2,4,1,1,2,2,2,4,1,1,2,2,2,2,4,1,1,2,2,2,6],
3,C4);
// ── Chopsticks ──
var chopsticks=rhyme("Chopsticks","Euphemia Allen (1877)",95,
[F4,G4,F4,G4,F4,G4,E4,G4,E4,G4,E4,G4,D4,F4,D4,F4,C4,E4,C4,E4,F4,G4,F4,G4,E4,G4,E4,G4,D4,F4,D4,F4,C4,E4,C4,E4],
[1,3,1,3,1,3,2,3,2,3,2,3,1,1,1,1,0,2,0,2,1,3,1,3,2,3,2,3,1,1,1,1,0,2,0,2],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,8],
3,C4);
// ── Lavender's Blue ──
var lavender=rhyme("Lavender's Blue","English Traditional (1670s)",80,
[E4,E4,D4,E4,F4,G4,G4,A4,G4,F4,F4,E4,D4,E4,E4,E4,D4,E4,F4,G4,G4,A4,G4,F4,E4],
[2,2,1,2,1,3,3,0,3,1,1,2,1,2,2,2,1,2,1,3,3,0,3,1,2],
[2,2,2,2,2,2,2,2,2,4,2,2,2,4,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
// ── Shortnin' Bread ──
var shortnin=rhyme("Shortnin' Bread","Traditional (1900s)",95,
[E5,E5,D5,C5,D5,C5,A4,G4,A4,G4,A4,G4,E5,E5,D5,C5,D5,C5,A4,G4,A4,G4,C5],
[2,2,1,0,1,0,0,3,0,3,0,3,2,2,1,0,1,0,0,3,0,3,0],
[2,2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,8],
3,C4);
// ── London Bridge ──
var london=rhyme("London Bridge Is Falling Down","English (1744)",75,
[G4,A4,G4,F4,E4,F4,G4,D4,E4,F4,E4,F4,G4,G4,A4,G4,F4,E4,F4,G4,D4,G4,E4,C4],
[3,0,3,1,2,1,3,1,2,1,2,1,3,3,0,3,1,2,1,3,1,3,2,0],
[2,2,2,2,2,2,4,2,2,4,2,2,4,2,2,2,2,2,2,4,4,2,2,6],
3,C4);
// ── Old MacDonald ──
var macdonald=rhyme("Old MacDonald Had a Farm","English (1706)",80,
[C4,C4,C4,G4,A4,A4,G4,E4,E4,D4,D4,C4,G4,C4,C4,C4,G4,A4,A4,G4,E4,E4,D4,D4,C4],
[0,0,0,3,0,0,3,2,2,1,1,0,3,0,0,0,3,0,0,3,2,2,1,1,0],
[2,2,2,2,2,2,4,2,2,2,2,4,4,2,2,2,2,2,2,4,2,2,2,2,6],
3,C4);
// ── Row Row Row Your Boat ──
var row=rhyme("Row Row Row Your Boat","English (1852)",70,
[C4,C4,C4,D4,E4,E4,D4,E4,F4,G4,C5,C5,C5,G4,G4,G4,E4,E4,E4,C4,C4,C4,G4,F4,E4,D4,C4],
[0,0,0,1,2,2,1,2,1,3,0,0,0,3,3,3,2,2,2,0,0,0,3,1,2,1,0],
[2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
// ── Three Blind Mice ──
var mice=rhyme("Three Blind Mice","English (1609)",75,
[E4,D4,C4,E4,D4,C4,G4,F4,E4,G4,F4,E4,G4,A4,G4,F4,E4,D4,G4,A4,G4,F4,E4,D4,C4],
[2,1,0,2,1,0,3,1,2,3,1,2,3,0,3,1,2,1,3,0,3,1,2,1,0],
[2,2,4,2,2,4,2,2,4,2,2,4,2,2,2,2,2,4,2,2,2,2,2,2,6],
3,C4);
// ── Itsy Bitsy Spider ──
var spider=rhyme("Itsy Bitsy Spider","English (1910s)",75,
[G4,C5,C5,C5,D5,E5,E5,D5,C5,D5,E5,C5,E5,E5,D5,D5,C5,G4,C5,C5,C5,D5,E5,E5,D5,C5,D5,E5,C5],
[3,0,0,0,1,2,2,1,0,1,2,0,2,2,1,1,0,3,0,0,0,1,2,2,1,0,1,2,0],
[2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,2,6],
3,C4);
// ── Pop Goes the Weasel ──
var weasel=rhyme("Pop Goes the Weasel","English (1850s)",85,
[C4,E4,E4,F4,G4,G4,C4,E4,E4,D4,F4,A4,A4,G4,C4,E4,E4,F4,G4,G4,E4,C4,D4,F4,E4,C4],
[0,2,2,1,3,3,0,2,2,1,1,0,0,3,0,2,2,1,3,3,2,0,1,1,2,0],
[2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
// ── Hot Cross Buns ──
var hotcross=rhyme("Hot Cross Buns","English (1798)",65,
[E4,D4,C4,E4,D4,C4,C4,C4,C4,C4,D4,D4,D4,D4,E4,D4,C4],
[2,1,0,2,1,0,0,0,0,0,1,1,1,1,2,1,0],
[2,2,4,2,2,4,2,2,2,2,2,2,2,2,2,2,6],
4,C4);
// ── Jack and Jill ──
var jack=rhyme("Jack and Jill","English (1765)",80,
[C4,C4,D4,E4,E4,F4,G4,G4,F4,E4,F4,G4,A4,A4,G4,F4,E4,D4,C4],
[0,0,1,2,2,1,3,3,1,2,1,3,0,0,3,1,2,1,0],
[2,2,2,2,2,2,2,4,2,2,2,2,2,4,2,2,2,2,6],
3,C4);
// ── Humpty Dumpty ──
var humpty=rhyme("Humpty Dumpty","English (1797)",75,
[E4,G4,A4,G4,F4,E4,D4,E4,F4,E4,D4,C4,E4,G4,A4,G4,F4,E4,D4,C4],
[2,3,0,3,1,2,1,2,1,2,1,0,2,3,0,3,1,2,1,0],
[2,2,4,2,2,2,4,2,2,2,2,4,2,2,4,2,2,2,2,6],
3,C4);
// ── Baa Baa Black Sheep (same melody as Twinkle Twinkle) ──
var baa=rhyme("Baa Baa Black Sheep","English (1731)",80,
[C4,C4,G4,G4,A4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4,G4,G4,G4,F4,F4,F4,E4,E4,E4,D4,D4,D4,C4],
[0,0,3,3,0,0,0,3,1,1,2,2,1,1,0,3,3,3,1,1,1,2,2,2,1,1,1,0],
[2,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
// ── Classics (from Prime, simple versions) ──
var twinkle=rhyme("Twinkle Twinkle Little Star","Traditional (1760s)",75,
[C4,C4,G4,G4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4,G4,G4,F4,F4,E4,E4,D4,C4,C4,G4,G4,A4,A4,G4,F4,F4,E4,E4,D4,D4,C4],
[0,0,3,3,0,0,3,1,1,2,2,1,1,0,3,3,1,1,2,2,1,0,0,3,3,0,0,3,1,1,2,2,1,1,0],
[2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,6],
3,C4);
var mary=rhyme("Mary Had a Little Lamb","S.J. Hale (1830)",80,
[E4,D4,C4,D4,E4,E4,E4,D4,D4,D4,E4,G4,G4,E4,D4,C4,D4,E4,E4,E4,D4,D4,E4,D4,C4],
[2,1,0,1,2,2,2,1,1,1,2,3,3,2,1,0,1,2,2,2,1,1,2,1,0],
[2,2,2,2,2,2,4,2,2,4,2,2,4,2,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
var birthday=rhyme("Happy Birthday","P.S. Hill (1893)",75,
[C4,C4,D4,C4,F4,E4,C4,C4,D4,C4,G4,F4,C4,C4,C5,A4,F4,E4,D4,Bb4,Bb4,A4,F4,G4,F4],
[0,0,1,0,1,2,0,0,1,0,3,1,0,0,0,0,1,2,1,2,2,0,1,3,1],
[1,1,2,2,2,4,1,1,2,2,2,4,1,1,2,2,2,2,4,1,1,2,2,2,6],
3,C4);
var chopsticks=rhyme("Chopsticks","Euphemia Allen (1877)",95,
[F4,G4,F4,G4,F4,G4,E4,G4,E4,G4,E4,G4,D4,F4,D4,F4,C4,E4,C4,E4,F4,G4,F4,G4,E4,G4,E4,G4,D4,F4,D4,F4,C4,E4,C4,E4],
[1,3,1,3,1,3,2,3,2,3,2,3,1,1,1,1,0,2,0,2,1,3,1,3,2,3,2,3,1,1,1,1,0,2,0,2],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,8],
3,C4);
var lavender=rhyme("Lavender's Blue","English Traditional (1670s)",80,
[E4,E4,D4,E4,F4,G4,G4,A4,G4,F4,F4,E4,D4,E4,E4,E4,D4,E4,F4,G4,G4,A4,G4,F4,E4],
[2,2,1,2,1,3,3,0,3,1,1,2,1,2,2,2,1,2,1,3,3,0,3,1,2],
[2,2,2,2,2,2,2,2,2,4,2,2,2,4,2,2,2,2,2,2,2,2,2,2,6],
3,C4);
var shortnin=rhyme("Shortnin' Bread","Traditional (1900s)",95,
[E5,E5,D5,C5,D5,C5,A4,G4,A4,G4,A4,G4,E5,E5,D5,C5,D5,C5,A4,G4,A4,G4,C5],
[2,2,1,0,1,0,0,3,0,3,0,3,2,2,1,0,1,0,0,3,0,3,0],
[2,2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,8],
3,C4);
return [baa,london,macdonald,row,mice,spider,weasel,hotcross,jack,humpty,twinkle,mary,birthday,chopsticks,lavender,shortnin];
})();
// ── RAGTIME (Scott Joplin & contemporaries, all public domain) ──
const RAGTIME_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,Ab4=NT.Ab4,A4=NT.A4,Bb4=NT.Bb4,B4=NT.B4,
C5=NT.C5,D5=NT.D5,Eb5=NT.Eb5,E5=NT.E5,F5=NT.F5,G5=NT.G5;
function rag(name,composer,bpm,theme,lanes,durations,passes,root){
var bg=[],mel=[],t=0,q=30/bpm;
for(var rep=0;rep<passes;rep++){
var v=0.2+rep*0.08;
// Ragtime uses piano-like timbres: strings for right hand, bass for left
var inst=rep%2===0?'strings':'woodwind';
theme.forEach(function(f,i){
var d=(durations?durations[i]:1)*q;
bg.push({t:t,f:f,d:d*0.8,i:inst,v:v});
// Stride bass pattern: root on beats, fifth on off-beats
if(i%4===0)bg.push({t:t,f:(root||theme[0])/2,d:d*0.6,i:'bass',v:v*0.5});
if(i%4===2)bg.push({t:t,f:(root||theme[0])*3/4,d:d*0.6,i:'bass',v:v*0.4});
mel.push({t:t,l:lanes[i]});t+=d;
});
t+=0.3;
}
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:'strings',v:0.5});});
bg.push({t:t,f:r/2,d:1.5,i:'bass',v:0.5});mel.push({t:t,l:4});
return {n:name,composer:composer,bpm:bpm,duration:Math.ceil(t+2.5),isClassical:true,bg:bg,melody:mel};
}
// ── The Entertainer — A section (most famous part) ──
var entertainer=rag("The Entertainer","Scott Joplin (1902)",130,
[D4,Eb4,E4,C5,E4,C5,E4,C5,C5,D5,Eb5,E5,C5,D5,E5,C5,D5,Eb5,E5,C5,E4,C5,E4,C5,A4,G4,A4,B4,C5],
[1,2,2,0,2,0,2,0,0,1,2,2,0,1,2,0,1,2,2,0,2,0,2,0,0,3,0,3,0],
[2,2,2,4,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,4,2,2,2,2,2,2,2,2,6],
4,C4);
// ── Maple Leaf Rag — A section ──
var maple=rag("Maple Leaf Rag","Scott Joplin (1899)",140,
[Ab4,A4,C5,E5,C5,A4,C5,A4,Ab4,A4,C5,E5,D5,C5,A4,G4,Ab4,A4,C5,E5,C5,A4,C5,A4,G4,A4,G4,E4,C4],
[0,0,0,2,0,0,0,0,0,0,0,2,1,0,0,3,0,0,0,2,0,0,0,0,3,0,3,2,0],
[2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2,2,2,2,8],
4,A4);
// ── The Easy Winners — A section ──
var easyw=rag("The Easy Winners","Scott Joplin (1901)",125,
[G4,B4,D5,B4,A4,G4,E4,G4,A4,B4,C5,D5,E5,D5,C5,B4,A4,G4,B4,D5,B4,A4,G4,E4,D4,E4,G4],
[3,3,1,3,0,3,2,3,0,3,0,1,2,1,0,3,0,3,3,1,3,0,3,2,1,2,3],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,6],
4,G4);
// ── Elite Syncopations — A section ──
var elite=rag("Elite Syncopations","Scott Joplin (1902)",130,
[E4,G4,A4,B4,C5,D5,E5,D5,C5,B4,A4,G4,E4,G4,A4,B4,C5,E5,D5,C5,B4,A4,G4,E4,D4,C4],
[2,3,0,3,0,1,2,1,0,3,0,3,2,3,0,3,0,2,1,0,3,0,3,2,1,0],
[2,2,2,2,2,2,2,2,2,2,2,2,4,2,2,2,2,2,2,2,2,2,2,2,2,6],
4,C4);
// ── Solace — A section (a Mexican serenade, slower ragtime) ──
var solace=rag("Solace","Scott Joplin (1909)",95,
[A4,G4,A4,C5,B4,A4,G4,E4,D4,E4,G4,A4,B4,A4,G4,E4,A4,G4,A4,C5,D5,C5,B4,A4,G4,E4,D4,C4],
[0,3,0,0,3,0,3,2,1,2,3,0,3,0,3,2,0,3,0,0,1,0,3,0,3,2,1,0],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,6],
4,A4);
// ── Peacherine Rag — A section ──
var peacherine=rag("Peacherine Rag","Scott Joplin (1901)",135,
[C5,C5,B4,C5,E5,D5,C5,A4,C5,C5,B4,C5,G4,A4,B4,C5,C5,B4,C5,E5,D5,C5,B4,A4,G4],
[0,0,3,0,2,1,0,0,0,0,3,0,3,0,3,0,0,3,0,2,1,0,3,0,3],
[2,2,2,4,2,2,2,4,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,8],
4,C4);
// ── The Cascades — A section ──
var cascades=rag("The Cascades","Scott Joplin (1904)",125,
[C5,B4,C5,E5,G5,E5,C5,D5,C5,B4,A4,G4,C5,B4,C5,E5,G5,E5,C5,D5,E5,D5,C5,B4,C5],
[0,3,0,2,3,2,0,1,0,3,0,3,0,3,0,2,3,2,0,1,2,1,0,3,0],
[2,2,4,2,2,2,2,2,2,2,2,4,2,2,4,2,2,2,2,2,2,2,2,2,6],
4,C4);
// ── Pineapple Rag — A section ──
var pineapple=rag("Pineapple Rag","Scott Joplin (1908)",130,
[C4,E4,G4,C5,E5,C5,G4,E4,C4,E4,G4,B4,D5,B4,G4,E4,C4,E4,G4,C5,E5,D5,C5,B4,A4,G4,C5],
[0,2,3,0,2,0,3,2,0,2,3,3,1,3,3,2,0,2,3,0,2,1,0,3,0,3,0],
[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,6],
4,C4);
return [entertainer,maple,easyw,elite,solace,peacherine,cascades,pineapple];
})();
var SYNTH_GENRES={"Nursery Rhymes":NURSERY_PIECES,"Singalong":SINGALONG_PIECES,"Christmas":CHRISTMAS_PIECES,"Folk":FOLK_PIECES,"Spirituals":SPIRITUAL_PIECES,"World":WORLD_PIECES,"Ragtime":RAGTIME_PIECES,"Classical":CLASSICAL_PIECES,"Video Games":VIDEOGAME_PIECES,"Chiptune Originals":CHIPTUNE_PIECES};
function simplifyNotes(notes){
if(!simpleMode||notes.length<2)return notes;
// Keep roughly half the notes — enforce a minimum gap between consecutive notes
var sorted=notes.slice().sort(function(a,b){return a.time-b.time;});
var minGap=0.35/gameSpeed; // ~350ms minimum between notes in simple mode
var result=[sorted[0]];
for(var i=1;i<sorted.length;i++){
if(sorted[i].time-result[result.length-1].time>=minGap){
result.push(sorted[i]);
}
}
return result;
}
function buildClassicalNotes(piece){
// Check for saved melody edits
var saved=edLoadSaved(piece.n);
var melody=saved?saved.melody:piece.melody;
return melody.filter(function(m){return activePads[m.l];})
.map(function(m){
var freq=261.63;
for(var i=0;i<piece.bg.length;i++){
var b=piece.bg[i];
if(Math.abs(b.t-m.t)<0.05&&b.i!=='bass'&&b.i!=='timpani'){freq=b.f;break;}
}
return{time:m.t/gameSpeed,lane:m.l,freq:freq,hit:false,missed:false};
})
.sort(function(a,b){return a.time-b.time;});
return simplifyNotes(raw);
}
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/gameSpeed,n.d/gameSpeed,n.i,n.v*1.5,gameSynthNodes);
}
gameEndTime=piece.duration/gameSpeed;
setTimeout(function(){if(playing)endTurn();},(piece.duration/gameSpeed)*1000+5000);
}
// ── STATE ──────────────────────────────────────────────
let mode="builtin",curGenre="Singalong",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,gameEndTime=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,0,0,0],msgTmr=null,prevBtns={},gameSynthNodes=[],classicalHitMode='orchestra';
let paused=false,pauseTime=0;
var gpMap=JSON.parse(localStorage.getItem("drgameGpMap")||"null")||{0:2,1:3,2:0,3:1,4:4,5:5,6:6,7:7,8:4};
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")||"[]");
var hiddenSongs=JSON.parse(localStorage.getItem("drgameHiddenSongs")||"{}");
function isSongHidden(genre,name){return hiddenSongs[genre]&&hiddenSongs[genre].indexOf(name)>=0;}
function hideSong(genre,name){
if(!mathGate("hide this song"))return;
if(!hiddenSongs[genre])hiddenSongs[genre]=[];
if(hiddenSongs[genre].indexOf(name)<0)hiddenSongs[genre].push(name);
localStorage.setItem("drgameHiddenSongs",JSON.stringify(hiddenSongs));
buildMenu();
}
function showAllSongs(genre){
delete hiddenSongs[genre];
localStorage.setItem("drgameHiddenSongs",JSON.stringify(hiddenSongs));
buildMenu();
}
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(){if(paused){paused=false;getAc().resume();document.getElementById("pause-overlay").classList.remove("show");}stopGame();stopPreview();show("sm");refreshMiniLB();}
function goScores(){renderScores();show("ss");}
// ── PAD TOGGLES ────────────────────────────────────────
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;
b.onclick=()=>{activePads[i]=!activePads[i];buildPadToggles();};
el.appendChild(b);
});
}
buildPadToggles();
function setSimpleMode(on){
simpleMode=on;
var full=document.getElementById("density-full"),simple=document.getElementById("density-simple");
if(full){full.className="smbtn"+(on?"":" purple");}
if(simple){simple.className="smbtn"+(on?" purple":"");}
}
setSimpleMode(false);
function saveSettings(){localStorage.setItem("drgameSettings",JSON.stringify({speed:gameSpeed,volume:masterVolume}));}
function syncSliders(){
var sv=Math.round(gameSpeed*100),vv=Math.round(masterVolume*100);
['speed-slider'].forEach(function(id){var e=document.getElementById(id);if(e)e.value=sv;});
['vol-slider','hud-vol'].forEach(function(id){var e=document.getElementById(id);if(e)e.value=vv;});
var se=document.getElementById("speed-val");if(se)se.textContent=sv+"%";
var ve=document.getElementById("vol-val");if(ve)ve.textContent=vv+"%";
var hve=document.getElementById("hud-vol-val");if(hve)hve.textContent=vv+"%";
}
function setSpeed(v){gameSpeed=v/100;syncSliders();saveSettings();}
function setVolume(v){masterVolume=v/100;syncSliders();saveSettings();}
function resetSettings(){gameSpeed=1.0;masterVolume=1.0;syncSliders();saveSettings();}
function loadSettings(){syncSliders();}
function confirmReset(){
if(!mathGate("reset ALL saved data")){alert("Incorrect — data NOT cleared.");return;}
localStorage.removeItem("drgame5");localStorage.removeItem("drgameSettings");
localStorage.removeItem("drgameGpMap");localStorage.removeItem("drgameKeyMap");localStorage.removeItem("drgameHidden");localStorage.removeItem("drgameHiddenSongs");
savedScores=[];hiddenGenres=[];hiddenSongs={};gpMap={0:2,1:3,2:0,3:1,4:4,5:5,6:6,7:7,8:4};customKeyMap=null;
LANES[0].key="f";LANES[1].key="g";LANES[2].key="h";LANES[3].key="j";LANES[4].key=" ";LANES[5].key="d";LANES[6].key="k";LANES[7].key="l";
refreshMiniLB();resetSettings();buildMenu();
alert("All data cleared!");
}
function hideGenre(g){
if(!mathGate("hide this genre"))return;
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':'');
document.getElementById("hitmode-orch").className="smbtn"+(m==='orchestra'?' purple':'');
}
function updateHitModeVisibility(){
document.getElementById("hitmode-toggle").style.display=SYNTH_GENRES[curGenre]?"block":"none";
}
// ── MENU ───────────────────────────────────────────────
function buildMenu(){
const gt=document.getElementById("gtabs");gt.innerHTML="";
var synthIcons={"Nursery Rhymes":"\uD83E\uDDD2","Singalong":"\uD83C\uDFB5","Christmas":"\uD83C\uDF84","Folk":"\uD83C\uDFB6","Spirituals":"\uD83D\uDE4F","World":"\uD83C\uDF0D","Ragtime":"\uD83C\uDFB9","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=(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="";rg.ondblclick=()=>startGame();
var songList=SYNTH_GENRES[curGenre]||GENRES[curGenre];
var hidCount=0;
songList.forEach((r,i)=>{
if(isSongHidden(curGenre,r.n)){hidCount++;return;}
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();};
b.oncontextmenu=(e)=>{e.preventDefault();hideSong(curGenre,r.n);};
rg.appendChild(b);
});
if(hidCount>0){
const sb=document.createElement("button");sb.className="rbtn";sb.style.opacity="0.4";sb.style.fontSize="10px";
sb.textContent="\u2795 Show "+hidCount+" hidden";sb.onclick=()=>showAllSongs(curGenre);
rg.appendChild(sb);
}
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();
updateEditBtn();
}
buildMenu();loadSettings();
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=SYNTH_GENRES[curGenre]?SYNTH_GENRES[curGenre][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();
// ── MELODY EDITOR ─────────────────────────────────────
var edPiece=null,edMelody=[],edBpm=120,edBars=8,edSelected=-1,edScrollX=0,edDragging=false,edDragNote=-1,edDragOffX=0,edDragOffY=0,edPreviewing=false,edIsClassical=false,edGenre="";
var edIsSingalong=false; // true = no bg orchestra in preview
var edTimeSig=[4,4]; // time signature [beats per measure, beat unit]
var edResizing=false,edResizeNote=-1; // right-edge duration drag
var edSelRange=null,edSelecting=false,edClipboard=[]; // range select + copy/paste
var edPlayhead=-1,edPlayheadStart=0,edPlayheadRAF=0,edPlayheadOffset=0; // preview playhead + start offset
var edShowLabels=false; // toggle note labels on canvas
var edShowPitch=false; // toggle pitch names on notes
var edPlayFromMarker=-1; // click-to-play marker position in seconds
var ED_LANE_H=50,ED_KICK_H=40,ED_HEAD=32,ED_NOTE_W=14,ED_NOTE_H=16,ED_MIN_DUR=0.05;
var ED_COLORS=["#e53e3e","#d69e2e","#3182ce","#38a169","#805ad5"];
var ED_NAMES=["Red","Yellow","Blue","Green","Kick"];
// Frequency to note name lookup
var ED_NOTE_NAMES=(function(){
var names=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"];
var map={};
for(var oct=0;oct<=8;oct++){
for(var n=0;n<12;n++){
var freq=440*Math.pow(2,(n-9)/12+(oct-4));
map[Math.round(freq*10)]=names[n]+oct;
}
}
// Add enharmonic aliases for display
return map;
})();
function edFreqToNote(freq){
if(!freq||freq<=0)return"?";
var key=Math.round(freq*10);
if(ED_NOTE_NAMES[key])return ED_NOTE_NAMES[key];
// Find closest
var best="?",bestDist=999;
for(var k in ED_NOTE_NAMES){
var d=Math.abs(parseInt(k)-key);
if(d<bestDist){bestDist=d;best=ED_NOTE_NAMES[k];}
}
return best;
}
// Note name to frequency (reverse of edFreqToNote)
// Accepts: C4, D#5, Db3, Eb4, F#3, Bb2, etc.
function edNoteToFreq(name){
if(!name||name==="?")return 0;
name=name.trim();
var match=name.match(/^([A-Ga-g])(#{1,2}|b{1,2}|♯|♭)?(\d)$/);
if(!match)return 0;
var letter=match[1].toUpperCase();
var accidental=match[2]||"";
var octave=parseInt(match[3]);
var semitones={"C":0,"D":2,"E":4,"F":5,"G":7,"A":9,"B":11}[letter];
if(semitones===undefined)return 0;
if(accidental==="#"||accidental==="♯")semitones+=1;
else if(accidental==="##")semitones+=2;
else if(accidental==="b"||accidental==="♭")semitones-=1;
else if(accidental==="bb")semitones-=2;
// MIDI note number: C4=60, A4=69
var midi=(octave+1)*12+semitones;
return parseFloat((440*Math.pow(2,(midi-69)/12)).toFixed(2));
}
// Duration name to seconds
function edDurNameToSec(name){
var q=edQuarterSec();
var map={"whole":q*4,"dotted-half":q*3,"half":q*2,"dotted-quarter":q*1.5,"quarter":q,"dotted-eighth":q*0.75,"eighth":q/2,"sixteenth":q/4,"32nd":q/8};
return map[name]||0;
}
// Duration seconds to name (for dropdown selection)
function edDurSecToName(durSec){
if(!durSec||durSec<=0)return "0";
var q=edQuarterSec();
var ratio=durSec/q;
if(ratio>=3.5) return "whole";
if(ratio>=2.5) return "dotted-half";
if(ratio>=1.8) return "half";
if(ratio>=1.3) return "dotted-quarter";
if(ratio>=0.8) return "quarter";
if(ratio>=0.6) return "dotted-eighth";
if(ratio>=0.4) return "eighth";
if(ratio>=0.2) return "sixteenth";
return "32nd";
}
// Update note properties panel when selection changes
function edUpdateNotePanel(){
var panel=document.getElementById("ed-note-panel");
if(edSelected<0||edSelected>=edMelody.length){
panel.style.display="none";
return;
}
panel.style.display="block";
var m=edMelody[edSelected];
// Pitch input
var pitchInput=document.getElementById("ed-note-pitch");
pitchInput.value=m.f?edFreqToNote(m.f):"";
// Duration dropdown
var durSel=document.getElementById("ed-note-dur");
durSel.value=edDurSecToName(m.d||0);
// Lane dropdown
document.getElementById("ed-note-lane").value=m.l;
// Info line
var info="t="+m.t.toFixed(3)+"s";
if(m.f)info+=" | freq="+m.f.toFixed(1)+"Hz";
if(m.d>0)info+=" | dur="+m.d.toFixed(3)+"s";
document.getElementById("ed-note-info").textContent=info;
// Resize canvas since panel may have appeared
setTimeout(edResize,30);
}
// Set pitch from note panel input
function edSetPitch(val){
if(edSelected<0)return;
var freq=edNoteToFreq(val);
if(freq>0){
edMelody[edSelected].f=freq;
}else if(val.trim()===""){
delete edMelody[edSelected].f;
}else{
document.getElementById("ed-note-info").textContent="Invalid pitch — use e.g. C4, D#5, Bb3";
return;
}
edDraw();
edUpdateNotePanel();
}
// Set duration from note panel dropdown
function edSetDuration(val){
if(edSelected<0)return;
if(val==="0"){
delete edMelody[edSelected].d;
}else{
edMelody[edSelected].d=parseFloat(edDurNameToSec(val).toFixed(4));
}
edDraw();
edUpdateNotePanel();
}
// Set lane from note panel dropdown
function edSetLane(val){
if(edSelected<0)return;
edMelody[edSelected].l=parseInt(val);
edDraw();
edUpdateNotePanel();
}
function getEdCanvas(){return document.getElementById("ed-canvas");}
function getEdCtx(){return getEdCanvas().getContext("2d");}
function edPxPerUnit(){return parseInt(document.getElementById("ed-zoom").value);}
function edTimeToX(t){return t*edPxPerUnit()-edScrollX;}
function edXToTime(x){return(x+edScrollX)/edPxPerUnit();}
function edYToLane(y){
if(y<ED_HEAD)return-1;
var laneY=y-ED_HEAD;
var padH=ED_LANE_H;
for(var i=0;i<4;i++){if(laneY<padH*(i+1))return i;}
return 4; // kick
}
function edLaneToY(l){
if(l<4)return ED_HEAD+l*ED_LANE_H+ED_LANE_H/2;
return ED_HEAD+4*ED_LANE_H+ED_KICK_H/2;
}
function mathGate(actionLabel){
var a=Math.floor(Math.random()*20)+5,b=Math.floor(Math.random()*20)+5;
var ans=prompt("To "+actionLabel+", solve this:\n\nWhat is "+a+" + "+b+"?");
if(ans===null)return false;
return parseInt(ans)===a+b;
}
function openEditor(){
if(!mathGate("edit this melody"))return;
var pieces=SYNTH_GENRES[curGenre]||GENRES[curGenre];
if(!pieces||!pieces[curIdx])return;
edPiece=pieces[curIdx];
edGenre=curGenre;
edIsClassical=!!edPiece.isClassical;
edIsSingalong=!!edPiece.isSingalong||(curGenre!=="Classical"&&edIsClassical);
edTimeSig=edPiece.timeSig||[4,4];
// Load saved edits or use original
var saved=edLoadSaved(edPiece.n);
if(saved){
edMelody=saved.melody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;if(m.f)n.f=m.f;return n;});
edBpm=saved.bpm||edPiece.bpm;
if(saved.timeSig)edTimeSig=saved.timeSig;
if(!edIsClassical)edBars=saved.bars||edPiece.bars||8;
}else if(edIsClassical){
edMelody=edPiece.melody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;if(m.f)n.f=m.f;return n;});
edBpm=edPiece.bpm;
}else{
edMelody=edPiece.p.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;return n;});
edBpm=edPiece.bpm;
edBars=edPiece.bars||8;
}
edSelected=-1;edScrollX=0;edSelRange=null;edClipboard=[];edPlayhead=-1;edPlayheadOffset=0;
document.getElementById("ed-title").textContent="Editing: "+edPiece.n+(edPiece.composer?" ("+edPiece.composer+")":"");
document.getElementById("ed-bpm").value=edBpm;
document.getElementById("ed-timesig").value=edTimeSig[0]+"/"+edTimeSig[1];
var modeLabel=edIsClassical?(edIsSingalong?"Singalong (full timeline)":"Classical (full timeline, conductor)"):"Pattern (4-beat cycle, repeats "+edBars+"x)";
document.getElementById("ed-mode-label").textContent=modeLabel;
document.getElementById("ed-code-panel").style.display="none";
document.getElementById("ed-import-panel").style.display="none";
show("sed");
// Need multiple frames for flex layout to settle after display:none→flex
requestAnimationFrame(function(){requestAnimationFrame(function(){edResize();});});
}
function closeEditor(){stopPreview();edPreviewing=false;edStopPlayhead();document.getElementById("ed-pv-btn").textContent="▶ Preview";show("sm");}
function edResize(){
var cv=getEdCanvas();
var cvRect=cv.getBoundingClientRect();
cv.width=cvRect.width||cv.parentElement.clientWidth||window.innerWidth;
// Height = remaining space from canvas top to window bottom
cv.height=Math.max(200, window.innerHeight-cvRect.top);
ED_LANE_H=Math.floor((cv.height-ED_HEAD-ED_KICK_H)/4);
edDraw();
}
window.addEventListener("resize",function(){if(document.getElementById("sed").classList.contains("active"))edResize();});
function edDraw(){
var cv=getEdCanvas(),ctx=getEdCtx(),W=cv.width,H=cv.height;
ctx.clearRect(0,0,W,H);
var pps=edPxPerUnit();
// Draw lane backgrounds
for(var i=0;i<4;i++){
ctx.fillStyle=i%2===0?"#0e0e1a":"#111122";
ctx.fillRect(0,ED_HEAD+i*ED_LANE_H,W,ED_LANE_H);
// Lane label
ctx.fillStyle=ED_COLORS[i];ctx.globalAlpha=0.3;
ctx.font="bold 12px sans-serif";
ctx.fillText(ED_NAMES[i],4,ED_HEAD+i*ED_LANE_H+15);
ctx.globalAlpha=1;
}
// Kick lane
ctx.fillStyle="#0a0a14";
ctx.fillRect(0,ED_HEAD+4*ED_LANE_H,W,ED_KICK_H);
ctx.fillStyle=ED_COLORS[4];ctx.globalAlpha=0.3;
ctx.font="bold 12px sans-serif";
ctx.fillText("Kick",4,ED_HEAD+4*ED_LANE_H+15);
ctx.globalAlpha=1;
// Measure header background
ctx.fillStyle="#0a0a12";
ctx.fillRect(0,0,W,ED_HEAD);
// Grid lines + measure numbers (time-signature aware)
var beatsPerMeasure=edTimeSig[0];
var beatUnit=edTimeSig[1]; // 4=quarter, 8=eighth
var beatSec=edQuarterSec(); // quarter note duration
var subBeatSec=beatSec/2; // eighth note grid
if(beatUnit===8) subBeatSec=beatSec/2; // for x/8, sub-grid is sixteenth
var measSec=edMeasureSec();
if(edIsClassical){
// Draw measure and beat lines
var startTime=edXToTime(0);
var endTime=edXToTime(W);
var startMeas=Math.max(0,Math.floor(startTime/measSec));
var endMeas=Math.ceil(endTime/measSec)+1;
for(var mi=startMeas;mi<=endMeas;mi++){
var measT=mi*measSec;
// Draw each beat within measure
var bCount=beatsPerMeasure;
var oneBeatSec=(beatUnit===8)?(beatSec/2):beatSec;
for(var bi=0;bi<bCount;bi++){
var bt2=measT+bi*oneBeatSec;
var bx=edTimeToX(bt2);
if(bx<-10||bx>W+10)continue;
var isMeasStart=bi===0;
ctx.strokeStyle=isMeasStart?"#555":"#282838";
ctx.lineWidth=isMeasStart?2:0.5;
ctx.beginPath();ctx.moveTo(bx,ED_HEAD);ctx.lineTo(bx,H);ctx.stroke();
// Sub-beat (eighth grid for x/4, sixteenth for x/8)
var subX=edTimeToX(bt2+oneBeatSec/2);
if(subX>0&&subX<W){
ctx.strokeStyle="#1a1a2a";ctx.lineWidth=0.3;
ctx.beginPath();ctx.moveTo(subX,ED_HEAD);ctx.lineTo(subX,H);ctx.stroke();
}
}
// Measure number and time sig in header
var mx=edTimeToX(measT);
if(mx>-20&&mx<W+20){
ctx.fillStyle="#888";ctx.font="bold 11px sans-serif";
ctx.fillText("M"+(mi+1),mx+3,ED_HEAD-5);
ctx.fillStyle="#555";ctx.font="10px sans-serif";
ctx.fillText(measT.toFixed(1)+"s",mx+2,H-4);
}
}
// Show time signature in header
ctx.fillStyle="#666";ctx.font="bold 10px sans-serif";
ctx.fillText(edTimeSig[0]+"/"+edTimeSig[1],edTimeToX(0)+1,ED_HEAD-5+40);
}else{
for(var sub=0;sub<=16;sub++){
var bt=sub*0.25;
var bx=edTimeToX(bt);
if(bx<0||bx>W)continue;
var isBeat=sub%4===0;
var isHalf=sub%2===0;
ctx.strokeStyle=isBeat?"#555":isHalf?"#333":"#1a1a2a";
ctx.lineWidth=isBeat?2:0.5;
ctx.beginPath();ctx.moveTo(bx,ED_HEAD);ctx.lineTo(bx,H);ctx.stroke();
if(isBeat){
ctx.fillStyle="#888";ctx.font="bold 11px sans-serif";
ctx.fillText("Beat "+(sub/4+1),bx+3,ED_HEAD-5);
ctx.fillText("Beat "+(sub/4+1),bx+3,H-4);
}
}
}
// START marker at t=0
var sx=edTimeToX(0);
if(sx>-2&&sx<W){
ctx.strokeStyle="#38a169";ctx.lineWidth=2;ctx.setLineDash([4,4]);
ctx.beginPath();ctx.moveTo(sx,0);ctx.lineTo(sx,H);ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle="#38a169";ctx.font="bold 10px sans-serif";
ctx.fillText("START",sx+3,12);
}
// Play-from marker (set by clicking in header)
if(edPlayFromMarker>=0){
var pmx=edTimeToX(edPlayFromMarker);
if(pmx>-2&&pmx<W){
ctx.strokeStyle="#ff0";ctx.lineWidth=2;ctx.setLineDash([2,2]);
ctx.beginPath();ctx.moveTo(pmx,0);ctx.lineTo(pmx,H);ctx.stroke();
ctx.setLineDash([]);
// Triangle marker in header
ctx.fillStyle="#ff0";
ctx.beginPath();ctx.moveTo(pmx-6,0);ctx.lineTo(pmx+6,0);ctx.lineTo(pmx,10);ctx.closePath();ctx.fill();
ctx.font="bold 9px sans-serif";
ctx.fillText("▶ "+edPlayFromMarker.toFixed(1)+"s",pmx+8,10);
}
}
// Header instruction text
ctx.fillStyle="#555";ctx.font="9px sans-serif";
var hdrTxt="Click header to set start position";
if(edPlayFromMarker>=0)hdrTxt+=" | Dbl-click or Preview to play from "+edPlayFromMarker.toFixed(1)+"s";
ctx.fillText(hdrTxt,edTimeToX(0)+50,ED_HEAD-3);
// Lane separator lines
for(i=1;i<5;i++){
var ly=ED_HEAD+i*ED_LANE_H;
ctx.strokeStyle="#333";ctx.lineWidth=1;
ctx.beginPath();ctx.moveTo(0,ly);ctx.lineTo(W,ly);ctx.stroke();
}
// Draw selection range highlight
if(edSelRange){
var rx1=edTimeToX(Math.min(edSelRange.start,edSelRange.end));
var rx2=edTimeToX(Math.max(edSelRange.start,edSelRange.end));
ctx.fillStyle="rgba(128,90,213,0.15)";
ctx.fillRect(rx1,ED_HEAD,rx2-rx1,H-ED_HEAD);
ctx.strokeStyle="#805ad5";ctx.lineWidth=1;ctx.setLineDash([3,3]);
ctx.beginPath();ctx.moveTo(rx1,ED_HEAD);ctx.lineTo(rx1,H);ctx.stroke();
ctx.beginPath();ctx.moveTo(rx2,ED_HEAD);ctx.lineTo(rx2,H);ctx.stroke();
ctx.setLineDash([]);
}
// Draw notes (with duration support + playhead highlight)
edMelody.forEach(function(m,idx){
var dur=m.d||0;
var noteW=dur>0?Math.max(ED_NOTE_W, dur*pps):ED_NOTE_W;
var nx=dur>0?edTimeToX(m.t):edTimeToX(m.t)-ED_NOTE_W/2;
var ny=edLaneToY(m.l)-ED_NOTE_H/2;
if(nx+noteW<0||nx>W)return;
var inRange=edSelRange&&!edSelecting&&m.t>=Math.min(edSelRange.start,edSelRange.end)&&m.t<=Math.max(edSelRange.start,edSelRange.end);
// Check if this note is "playing" (near the playhead)
var isPlaying=edPlayhead>=0&&m.t<=edPlayhead&&m.t+Math.max(dur,0.15)>edPlayhead;
ctx.fillStyle=isPlaying?"#fff":ED_COLORS[m.l]||"#888";
ctx.globalAlpha=idx===edSelected?1:isPlaying?1:inRange?0.95:0.8;
// Glow effect for playing notes
if(isPlaying){
ctx.shadowColor=ED_COLORS[m.l]||"#fff";
ctx.shadowBlur=12;
}
ctx.fillRect(nx,ny,noteW,ED_NOTE_H);
ctx.shadowBlur=0;
// Duration right-edge handle
if(dur>0){
ctx.fillStyle="rgba(255,255,255,0.3)";
ctx.fillRect(nx+noteW-4,ny,4,ED_NOTE_H);
}
if(isPlaying){
ctx.strokeStyle=ED_COLORS[m.l]||"#fff";ctx.lineWidth=2.5;
ctx.strokeRect(nx-2,ny-2,noteW+4,ED_NOTE_H+4);
} else if(idx===edSelected){
ctx.strokeStyle="#fff";ctx.lineWidth=2;
ctx.strokeRect(nx-1,ny-1,noteW+2,ED_NOTE_H+2);
} else if(inRange){
ctx.strokeStyle="#805ad5";ctx.lineWidth=1.5;
ctx.strokeRect(nx-1,ny-1,noteW+2,ED_NOTE_H+2);
}
// Musical notation symbol + pitch name on note
if(dur>0||edShowPitch){
ctx.textAlign="center";
if(dur>0){
var di=edDurInfo(dur);
ctx.font="bold 11px sans-serif";
ctx.fillStyle=isPlaying?"#000":"rgba(255,255,255,0.9)";
ctx.fillText(di.sym,nx+noteW/2,ny+ED_NOTE_H/2+1);
}
if(edShowPitch&&m.f){
var noteName=edFreqToNote(m.f);
ctx.font="bold 9px sans-serif";
ctx.fillStyle=isPlaying?"#000":"#ff0";
ctx.fillText(noteName,nx+noteW/2,ny+ED_NOTE_H+10);
}
ctx.textAlign="left";
}
// Note label (when Code/Labels toggled on, or for selected note)
if(edShowLabels||idx===edSelected){
ctx.font="9px monospace";
var label="t:"+m.t.toFixed(edIsClassical?2:2)+",l:"+m.l;
if(m.f)label+=" "+edFreqToNote(m.f);
if(dur>0){
var di2=edDurInfo(dur);
label+=","+di2.name+"("+dur.toFixed(2)+"s)";
}
var lx=nx;
var ly=ny-4;
if(ly<ED_HEAD+8)ly=ny+ED_NOTE_H+10+(edShowPitch&&m.f?10:0);
ctx.fillStyle=idx===edSelected?"#fff":"rgba(255,255,255,0.7)";
ctx.fillText(label,lx,ly);
}
ctx.globalAlpha=1;
});
// Duration / pattern end marker
if(edPiece){
var endTime=edIsClassical?(edPiece.duration||90):4; // 4 beats for pattern
var dx=edTimeToX(endTime);
if(dx>0&&dx<W){
ctx.strokeStyle="#e53e3e";ctx.lineWidth=2;ctx.setLineDash([4,4]);
ctx.beginPath();ctx.moveTo(dx,0);ctx.lineTo(dx,H);ctx.stroke();
ctx.setLineDash([]);
ctx.fillStyle="#e53e3e";ctx.font="10px sans-serif";
ctx.fillText(edIsClassical?"END":"LOOP (4 beats)",dx+3,12);
}
}
// Playhead line during preview
if(edPlayhead>=0){
var phx=edTimeToX(edPlayhead);
if(phx>=0&&phx<=W){
ctx.strokeStyle="#ff0";ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(phx,0);ctx.lineTo(phx,H);ctx.stroke();
// Triangle marker at top
ctx.fillStyle="#ff0";
ctx.beginPath();ctx.moveTo(phx,0);ctx.lineTo(phx-5,0);ctx.lineTo(phx,10);ctx.lineTo(phx+5,0);ctx.closePath();ctx.fill();
}
}
// Status
var statusTxt=edMelody.length+" notes";
if(edSelRange&&!edSelecting){
var lo=Math.min(edSelRange.start,edSelRange.end),hi=Math.max(edSelRange.start,edSelRange.end);
var cnt=edMelody.filter(function(m){return m.t>=lo&&m.t<=hi;}).length;
statusTxt+=" | Range: "+lo.toFixed(2)+"s"+hi.toFixed(2)+"s ("+cnt+" notes) | Ctrl+C copy, Ctrl+V paste, Del delete";
}else if(edSelected>=0){
var sm=edMelody[edSelected];
statusTxt+=" | #"+edSelected+" "+ED_NAMES[sm.l]+" t="+sm.t.toFixed(2)+"s";
if(sm.f)statusTxt+=" "+edFreqToNote(sm.f);
if(sm.d>0){var sdi=edDurInfo(sm.d);statusTxt+=" "+sdi.sym+sdi.name;}
statusTxt+=" | 1-5=dur, Shift+\u2191\u2193=pitch, \u2190\u2192=time, \u2191\u2193=lane";
}else{
statusTxt+=" | Click=add, Shift+drag=select range";
}
document.getElementById("ed-status").textContent=statusTxt;
edUpdateNotePanel();
}
function edHitTest(x,y){
var pps=edPxPerUnit();
var PAD_X=6,PAD_Y=6; // extra padding around notes for easier clicking
for(var i=edMelody.length-1;i>=0;i--){
var m=edMelody[i];
var dur=m.d||0;
var noteW=dur>0?Math.max(ED_NOTE_W,dur*pps):ED_NOTE_W;
var nx=dur>0?edTimeToX(m.t):edTimeToX(m.t)-ED_NOTE_W/2;
var ny=edLaneToY(m.l)-ED_NOTE_H/2;
if(x>=nx-PAD_X&&x<=nx+noteW+PAD_X&&y>=ny-PAD_Y&&y<=ny+ED_NOTE_H+PAD_Y)return i;
}
return-1;
}
function edIsOnRightEdge(x,idx){
var m=edMelody[idx],pps=edPxPerUnit();
var dur=m.d||0;
if(dur<=0)return false;
var noteW=Math.max(ED_NOTE_W,dur*pps);
var nx=edTimeToX(m.t);
var rightX=nx+noteW;
return Math.abs(x-rightX)<8;
}
// Mouse handlers
function edMouseDown(e){
var cv=getEdCanvas(),rect=cv.getBoundingClientRect();
var mx=e.clientX-rect.left,my=e.clientY-rect.top;
// Click on header/timeline area = set play-from marker
if(my<ED_HEAD){
var startT=edXToTime(mx);
if(startT<0)startT=0;
edPlayFromMarker=startT;
edDraw();
// Double-click starts playback immediately from this position
if(e.detail===2){
edPreview(startT);
}
return;
}
// Shift+click = range select
if(e.shiftKey){
var t=edXToTime(mx);
edSelRange={start:t,end:t};
edSelecting=true;
edSelected=-1;
edDraw();
return;
}
var hit=edHitTest(mx,my);
// If direct hit missed, check for nearest note in same lane within snap distance
if(hit<0){
var clickT=edXToTime(mx);
var clickL=edYToLane(my);
var snapDist=edIsClassical?(60/edBpm/2):0.3; // search radius in time units
var bestDist=snapDist,bestIdx=-1;
for(var ni=0;ni<edMelody.length;ni++){
var nm=edMelody[ni];
if(nm.l===clickL){
var d=Math.abs(nm.t-clickT);
if(d<bestDist){bestDist=d;bestIdx=ni;}
}
}
if(bestIdx>=0)hit=bestIdx;
}
if(hit>=0){
// Check if clicking right edge for resize
if(edIsOnRightEdge(mx,hit)){
edResizing=true;edResizeNote=hit;edSelected=hit;
edDraw();
return;
}
edSelected=hit;
edDragging=true;edDragNote=hit;
edDragOffX=mx-edTimeToX(edMelody[hit].t);
edDragOffY=my-edLaneToY(edMelody[hit].l);
edSelRange=null; // clear range on note click
edDraw();
}else{
// Double-click on empty space = add note (single click just deselects)
edSelRange=null;
if(e.detail>=2){
var t=edXToTime(mx);
var l=edYToLane(my);
if(l>=0&&l<=4&&t>=0){
var snap=edIsClassical?(60/edBpm/4):0.25;
t=Math.round(t/snap)*snap;
t=parseFloat(t.toFixed(4));
if(!edIsClassical)t=Math.max(0,Math.min(3.99,t));
edMelody.push({t:t,l:l});
edMelody.sort(function(a,b){return a.t-b.t;});
edSelected=edMelody.findIndex(function(m){return m.t===t&&m.l===l;});
edDraw();
}
}else{
edSelected=-1;
edDraw();
}
}
}
function edMouseMove(e){
var cv=getEdCanvas(),rect=cv.getBoundingClientRect();
var mx=e.clientX-rect.left,my=e.clientY-rect.top;
// Range selecting
if(edSelecting&&edSelRange){
edSelRange.end=edXToTime(mx);
edDraw();
return;
}
// Resizing note duration
if(edResizing&&edResizeNote>=0){
var m=edMelody[edResizeNote];
var snap=edIsClassical?(60/edBpm/4):0.25;
var newEnd=edXToTime(mx);
var dur=Math.max(ED_MIN_DUR, newEnd-m.t);
dur=Math.round(dur/snap)*snap;
dur=Math.max(snap, dur);
m.d=parseFloat(dur.toFixed(4));
edDraw();
return;
}
// Dragging note position
if(!edDragging||edDragNote<0)return;
var t=edXToTime(mx-edDragOffX);
var l=edYToLane(my);
var snap2=60/edBpm/4;
t=Math.max(0,Math.round(t/snap2)*snap2);
if(l>=0&&l<=4){
edMelody[edDragNote].t=t;
edMelody[edDragNote].l=l;
edSelected=edDragNote;
edDraw();
}
// Update cursor for right-edge hover
var hit2=edHitTest(mx,my);
cv.style.cursor=(hit2>=0&&edIsOnRightEdge(mx,hit2))?"ew-resize":(hit2>=0?"grab":"crosshair");
}
function edMouseUp(){
if(edSelecting){
edSelecting=false;
// If range is too small, clear it
if(edSelRange&&Math.abs(edSelRange.end-edSelRange.start)<0.01)edSelRange=null;
edDraw();
return;
}
if(edResizing){
edResizing=false;edResizeNote=-1;
edDraw();
return;
}
if(edDragging){
edDragging=false;edDragNote=-1;
edMelody.sort(function(a,b){return a.t-b.t;});
edDraw();
}
}
function edWheel(e){
e.preventDefault();
edScrollX+=e.deltaX||e.deltaY;
if(edScrollX<0)edScrollX=0;
edDraw();
}
function edKeyDown(e){
if(!document.getElementById("sed").classList.contains("active"))return;
// Don't intercept keys when user is typing in an input or select (e.g. pitch input)
var tag=document.activeElement&&document.activeElement.tagName;
if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT")return;
// Ctrl+C — copy selected range or selected note
if((e.ctrlKey||e.metaKey)&&e.key==="c"){
if(edSelRange){
var lo=Math.min(edSelRange.start,edSelRange.end);
var hi=Math.max(edSelRange.start,edSelRange.end);
edClipboard=edMelody.filter(function(m){return m.t>=lo&&m.t<=hi;})
.map(function(m){return{t:m.t-lo,l:m.l,d:m.d||0};});
document.getElementById("ed-status").textContent="Copied "+edClipboard.length+" notes";
}else if(edSelected>=0){
var m=edMelody[edSelected];
edClipboard=[{t:0,l:m.l,d:m.d||0}];
document.getElementById("ed-status").textContent="Copied 1 note";
}
e.preventDefault();return;
}
// Ctrl+V — paste at end of current notes or at selection start
if((e.ctrlKey||e.metaKey)&&e.key==="v"){
if(edClipboard.length===0)return;
// Paste after the last note, or at end of selection range
var pasteAt;
if(edSelRange){
pasteAt=Math.max(edSelRange.start,edSelRange.end);
}else{
var maxT=0;
edMelody.forEach(function(m){var end=m.t+(m.d||0);if(end>maxT)maxT=end;if(m.t>maxT)maxT=m.t;});
var snap=edIsClassical?(60/edBpm/4):0.25;
pasteAt=Math.ceil(maxT/snap)*snap; // snap to next grid line
}
edClipboard.forEach(function(c){
var note={t:parseFloat((pasteAt+c.t).toFixed(4)),l:c.l};
if(c.d>0)note.d=c.d;
edMelody.push(note);
});
edMelody.sort(function(a,b){return a.t-b.t;});
edSelected=-1;edSelRange=null;
edDraw();
document.getElementById("ed-status").textContent="Pasted "+edClipboard.length+" notes at t="+pasteAt.toFixed(2);
e.preventDefault();return;
}
// Ctrl+A — select all (set range over all notes)
if((e.ctrlKey||e.metaKey)&&e.key==="a"&&edMelody.length>0){
var first=edMelody[0].t;
var last=edMelody[edMelody.length-1].t+(edMelody[edMelody.length-1].d||0);
edSelRange={start:first,end:last};
edSelected=-1;
edDraw();
e.preventDefault();return;
}
// Escape — clear selection range and play-from marker
if(e.key==="Escape"){
edSelRange=null;edSelected=-1;edPlayFromMarker=-1;edDraw();e.preventDefault();return;
}
if((e.key==="Delete"||e.key==="Backspace")&&edSelected>=0){
edMelody.splice(edSelected,1);
edSelected=-1;
edDraw();
e.preventDefault();
}
// Delete selected range
if((e.key==="Delete"||e.key==="Backspace")&&edSelRange&&edSelected<0){
var lo2=Math.min(edSelRange.start,edSelRange.end);
var hi2=Math.max(edSelRange.start,edSelRange.end);
var before=edMelody.length;
edMelody=edMelody.filter(function(m){return m.t<lo2||m.t>hi2;});
edSelRange=null;
edDraw();
document.getElementById("ed-status").textContent="Deleted "+(before-edMelody.length)+" notes";
e.preventDefault();
}
if(e.key==="ArrowLeft"&&edSelected>=0){
var snap=edIsClassical?(60/edBpm/4):0.25;
edMelody[edSelected].t=Math.max(0,parseFloat((edMelody[edSelected].t-snap).toFixed(4)));
edMelody.sort(function(a,b){return a.t-b.t;});
edDraw();e.preventDefault();
}
if(e.key==="ArrowRight"&&edSelected>=0){
var snap2=edIsClassical?(60/edBpm/4):0.25;
var maxT2=edIsClassical?9999:3.99;
edMelody[edSelected].t=Math.min(maxT2,parseFloat((edMelody[edSelected].t+snap2).toFixed(4)));
edMelody.sort(function(a,b){return a.t-b.t;});
edDraw();e.preventDefault();
}
if(e.key==="ArrowUp"&&edSelected>=0){
if(e.shiftKey&&e.ctrlKey&&edMelody[edSelected].f){
// Ctrl+Shift+Up = pitch up one octave
edMelody[edSelected].f=parseFloat((edMelody[edSelected].f*2).toFixed(2));
}else if(e.shiftKey&&edMelody[edSelected].f){
// Shift+Up = pitch up one semitone
edMelody[edSelected].f=parseFloat((edMelody[edSelected].f*Math.pow(2,1/12)).toFixed(2));
}else if(!e.shiftKey){
edMelody[edSelected].l=Math.max(0,edMelody[edSelected].l-1);
}
edDraw();e.preventDefault();
}
if(e.key==="ArrowDown"&&edSelected>=0){
if(e.shiftKey&&e.ctrlKey&&edMelody[edSelected].f){
// Ctrl+Shift+Down = pitch down one octave
edMelody[edSelected].f=parseFloat((edMelody[edSelected].f/2).toFixed(2));
}else if(e.shiftKey&&edMelody[edSelected].f){
// Shift+Down = pitch down one semitone
edMelody[edSelected].f=parseFloat((edMelody[edSelected].f/Math.pow(2,1/12)).toFixed(2));
}else if(!e.shiftKey){
edMelody[edSelected].l=Math.min(4,edMelody[edSelected].l+1);
}
edDraw();e.preventDefault();
}
// Number keys 1-5: set note value on selected note
// 1=whole, 2=half, 3=quarter, 4=eighth, 5=sixteenth
if(e.key>="1"&&e.key<="5"&&edSelected>=0&&!e.ctrlKey&&!e.metaKey){
var q=edQuarterSec();
var durMap={"1":q*4,"2":q*2,"3":q,"4":q/2,"5":q/4};
edMelody[edSelected].d=parseFloat(durMap[e.key].toFixed(4));
edDraw();e.preventDefault();
return;
}
// 6=dotted half, 7=dotted quarter, 8=dotted eighth
if(e.key>="6"&&e.key<="8"&&edSelected>=0&&!e.ctrlKey&&!e.metaKey){
var q2=edQuarterSec();
var durMap2={"6":q2*3,"7":q2*1.5,"8":q2*0.75};
edMelody[edSelected].d=parseFloat(durMap2[e.key].toFixed(4));
edDraw();e.preventDefault();
return;
}
// D key — toggle/set duration on selected note
if(e.key==="d"&&edSelected>=0&&!e.ctrlKey&&!e.metaKey){
var n=edMelody[edSelected];
var snapD=edIsClassical?(60/edBpm/4):0.25;
if(!n.d||n.d<=0){
n.d=snapD; // add default duration
}else{
n.d=parseFloat((n.d+snapD).toFixed(4)); // increase by one grid unit
}
edDraw();e.preventDefault();
}
// Shift+D — remove duration (make instant)
if(e.key==="D"&&edSelected>=0){
delete edMelody[edSelected].d;
edDraw();e.preventDefault();
}
}
// Setup editor canvas events
(function(){
var cv=document.getElementById("ed-canvas");
cv.addEventListener("mousedown",edMouseDown);
cv.addEventListener("mousemove",edMouseMove);
cv.addEventListener("mouseup",edMouseUp);
cv.addEventListener("mouseleave",edMouseUp);
cv.addEventListener("wheel",edWheel,{passive:false});
document.addEventListener("keydown",edKeyDown);
// Auto-resize canvas when code panel textarea is resized
var codeTA=document.getElementById("ed-code");
if(codeTA&&typeof ResizeObserver!=="undefined"){
new ResizeObserver(function(){if(document.getElementById("sed").classList.contains("active"))edResize();}).observe(codeTA);
}
})();
function edUpdateBpm(){edBpm=parseInt(document.getElementById("ed-bpm").value)||120;edDraw();}
function edUpdateTimeSig(){
var val=document.getElementById("ed-timesig").value;
var parts=val.split("/");
edTimeSig=[parseInt(parts[0])||4,parseInt(parts[1])||4];
edDraw();
}
// Get quarter note duration in seconds at current BPM
function edQuarterSec(){return 60/edBpm;}
// Get measure duration in seconds based on time signature
function edMeasureSec(){
var q=edQuarterSec();
// For x/4 time: beats * quarter note duration
// For x/8 time: beats * eighth note duration
if(edTimeSig[1]===8) return edTimeSig[0]*(q/2);
return edTimeSig[0]*q;
}
// Duration name and symbol for a note duration in seconds
function edDurInfo(durSec){
var q=edQuarterSec();
var ratio=durSec/q;
// Map ratio to note value
if(ratio>=3.5) return {sym:"\uD834\uDD5D",name:"whole",val:4};
if(ratio>=2.5) return {sym:"\uD834\uDD5E.",name:"dotted half",val:3};
if(ratio>=1.8) return {sym:"\uD834\uDD5E",name:"half",val:2};
if(ratio>=1.3) return {sym:"\u2669.",name:"dotted quarter",val:1.5};
if(ratio>=0.8) return {sym:"\u2669",name:"quarter",val:1};
if(ratio>=0.6) return {sym:"\u266A.",name:"dotted eighth",val:0.75};
if(ratio>=0.4) return {sym:"\u266A",name:"eighth",val:0.5};
if(ratio>=0.2) return {sym:"\u266C",name:"sixteenth",val:0.25};
return {sym:"\u266C\u266C",name:"32nd",val:0.125};
}
function edStopPlayhead(){
edPlayhead=-1;
if(edPlayheadRAF)cancelAnimationFrame(edPlayheadRAF);
edPlayheadRAF=0;
edDraw();
}
function edStartPlayhead(totalDur,offset){
edPlayheadStart=performance.now();
var dur=totalDur;
var off=offset||0;
function tick(){
if(!edPreviewing){edStopPlayhead();return;}
var elapsed=(performance.now()-edPlayheadStart)/1000;
if(elapsed>dur){
edPreviewing=false;
document.getElementById("ed-pv-btn").textContent="▶ Preview";
edStopPlayhead();
return;
}
edPlayhead=elapsed+off; // playhead position in absolute piece time
// Auto-scroll to keep playhead visible
var cv=getEdCanvas();
var phx=edTimeToX(edPlayhead);
if(phx>cv.width*0.75){
edScrollX+=(phx-cv.width*0.5);
}
edDraw();
edPlayheadRAF=requestAnimationFrame(tick);
}
edPlayheadRAF=requestAnimationFrame(tick);
}
function edPreview(startFrom){
if(edPreviewing){stopPreview();edPreviewing=false;edStopPlayhead();document.getElementById("ed-pv-btn").textContent="▶ Preview";return;}
var ac=getAc(true);ac.resume();
var piece=edPiece;
var offset=typeof startFrom==="number"?startFrom:(edPlayFromMarker>=0?edPlayFromMarker:0);
edPlayheadOffset=offset;
if(offset===0)edScrollX=0; // reset scroll only when starting from beginning
if(edIsClassical){
if(!edIsSingalong){
// Classical: play background orchestration (conductor mode)
if(piece.bg)for(var i=0;i<piece.bg.length;i++){
var n=piece.bg[i];
if(n.t<offset)continue; // skip notes before start position
playSynth(ac,n.f,ac.currentTime+0.1+(n.t-offset),n.d,n.i,n.v*1.2,previewNodes);
}
}
// Play melody notes (what the player sees/hits)
edMelody.forEach(function(m){
if(m.t<offset)return; // skip notes before start position
var freq=m.f||[329,392,440,349,220][m.l]||329;
var dur=m.d||0.1;
var inst=edIsSingalong?'strings':'woodwind';
var vol=edIsSingalong?0.6:0.5;
playSynth(ac,freq,ac.currentTime+0.1+(m.t-offset),Math.min(dur,2),'woodwind',vol,previewNodes);
});
var totalDur=(piece.duration||90)-offset;
edPreviewing=true;
document.getElementById("ed-pv-btn").textContent="⏹ Stop";
edStartPlayhead(totalDur,offset);
}else{
// Regular rhythm: play drum sounds for each note in pattern, looped over bars
var bs=60/edBpm;
var bars=edBars||8;
var drumFreqs=[329,392,440,349,220]; // red,yellow,blue,green,kick
var drumInst=['strings','woodwind','woodwind','strings','bass'];
for(var bar=0;bar<bars;bar++){
edMelody.forEach(function(m){
var t=ac.currentTime+0.1+(bar*4+m.t)*bs;
var freq=drumFreqs[m.l]||329;
playSynth(ac,freq,t,0.08,drumInst[m.l]||'woodwind',0.6,previewNodes);
});
}
var totalDur=bars*4*bs;
edPreviewing=true;
document.getElementById("ed-pv-btn").textContent="⏹ Stop";
edPlayheadStart=performance.now();
var beatDur=bs;
function patternTick(){
if(!edPreviewing){edStopPlayhead();return;}
var elapsed=(performance.now()-edPlayheadStart)/1000;
if(elapsed>totalDur){
edPreviewing=false;
document.getElementById("ed-pv-btn").textContent="▶ Preview";
edStopPlayhead();
return;
}
var beatPos=(elapsed/beatDur)%4;
edPlayhead=beatPos;
edDraw();
edPlayheadRAF=requestAnimationFrame(patternTick);
}
edPlayheadRAF=requestAnimationFrame(patternTick);
}
}
// Save/load edits to localStorage
var ED_STORAGE_KEY="drgame_edits";
function edGetAllSaved(){
var local={};try{local=JSON.parse(localStorage.getItem(ED_STORAGE_KEY))||{};}catch(e){}
// Merge baked edits as fallback — localStorage overrides baked
if(typeof BAKED_EDITS==='object'&&BAKED_EDITS){
for(var k in BAKED_EDITS)if(!local[k])local[k]=BAKED_EDITS[k];
}
return local;
}
function edLoadSaved(name){var all=edGetAllSaved();return all[name]||null;}
function edSave(){
var all=edGetAllSaved();
var entry={melody:edMelody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;if(m.f)n.f=m.f;return n;}),bpm:edBpm,isClassical:edIsClassical,timeSig:edTimeSig};
if(!edIsClassical)entry.bars=edBars;
all[edPiece.n]=entry;
try{localStorage.setItem(ED_STORAGE_KEY,JSON.stringify(all));}catch(e){}
document.getElementById("ed-status").textContent="Saved! ("+edMelody.length+" notes)";
}
function edReset(){
if(!confirm("Reset to original? This discards your edits."))return;
var all=edGetAllSaved();delete all[edPiece.n];
try{localStorage.setItem(ED_STORAGE_KEY,JSON.stringify(all));}catch(e){}
if(edIsClassical){
edMelody=edPiece.melody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;if(m.f)n.f=m.f;return n;});
}else{
edMelody=edPiece.p.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;if(m.f)n.f=m.f;return n;});
edBars=edPiece.bars||8;
}
edBpm=edPiece.bpm;
document.getElementById("ed-bpm").value=edBpm;
edSelected=-1;edDraw();
}
function edExport(){
var laneNames=["Red","Yellow","Blue","Green","Kick"];
var exportMelody=edMelody.map(function(m){
var n={t:m.t,l:m.l,lane:laneNames[m.l]};
if(m.f){n.f=m.f;n.pitch=edFreqToNote(m.f);}
if(m.d>0){n.d=m.d;var di=edDurInfo(m.d);n.duration=di.name;}
return n;
});
var data=JSON.stringify({name:edPiece.n,bpm:edBpm,timeSig:edTimeSig[0]+"/"+edTimeSig[1],type:edIsClassical?"classical":"pattern",melody:exportMelody},null,2);
navigator.clipboard.writeText(data).then(function(){
document.getElementById("ed-status").textContent="Copied to clipboard!";
}).catch(function(){
prompt("Copy this JSON:",data);
});
}
// Code view toggle — shows labels on canvas + code panel
function edTogglePitch(){
edShowPitch=!edShowPitch;
var btn=document.getElementById("ed-pitch-btn");
btn.style.background=edShowPitch?"#38a169":"";
edDraw();
}
function edToggleCode(){
edShowLabels=!edShowLabels;
var btn=document.getElementById("ed-code-btn");
btn.style.background=edShowLabels?"#3182ce":"";
var panel=document.getElementById("ed-code-panel");
if(edShowLabels){
panel.style.display="block";
edUpdateCode();
}else{
panel.style.display="none";
}
setTimeout(function(){edResize();edDraw();},50);
}
function edUpdateCode(){
var ta=document.getElementById("ed-code");
var laneNames=["Red","Yel","Blu","Grn","Kick"];
if(edIsClassical){
var lines=edMelody.map(function(m){
var s=" {t:"+m.t.toFixed(3)+", l:"+m.l;
if(m.f)s+=", f:"+m.f.toFixed(2);
if(m.d>0)s+=", d:"+m.d.toFixed(3);
s+="}";
// Human-readable comment
var comment=" // "+laneNames[m.l];
if(m.f)comment+=" "+edFreqToNote(m.f);
if(m.d>0){var di=edDurInfo(m.d);comment+=" "+di.sym+" "+di.name;}
return s+","+comment;
});
ta.value="// "+edPiece.n+" — melody ("+edMelody.length+" notes)\n"+
"// BPM: "+edBpm+", Time Sig: "+edTimeSig[0]+"/"+edTimeSig[1]+", Duration: "+(edPiece.duration||"?")+"s\n"+
"// Lanes: 0=Red 1=Yellow 2=Blue 3=Green 4=Kick\n"+
"// f=frequency(Hz) → pitch name, d=duration(s) → note value\n"+
"// Pitch: edit f values or use note names in Apply Code (e.g. f:\"C4\" → converted)\n"+
"melody: [\n"+lines.join("\n")+"\n]";
}else{
var lines2=edMelody.map(function(m){
var s=" {t:"+m.t+", l:"+m.l;
if(m.d>0)s+=", d:"+m.d;
s+="}";
var comment=" // "+laneNames[m.l];
if(m.d>0){var di=edDurInfo(m.d);comment+=" "+di.sym+" "+di.name;}
return s+","+comment;
});
ta.value="// "+edPiece.n+" — pattern ("+edMelody.length+" notes, repeats "+edBars+"x)\n"+
"// BPM: "+edBpm+", Bars: "+edBars+"\n"+
"// t = beat offset (0-4), Lanes: 0=Red 1=Yellow 2=Blue 3=Green 4=Kick | d=duration\n"+
"p: [\n"+lines2.join("\n")+"\n]";
}
}
function edApplyCode(){
var ta=document.getElementById("ed-code");
var code=ta.value;
// Extract the array from the code text
var match=code.match(/\[\s*([\s\S]*)\s*\]/);
if(!match){document.getElementById("ed-status").textContent="Could not find [...] array in code";return;}
try{
// Parse the array — handle JS object notation {t:1, l:2} as JSON
var arrStr="["+match[1].replace(/(\w+)\s*:/g,'"$1":')+"]";
var arr=JSON.parse(arrStr);
edMelody=arr.filter(function(m){return typeof m.t==="number"&&typeof m.l==="number";})
.map(function(m){
var n={t:m.t,l:m.l};
if(m.d>0)n.d=m.d;
// Handle f as number (Hz) or string (note name like "C4")
if(typeof m.f==="string"){
var freq=edNoteToFreq(m.f);
if(freq>0)n.f=freq;
}else if(typeof m.f==="number"&&m.f>0){
n.f=m.f;
}
return n;
});
edMelody.sort(function(a,b){return a.t-b.t;});
edSelected=-1;edDraw();
document.getElementById("ed-status").textContent="Applied! "+edMelody.length+" notes loaded from code.";
}catch(e){
document.getElementById("ed-status").textContent="Parse error: "+e.message;
}
}
// Import panel
function edToggleImport(){
var panel=document.getElementById("ed-import-panel");
panel.style.display=panel.style.display==="none"?"block":"none";
setTimeout(edResize,50);
}
function edDoImport(){
var text=document.getElementById("ed-import-text").value.trim();
if(!text){document.getElementById("ed-import-status").textContent="Paste some data first";return;}
try{
var data=JSON.parse(text);
// Accept array of {t,l} or object with melody property
var arr=Array.isArray(data)?data:(data.melody||data.p||[]);
if(!Array.isArray(arr)||arr.length===0){document.getElementById("ed-import-status").textContent="No notes found";return;}
edMelody=arr.filter(function(m){return typeof m.t==="number"&&typeof m.l==="number";})
.map(function(m){
var n={t:m.t,l:m.l};
if(m.d>0)n.d=m.d;
// Handle pitch as note name string or "pitch" field from export
if(typeof m.f==="number"&&m.f>0){n.f=m.f;}
else if(typeof m.f==="string"){var freq=edNoteToFreq(m.f);if(freq>0)n.f=freq;}
else if(typeof m.pitch==="string"){var freq2=edNoteToFreq(m.pitch);if(freq2>0)n.f=freq2;}
return n;
});
edMelody.sort(function(a,b){return a.t-b.t;});
if(data.bpm)edBpm=data.bpm;
document.getElementById("ed-bpm").value=edBpm;
edSelected=-1;edDraw();edUpdateCode();
document.getElementById("ed-import-status").textContent="Imported "+edMelody.length+" notes!";
}catch(e){
document.getElementById("ed-import-status").textContent="JSON parse error: "+e.message;
}
}
function edImportMIDI(){document.getElementById("ed-midi-file").click();}
function edHandleFile(e){
var file=e.target.files[0];if(!file)return;
var reader=new FileReader();
if(file.name.endsWith(".json")){
reader.onload=function(ev){
document.getElementById("ed-import-text").value=ev.target.result;
edDoImport();
};
reader.readAsText(file);
}else{
document.getElementById("ed-import-status").textContent="MIDI parsing not yet supported — convert to JSON first (use AI or online converter)";
}
e.target.value="";
}
// Download all edits as a JSON file (for transferring between machines)
function edDownloadAllEdits(){
var all=edGetAllSaved();
var keys=Object.keys(all);
if(keys.length===0){document.getElementById("ed-status").textContent="No edits to download";return;}
var json=JSON.stringify(all,null,2);
var blob=new Blob([json],{type:"application/json"});
var a=document.createElement("a");
a.href=URL.createObjectURL(blob);
a.download="drum-game-edits.json";
a.click();URL.revokeObjectURL(a.href);
document.getElementById("ed-status").textContent="Downloaded "+keys.length+" edit(s)";
}
// Load edits from a JSON file into localStorage
function edLoadEditsFile(e){
var file=e.target.files[0];if(!file)return;
var reader=new FileReader();
reader.onload=function(ev){
try{
var imported=JSON.parse(ev.target.result);
var all={}; try{all=JSON.parse(localStorage.getItem(ED_STORAGE_KEY))||{};}catch(x){}
var count=0;
for(var k in imported){all[k]=imported[k];count++;}
localStorage.setItem(ED_STORAGE_KEY,JSON.stringify(all));
document.getElementById("ed-status").textContent="Loaded "+count+" edit(s) from file";
// Refresh current editor view if open
if(edPiece){var saved=edLoadSaved(edPiece.n);if(saved){edMelody=saved.melody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;return n;});edBpm=saved.bpm||edBpm;document.getElementById("ed-bpm").value=edBpm;edDraw();}}
}catch(err){document.getElementById("ed-status").textContent="Error: "+err.message;}
};
reader.readAsText(file);
e.target.value="";
}
// Bake all edits into HTML — downloads a copy of index.html with edits embedded
function edBakeIntoHtml(){
var all=edGetAllSaved();
var keys=Object.keys(all);
if(keys.length===0){document.getElementById("ed-status").textContent="No edits to bake";return;}
// Get the current page HTML
var html=document.documentElement.outerHTML;
// Replace the BAKED_EDITS variable between the markers
var bakedJson=JSON.stringify(all);
html=html.replace(
/\/\/ ── BAKED EDITS[^\n]*\nvar BAKED_EDITS\s*=\s*[^;]*;\n\/\/ ── END BAKED EDITS ──/,
"// ── BAKED EDITS (auto-populated by \"Bake into HTML\") ──\nvar BAKED_EDITS = "+bakedJson+";\n// ── END BAKED EDITS ──"
);
var blob=new Blob(["<!DOCTYPE html>\n"+document.documentElement.outerHTML],{type:"text/html"});
var a=document.createElement("a");
a.href=URL.createObjectURL(blob);
a.download="index.html";
a.click();URL.revokeObjectURL(a.href);
document.getElementById("ed-status").textContent="Downloaded index.html with "+keys.length+" baked edit(s)";
}
// Show/hide edit button — works for all genres
function updateEditBtn(){
var pieces=SYNTH_GENRES[curGenre]||GENRES[curGenre];
var btn=document.getElementById("edit-btn");
btn.style.display=(pieces&&pieces[curIdx])?"":"none";
}
// ── 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=SYNTH_GENRES[curGenre]?SYNTH_GENRES[curGenre][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;gameEndTime=null;}
function buildNotes(r){
var saved=edLoadSaved(r.n);
var pattern=saved?saved.melody:r.p;
var bpm=saved?saved.bpm:r.bpm;
const bs=60/bpm/gameSpeed,bars=r.bars||8,res=[];
for(let bar=0;bar<bars;bar++)
for(const p of pattern)
if(activePads[p.l])
res.push({time:(bar*4+p.t)*bs,lane:p.l,hit:false,missed:false});
var sorted=res.sort((a,b)=>a.time-b.time);
return simplifyNotes(sorted);
}
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/gameSpeed,bars=r.bars||8;
gameStartTime=ac.currentTime;
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;
gameEndTime=totalSec;
setTimeout(()=>{if(playing)endTurn();},totalSec*1000+5000);
}
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 isClassical=gameRhythm&&gameRhythm.isClassical;
var isClassicalOrch=isClassical&&classicalHitMode==='orchestra';
// Drum hit sound — quieter in classical so orchestra background is audible
if(!isClassicalOrch){
playDrumAt(getAc(),lane,getAc().currentTime,isClassical?3: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.35,'strings',0.7,[]);
playSynth(ac,best.freq,ct,0.35,'brass',0.4,[]);
} else if(isClassicalOrch){
playDrumAt(getAc(),lane,getAc().currentTime,3,[]);
}
showMsg(bestD<0.05?"PERFECT!":"GOOD",bestD<0.05?"#68d391":"#d69e2e");
} else {
// Miss — quiet drum thud as feedback
if(isClassicalOrch)playDrumAt(getAc(),lane,getAc().currentTime,2,[]);
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);
// Pad lanes (everything except kick) in columns; kick centered at bottom
const padLanes=LANES.map((l,i)=>i).filter(i=>i!==4&&activePads[i]);
const hasKick=activePads[4];
const PAD_H=hasKick?Math.floor(H*0.78):H;
const KICK_H=H-PAD_H;
const nLanes=padLanes.length;
const LANE_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={};
padLanes.forEach((li,col)=>{laneX[li]=col*LANE_W+LANE_W/2;});
// Draw pad lanes
padLanes.forEach((li,col)=>{
const x=col*LANE_W;
cx.fillStyle=laneFlash[li]?LANES[li].color+"33":"#0d0d16";
cx.fillRect(x,0,LANE_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+LANE_W/2,PAD_H-20);
cx.fillStyle="#999";cx.font="10px sans-serif";
var keyLabel=LANES[li].key===" "?"SPACE":LANES[li].key.toUpperCase();
cx.fillText(keyLabel,x+LANE_W/2,PAD_H-7);
});
// Kick zone — centered at bottom, notes fall from top of screen
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;
// Pad 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 — pad lanes
padLanes.forEach(li=>{
const x=laneX[li],r=Math.min(LANE_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();}
}
// Auto-end when all notes resolved and past expected end time
if(playing&&mode==="builtin"&&gameEndTime!=null&&now>=gameEndTime){
if(notes.length===0||notes.every(n=>n.hit||n.missed))endTurn();
}
// Notes — pad lanes fall in columns, kick falls centered from top
if(mode==="builtin"&&now>-9000){
for(const n of notes){
if(n.hit)continue;
const dt=n.time-now;
if(n.lane===4){
// Kick — falls from top of screen, centered, landing on KICK_HIT_Y
if(!hasKick)continue;
const ky=KICK_HIT_Y-dt*SPEED;
if(ky<-40||ky>H+40){
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();}}
} else {
// Pad lanes — fall in columns
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(LANE_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();}}
}
}
}
}
draw();
// ── DRUM SOUNDS ────────────────────────────────────────
function playDrumAt(ac,lane,t,vol,arr){
vol=vol*masterVolume;
const type=LANES[lane].type;
function mk(n){if(arr)arr.push(n);}
if(type==="kick"||type==="bass"){
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 if(type==="crash"){
// Crash cymbal — long noise burst with resonance
const sz=ac.sampleRate*0.4,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="bandpass";f.frequency.value=4000;f.Q.value=0.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(vol*.6,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.4);
s.start(t);s.stop(t+0.45);mk(s);
} else if(type==="ride"){
// Ride cymbal — shorter metallic ping
const sz=ac.sampleRate*0.15,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="bandpass";f.frequency.value=6000;f.Q.value=1.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
g.gain.setValueAtTime(vol*.45,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
s.start(t);s.stop(t+0.15);mk(s);
// Add a subtle pitched ping
const o=ac.createOscillator(),g2=ac.createGain();
o.type="triangle";o.connect(g2);g2.connect(ac.destination);o.frequency.value=800;
g2.gain.setValueAtTime(vol*.15,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.08);
o.start(t);o.stop(t+0.1);mk(o);
} else { // tom / tom2 / tom3
const freq=type==="tom"?160:type==="tom2"?120:90;
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);
}
}
// ── PAUSE ─────────────────────────────────────────────
function togglePause(){
if(!playing&&!paused)return;
var ac=getAc();
if(!paused){
paused=true;playing=false;pauseTime=ac.currentTime;
ac.suspend();
document.getElementById("pause-overlay").classList.add("show");
} else {
paused=false;playing=true;
ac.resume();
document.getElementById("pause-overlay").classList.remove("show");
}
}
// ── REMAP ─────────────────────────────────────────────
var remapLanes=LANES.map(function(ln,i){return{l:i,name:ln.label.toUpperCase(),color:ln.color};});
function startRemap(){
remapping=true;remapStep=0;remapResult={};
document.getElementById("remap-overlay").classList.add("show");
showRemapStep();
}
function cancelRemap(){
remapping=false;document.getElementById("remap-overlay").classList.remove("show");
}
function showRemapStep(){
if(remapStep>=remapLanes.length){
// Done — save mapping
gpMap={};
for(var k in remapResult)gpMap[k]=remapResult[k];
localStorage.setItem("drgameGpMap",JSON.stringify(gpMap));
remapping=false;
document.getElementById("remap-overlay").classList.remove("show");
document.getElementById("remap-prompt").textContent="";
return;
}
var rl=remapLanes[remapStep];
document.getElementById("remap-prompt").innerHTML="Hit your <span style='color:"+rl.color+";font-size:24px'>"+rl.name+"</span> pad";
document.getElementById("remap-status").textContent="Step "+(remapStep+1)+" of "+remapLanes.length+(remapStep===4?" (or press foot pedal)":"");
}
var remapCooldown=false;
function handleRemapButton(bi){
if(!remapping||remapCooldown)return false;
remapResult[bi]=remapLanes[remapStep].l;
remapStep++;
remapCooldown=true;
setTimeout(function(){remapCooldown=false;},300);
showRemapStep();
return true;
}
// ── KEYBOARD REMAP ────────────────────────────────────
var keyRemapping=false,keyRemapStep=0,keyRemapResult={};
var customKeyMap=JSON.parse(localStorage.getItem("drgameKeyMap")||"null");
// Apply saved key mappings on load
if(customKeyMap){
LANES.forEach(function(ln,i){if(customKeyMap[i]!==undefined)ln.key=customKeyMap[i];});
}
function startKeyRemap(){
keyRemapping=true;keyRemapStep=0;keyRemapResult={};
document.getElementById("keyremap-overlay").classList.add("show");
showKeyRemapStep();
}
function cancelKeyRemap(){
keyRemapping=false;document.getElementById("keyremap-overlay").classList.remove("show");
}
function showKeyRemapStep(){
if(keyRemapStep>=LANES.length){
// Done — apply and save
LANES.forEach(function(ln,i){if(keyRemapResult[i]!==undefined)ln.key=keyRemapResult[i];});
var map={};LANES.forEach(function(ln,i){map[i]=ln.key;});
localStorage.setItem("drgameKeyMap",JSON.stringify(map));
customKeyMap=map;
keyRemapping=false;
document.getElementById("keyremap-overlay").classList.remove("show");
buildPadToggles();
return;
}
var ln=LANES[keyRemapStep];
document.getElementById("keyremap-prompt").innerHTML="Press key for <span style='color:"+ln.color+";font-size:24px'>"+ln.label.toUpperCase()+"</span>";
document.getElementById("keyremap-status").textContent="Step "+(keyRemapStep+1)+" of "+LANES.length+" (press any key)";
}
var keyRemapCooldown=false;
window.addEventListener("keydown",function(e){
var aeTag=document.activeElement&&document.activeElement.tagName;
if(aeTag==="INPUT"||aeTag==="TEXTAREA"||aeTag==="SELECT")return;
if(keyRemapping){
if(e.key==="Escape"){cancelKeyRemap();e.preventDefault();return;}
if(keyRemapCooldown)return;
e.preventDefault();
keyRemapResult[keyRemapStep]=e.key===" "?" ":e.key.toLowerCase();
keyRemapStep++;
keyRemapCooldown=true;
setTimeout(function(){keyRemapCooldown=false;},300);
showKeyRemapStep();
return;
}
},true);
// ── INPUT ──────────────────────────────────────────────
window.addEventListener("keydown",e=>{
var aeTag=document.activeElement&&document.activeElement.tagName;
if(aeTag==="INPUT"||aeTag==="TEXTAREA"||aeTag==="SELECT")return;
if(keyRemapping)return;
if(e.key==="Escape"){e.preventDefault();togglePause();return;}
if(paused)return;
const i=LANES.findIndex(l=>l.key===e.key.toLowerCase()||(l.key===" "&&e.key===" "));
if(i>=0){e.preventDefault();triggerPad(i);}
});
// ── GAMEPAD AUTO-DETECT ──────────────────────────────
var gpAutoDetected=false;
function autoDetectGamepad(gp){
if(gpAutoDetected)return;
gpAutoDetected=true;
var nButtons=gp.buttons.length;
// Enable lanes based on detected button count (5 = standard Rock Band, more = extended kit)
var lanesToEnable=Math.min(nButtons,LANES.length);
// Always enable at least the first 5 (standard kit) if we have 5+ buttons
if(lanesToEnable>=5){
for(var i=0;i<5;i++)activePads[i]=true;
// Enable extra lanes for extended kits (6+ buttons)
for(var j=5;j<lanesToEnable;j++)activePads[j]=true;
}
buildPadToggles();
var gpd=document.getElementById("gpd");
if(gpd)gpd.textContent="🎮 "+gp.id.slice(0,25)+" ("+nButtons+" buttons)";
}
function pollGP(){
const gps=navigator.getGamepads?navigator.getGamepads():[];
let found=false;
for(const gp of gps){
if(!gp)continue;found=true;
if(!gpAutoDetected)autoDetectGamepad(gp);
else document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20);
var remapHandled=false;
gp.buttons.forEach((btn,bi)=>{
if(btn.pressed&&!prevBtns[bi]){
if(remapping){
if(!remapHandled){remapHandled=true;handleRemapButton(bi);}
}
else if(!paused&&gpMap[bi]!==undefined)triggerPad(gpMap[bi]);
}
prevBtns[bi]=btn.pressed;
});
}
if(!found)document.getElementById("gpd").textContent="No gamepad";
requestAnimationFrame(pollGP);
}
pollGP();
</script>
</body>
</html>