The edKeyDown handler was catching Backspace, Delete, arrow keys, and other keys globally, even when the user was typing in the pitch input field. This made it impossible to edit the pitch value (e.g. changing D4 to A4) because Backspace would delete the selected note instead of editing the text. Added a check to skip the handler when focus is on INPUT, TEXTAREA, or SELECT. https://claude.ai/code/session_01TLvDyKzUQekd9hzFuEJ5YX
6566 lines
287 KiB
HTML
6566 lines
287 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>🥁 Drum Rhythm Game</title>
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0;}
|
||
body{background:#0a0a0f;color:#eee;font-family:sans-serif;height:100vh;display:flex;flex-direction:column;overflow:hidden;}
|
||
.screen{display:none;flex-direction:column;align-items:center;width:100%;height:100%;overflow-y:auto;padding:16px;gap:10px;}
|
||
.screen.active{display:flex;}
|
||
.card{background:#13131f;border:1px solid #222;border-radius:10px;padding:14px;width:100%;max-width:700px;}
|
||
.card h2{font-size:11px;opacity:.5;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;}
|
||
.mode-row{display:flex;gap:8px;}
|
||
.mode-card{flex:1;padding:10px;border-radius:8px;border:2px solid #222;background:#0d0d1a;cursor:pointer;text-align:center;}
|
||
.mode-card.sel{border-color:#805ad5;}
|
||
.mode-card h3{font-size:13px;margin-bottom:3px;}
|
||
.mode-card p{font-size:11px;opacity:.5;}
|
||
.genre-tabs{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px;}
|
||
.gtab{padding:5px 12px;border-radius:16px;border:1px solid #333;background:transparent;color:#aaa;cursor:pointer;font-size:12px;}
|
||
.gtab.active{background:#805ad5;border-color:#805ad5;color:#fff;}
|
||
.rgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;max-height:160px;overflow-y:auto;}
|
||
.rbtn{padding:6px 8px;border-radius:5px;border:1px solid #2a2a3a;background:#0d0d1a;color:#ccc;cursor:pointer;font-size:11px;text-align:left;}
|
||
.rbtn.sel{border-color:#38a169;color:#eee;}
|
||
.rbtn small{opacity:.5;display:block;}
|
||
.pad-toggle{display:flex;gap:8px;flex-wrap:wrap;}
|
||
.ptog{padding:7px 14px;border-radius:20px;border:2px solid;cursor:pointer;font-size:12px;font-weight:bold;opacity:.4;transition:opacity .15s;}
|
||
.ptog.on{opacity:1;}
|
||
.prow{display:flex;gap:6px;align-items:center;margin-bottom:5px;}
|
||
.prow input{background:#0d0d1a;border:1px solid #333;border-radius:5px;padding:5px 8px;color:#eee;font-size:12px;flex:1;}
|
||
.smbtn{padding:5px 10px;border-radius:5px;border:1px solid #333;background:transparent;color:#eee;cursor:pointer;font-size:12px;}
|
||
.smbtn.green{color:#68d391;border-color:#68d391;}
|
||
.smbtn.red{color:#fc8181;border-color:#fc8181;}
|
||
.smbtn.purple{color:#b794f4;border-color:#b794f4;}
|
||
.bigbtn{padding:11px 32px;border-radius:9px;border:none;background:#805ad5;color:#fff;font-size:15px;cursor:pointer;font-weight:bold;}
|
||
.bigbtn:hover{background:#9f7aea;}
|
||
#hud{padding:7px 12px;background:#111;border-bottom:1px solid #1e1e2e;display:flex;align-items:center;gap:10px;flex-shrink:0;width:100%;}
|
||
#hud-title{font-size:12px;font-weight:bold;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||
.hstat{text-align:center;min-width:50px;}.hstat span{font-size:9px;opacity:.5;display:block;}.hstat b{font-size:16px;}
|
||
#cwrap{flex:1;position:relative;width:100%;}
|
||
canvas{display:block;}
|
||
#msg{position:absolute;top:28%;left:50%;transform:translate(-50%,-50%);font-size:28px;font-weight:bold;pointer-events:none;opacity:0;text-shadow:0 0 20px currentColor;white-space:nowrap;}
|
||
#cdown{position:absolute;top:40%;left:50%;transform:translate(-50%,-50%);font-size:80px;font-weight:bold;pointer-events:none;opacity:0;color:#fff;}
|
||
#pbanner{position:absolute;top:12px;left:50%;transform:translateX(-50%);background:#1a1a2e;border:1px solid #444;border-radius:7px;padding:7px 18px;font-size:13px;opacity:0;pointer-events:none;transition:opacity .3s;}
|
||
#ss{padding:16px;}
|
||
.stbl{width:100%;max-width:600px;border-collapse:collapse;}
|
||
.stbl th{text-align:left;padding:7px 9px;border-bottom:1px solid #222;font-size:11px;opacity:.5;}
|
||
.stbl td{padding:7px 9px;border-bottom:1px solid #111;font-size:12px;}
|
||
.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){
|
||
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=>{
|
||
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>
|