The import panel now has 4 tabs: - JSON: existing JSON import (unchanged) - Text/Shorthand: type notes like "C4 quarter, D4 eighth, E4 half" or "C4 q D4 e E4 h" — supports all duration names, rests, auto lane assignment by pitch range - MusicXML: import .xml/.musicxml files from MuseScore, Finale, Sibelius, flat.io etc. Parses pitch, duration, tempo, chords, rests, and mid-stream tempo changes - MIDI: import .mid files with a minimal parser that handles format 0/1, tempo events, note on/off pairing, and tick-to-second conversion All import modes support an "Append" checkbox to add to existing notes. Lanes are auto-assigned by pitch range (high=Red, low=Kick). https://claude.ai/code/session_01TLvDyKzUQekd9hzFuEJ5YX
6984 lines
304 KiB
HTML
6984 lines
304 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="edGoToStart()" title="Scroll to start and reset play-from marker (Home key)">⏮ Start</button>
|
||
<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:4px;margin-bottom:6px;">
|
||
<button class="smbtn" onclick="edShowImportTab('json')" id="ed-imp-tab-json" style="background:#3182ce;font-size:10px;">JSON</button>
|
||
<button class="smbtn" onclick="edShowImportTab('text')" id="ed-imp-tab-text" style="font-size:10px;">Text/Shorthand</button>
|
||
<button class="smbtn" onclick="edShowImportTab('musicxml')" id="ed-imp-tab-musicxml" style="font-size:10px;">MusicXML</button>
|
||
<button class="smbtn" onclick="edShowImportTab('midi')" id="ed-imp-tab-midi" style="font-size:10px;">MIDI</button>
|
||
</div>
|
||
<div id="ed-imp-json" 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 JSON</button>
|
||
<button class="smbtn" onclick="document.getElementById('ed-json-file').click()">Load .json File</button>
|
||
<input type="file" id="ed-json-file" accept=".json" style="display:none;" onchange="edHandleFile(event)">
|
||
</div>
|
||
<div id="ed-imp-text" style="display:none;">
|
||
<span style="color:#888;font-size:11px;">Type notes as: <b style="color:#ff8;">C4 quarter, D4 eighth, E4 half</b> — or: <b style="color:#ff8;">C4 q D4 e E4 h</b> — or one per line. Lane auto-assigned by pitch.</span>
|
||
<textarea id="ed-shorthand-text" style="width:100%;height:80px;background:#111;color:#ff8;font-family:monospace;font-size:11px;border:1px solid #333;padding:4px;resize:vertical;margin-top:4px;" placeholder="C4 quarter, D4 eighth, E4 half, F4 quarter or: C4 q D4 e E4 h F4 q or one per line: C4 quarter D4 eighth"></textarea>
|
||
<div style="display:flex;gap:8px;align-items:center;margin-top:4px;">
|
||
<button class="smbtn" onclick="edDoShorthandImport()" style="background:#3182ce;">Import Notes</button>
|
||
<label style="color:#888;font-size:11px;"><input type="checkbox" id="ed-short-append"> Append (don't replace)</label>
|
||
</div>
|
||
<div style="color:#555;font-size:10px;margin-top:4px;">
|
||
Durations: <b>w</b>hole, <b>dh</b> dotted-half, <b>h</b>alf, <b>dq</b> dotted-quarter, <b>q</b>uarter, <b>de</b> dotted-eighth, <b>e</b>ighth, <b>s</b>ixteenth, <b>r</b> rest (skip) — default: quarter
|
||
</div>
|
||
</div>
|
||
<div id="ed-imp-musicxml" style="display:none;">
|
||
<span style="color:#888;font-size:11px;">Import a MusicXML file (.xml or .musicxml) — exports from MuseScore, Finale, Sibelius, flat.io, etc.</span>
|
||
<div style="display:flex;gap:8px;align-items:center;margin-top:6px;">
|
||
<button class="smbtn" onclick="document.getElementById('ed-musicxml-file').click()" style="background:#3182ce;">Choose MusicXML File</button>
|
||
<input type="file" id="ed-musicxml-file" accept=".xml,.musicxml,.mxl" style="display:none;" onchange="edHandleMusicXML(event)">
|
||
<label style="color:#888;font-size:11px;"><input type="checkbox" id="ed-mxml-append"> Append</label>
|
||
</div>
|
||
</div>
|
||
<div id="ed-imp-midi" style="display:none;">
|
||
<span style="color:#888;font-size:11px;">Import a MIDI file (.mid, .midi) — basic single-track import.</span>
|
||
<div style="display:flex;gap:8px;align-items:center;margin-top:6px;">
|
||
<button class="smbtn" onclick="document.getElementById('ed-midi-file').click()" style="background:#3182ce;">Choose MIDI File</button>
|
||
<input type="file" id="ed-midi-file" accept=".mid,.midi" style="display:none;" onchange="edHandleMIDI(event)">
|
||
<label style="color:#888;font-size:11px;"><input type="checkbox" id="ed-midi-append"> Append</label>
|
||
</div>
|
||
</div>
|
||
<div><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 edDraggingMarker=false; // true when dragging the play-from marker
|
||
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 — skip overwrite when user is actively typing in it
|
||
var pitchInput=document.getElementById("ed-note-pitch");
|
||
if(document.activeElement!==pitchInput){
|
||
pitchInput.value=m.f?edFreqToNote(m.f):"";
|
||
}
|
||
// Duration dropdown — skip if focused
|
||
var durSel=document.getElementById("ed-note-dur");
|
||
if(document.activeElement!==durSel){
|
||
durSel.value=edDurSecToName(m.d||0);
|
||
}
|
||
// Lane dropdown — skip if focused
|
||
var laneEl=document.getElementById("ed-note-lane");
|
||
if(document.activeElement!==laneEl){
|
||
laneEl.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 and start dragging it
|
||
if(my<ED_HEAD){
|
||
var startT=edXToTime(mx);
|
||
if(startT<0)startT=0;
|
||
edPlayFromMarker=startT;
|
||
edDraggingMarker=true;
|
||
edDraw();
|
||
// Double-click starts playback immediately from this position
|
||
if(e.detail===2){
|
||
edDraggingMarker=false;
|
||
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;
|
||
|
||
// Dragging play-from marker in header
|
||
if(edDraggingMarker){
|
||
var t=edXToTime(mx);
|
||
if(t<0)t=0;
|
||
edPlayFromMarker=t;
|
||
// Auto-scroll when dragging near edges
|
||
var cv2=getEdCanvas();
|
||
if(mx<30&&edScrollX>0){edScrollX=Math.max(0,edScrollX-10);}
|
||
if(mx>cv2.width-30){edScrollX+=10;}
|
||
edDraw();
|
||
return;
|
||
}
|
||
|
||
// 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(edDraggingMarker){
|
||
edDraggingMarker=false;
|
||
edDraw();
|
||
return;
|
||
}
|
||
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;
|
||
}
|
||
|
||
// Home key — scroll to start and reset play-from marker
|
||
if(e.key==="Home"){edGoToStart();e.preventDefault();return;}
|
||
|
||
// End key — scroll to end of piece
|
||
if(e.key==="End"){
|
||
var cv=getEdCanvas();
|
||
var endT=edIsClassical?(edPiece&&edPiece.duration||90):4;
|
||
edScrollX=Math.max(0,endT*edPxPerUnit()-cv.width+50);
|
||
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 edGoToStart(){
|
||
edScrollX=0;
|
||
edPlayFromMarker=-1;
|
||
edDraw();
|
||
}
|
||
|
||
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 edHandleFile(e){
|
||
var file=e.target.files[0];if(!file)return;
|
||
var reader=new FileReader();
|
||
reader.onload=function(ev){
|
||
document.getElementById("ed-import-text").value=ev.target.result;
|
||
edDoImport();
|
||
};
|
||
reader.readAsText(file);
|
||
e.target.value="";
|
||
}
|
||
|
||
// Tab switching for import panel
|
||
function edShowImportTab(tab){
|
||
["json","text","musicxml","midi"].forEach(function(t){
|
||
document.getElementById("ed-imp-"+t).style.display=t===tab?"block":"none";
|
||
var btn=document.getElementById("ed-imp-tab-"+t);
|
||
btn.style.background=t===tab?"#3182ce":"";
|
||
});
|
||
document.getElementById("ed-import-status").textContent="";
|
||
}
|
||
|
||
// --- TEXT/SHORTHAND IMPORT ---
|
||
// Parses: "C4 quarter, D4 eighth, E4 half" or "C4 q D4 e E4 h" or one-per-line
|
||
function edDoShorthandImport(){
|
||
var text=document.getElementById("ed-shorthand-text").value.trim();
|
||
if(!text){document.getElementById("ed-import-status").textContent="Type some notes first";return;}
|
||
var status=document.getElementById("ed-import-status");
|
||
var durMap={
|
||
"w":"whole","whole":"whole",
|
||
"dh":"dotted-half","dotted-half":"dotted-half","d.h":"dotted-half",
|
||
"h":"half","half":"half",
|
||
"dq":"dotted-quarter","dotted-quarter":"dotted-quarter","d.q":"dotted-quarter",
|
||
"q":"quarter","quarter":"quarter",
|
||
"de":"dotted-eighth","dotted-eighth":"dotted-eighth","d.e":"dotted-eighth",
|
||
"e":"eighth","eighth":"eighth",
|
||
"s":"sixteenth","sixteenth":"sixteenth",
|
||
"ts":"32nd","32nd":"32nd"
|
||
};
|
||
// Normalize: replace commas and newlines with spaces, collapse whitespace
|
||
var tokens=text.replace(/,/g," ").replace(/\n/g," ").replace(/\s+/g," ").trim().split(" ");
|
||
var notes=[];
|
||
var t=0;
|
||
var q=60/edBpm; // quarter note duration in seconds
|
||
var i=0;
|
||
var errors=[];
|
||
while(i<tokens.length){
|
||
var tok=tokens[i];
|
||
// Check if it's a note name (letter + optional accidental + octave)
|
||
if(/^[A-Ga-g][#b♯♭]?\d$/.test(tok)){
|
||
var durName="quarter"; // default
|
||
var durSec=q;
|
||
// Check if next token is a duration
|
||
if(i+1<tokens.length){
|
||
var nextLow=tokens[i+1].toLowerCase();
|
||
if(durMap[nextLow]){
|
||
durName=durMap[nextLow];
|
||
durSec=edDurNameToSec(durName);
|
||
i++; // consume duration token
|
||
}
|
||
}
|
||
var freq=edNoteToFreq(tok);
|
||
if(freq>0){
|
||
// Auto-assign lane based on pitch range
|
||
var lane=edPitchToLane(freq);
|
||
notes.push({t:parseFloat(t.toFixed(4)),l:lane,f:freq,d:parseFloat(durSec.toFixed(4))});
|
||
t+=durSec;
|
||
}else{
|
||
errors.push("Bad note: "+tok);
|
||
}
|
||
i++;
|
||
}else if(tok.toLowerCase()==="r"||tok.toLowerCase()==="rest"){
|
||
// Rest — advance time without adding a note
|
||
var restDur=q;
|
||
if(i+1<tokens.length){
|
||
var nextLow2=tokens[i+1].toLowerCase();
|
||
if(durMap[nextLow2]){restDur=edDurNameToSec(durMap[nextLow2]);i++;}
|
||
}
|
||
t+=restDur;
|
||
i++;
|
||
}else if(durMap[tok.toLowerCase()]){
|
||
// Bare duration without preceding note — skip
|
||
i++;
|
||
}else{
|
||
errors.push("Unknown: "+tok);
|
||
i++;
|
||
}
|
||
}
|
||
if(notes.length===0){status.textContent="No valid notes found. "+errors.join(", ");return;}
|
||
var append=document.getElementById("ed-short-append").checked;
|
||
if(append){
|
||
var offset=0;
|
||
edMelody.forEach(function(m){var end=m.t+(m.d||0);if(end>offset)offset=end;if(m.t>offset)offset=m.t;});
|
||
notes.forEach(function(n){n.t=parseFloat((n.t+offset).toFixed(4));});
|
||
edMelody=edMelody.concat(notes);
|
||
}else{
|
||
edMelody=notes;
|
||
}
|
||
edMelody.sort(function(a,b){return a.t-b.t;});
|
||
edSelected=-1;edDraw();edUpdateCode();
|
||
var msg="Imported "+notes.length+" notes from shorthand!";
|
||
if(errors.length>0)msg+=" ("+errors.length+" warnings: "+errors.slice(0,3).join(", ")+")";
|
||
status.textContent=msg;
|
||
}
|
||
|
||
// Auto-assign lane based on pitch frequency
|
||
function edPitchToLane(freq){
|
||
// Map pitch ranges to lanes 0-4
|
||
// Lane 0=Red (high), 1=Yellow, 2=Blue, 3=Green (low), 4=Kick (very low)
|
||
if(freq>=500)return 0; // C5 and above → Red
|
||
if(freq>=370)return 1; // F#4 to B4 → Yellow
|
||
if(freq>=280)return 2; // C#4 to F4 → Blue
|
||
if(freq>=180)return 3; // F#3 to C4 → Green
|
||
return 4; // below F#3 → Kick
|
||
}
|
||
|
||
// --- MUSICXML IMPORT ---
|
||
function edHandleMusicXML(e){
|
||
var file=e.target.files[0];if(!file)return;
|
||
var status=document.getElementById("ed-import-status");
|
||
var reader=new FileReader();
|
||
reader.onload=function(ev){
|
||
try{
|
||
var parser=new DOMParser();
|
||
var xml=parser.parseFromString(ev.target.result,"text/xml");
|
||
var parseErr=xml.querySelector("parsererror");
|
||
if(parseErr){status.textContent="XML parse error — is this a valid MusicXML file?";return;}
|
||
var notes=edParseMusicXML(xml);
|
||
if(notes.length===0){status.textContent="No notes found in MusicXML";return;}
|
||
var append=document.getElementById("ed-mxml-append").checked;
|
||
if(append){
|
||
var offset=0;
|
||
edMelody.forEach(function(m){var end=m.t+(m.d||0);if(end>offset)offset=end;});
|
||
notes.forEach(function(n){n.t=parseFloat((n.t+offset).toFixed(4));});
|
||
edMelody=edMelody.concat(notes);
|
||
}else{
|
||
edMelody=notes;
|
||
}
|
||
edMelody.sort(function(a,b){return a.t-b.t;});
|
||
edSelected=-1;edDraw();edUpdateCode();
|
||
status.textContent="Imported "+notes.length+" notes from MusicXML!";
|
||
}catch(err){
|
||
status.textContent="MusicXML error: "+err.message;
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
e.target.value="";
|
||
}
|
||
|
||
function edParseMusicXML(xml){
|
||
var notes=[];
|
||
var q=60/edBpm; // quarter note duration
|
||
// Find divisions (ticks per quarter note)
|
||
var divEl=xml.querySelector("attributes divisions");
|
||
var divisions=divEl?parseInt(divEl.textContent):1;
|
||
// Get BPM from tempo marking if present
|
||
var tempoEl=xml.querySelector("sound[tempo]");
|
||
if(tempoEl){
|
||
var xmlBpm=parseFloat(tempoEl.getAttribute("tempo"));
|
||
if(xmlBpm>0){edBpm=Math.round(xmlBpm);document.getElementById("ed-bpm").value=edBpm;q=60/edBpm;}
|
||
}
|
||
// Walk through all measures → notes
|
||
var t=0; // current time in seconds
|
||
var measures=xml.querySelectorAll("measure");
|
||
for(var mi=0;mi<measures.length;mi++){
|
||
var meas=measures[mi];
|
||
// Check for mid-stream divisions change
|
||
var divEl2=meas.querySelector("attributes divisions");
|
||
if(divEl2)divisions=parseInt(divEl2.textContent)||divisions;
|
||
// Check for tempo change
|
||
var tempoEl2=meas.querySelector("direction sound[tempo]");
|
||
if(!tempoEl2)tempoEl2=meas.querySelector("sound[tempo]");
|
||
if(tempoEl2){
|
||
var newBpm=parseFloat(tempoEl2.getAttribute("tempo"));
|
||
if(newBpm>0){q=60/newBpm;}
|
||
}
|
||
var children=meas.children;
|
||
for(var ci=0;ci<children.length;ci++){
|
||
var el=children[ci];
|
||
if(el.tagName==="forward"){
|
||
var fdur=el.querySelector("duration");
|
||
if(fdur)t+=(parseInt(fdur.textContent)/divisions)*q;
|
||
continue;
|
||
}
|
||
if(el.tagName==="backup"){
|
||
var bdur=el.querySelector("duration");
|
||
if(bdur)t-=(parseInt(bdur.textContent)/divisions)*q;
|
||
if(t<0)t=0;
|
||
continue;
|
||
}
|
||
if(el.tagName!=="note")continue;
|
||
var durEl=el.querySelector("duration");
|
||
var durTicks=durEl?parseInt(durEl.textContent):divisions;
|
||
var durSec=(durTicks/divisions)*q;
|
||
// Rest — advance time but don't add a note
|
||
if(el.querySelector("rest")){
|
||
if(!el.querySelector("chord"))t+=durSec;
|
||
continue;
|
||
}
|
||
// Chord notes don't advance time
|
||
var isChord=!!el.querySelector("chord");
|
||
// Get pitch
|
||
var pitchEl=el.querySelector("pitch");
|
||
if(!pitchEl){if(!isChord)t+=durSec;continue;}
|
||
var step=pitchEl.querySelector("step");
|
||
var octave=pitchEl.querySelector("octave");
|
||
var alter=pitchEl.querySelector("alter");
|
||
if(!step||!octave){if(!isChord)t+=durSec;continue;}
|
||
var noteName=step.textContent;
|
||
var alterVal=alter?parseInt(alter.textContent):0;
|
||
if(alterVal===1)noteName+="#";
|
||
else if(alterVal===-1)noteName+="b";
|
||
else if(alterVal===2)noteName+="##";
|
||
else if(alterVal===-2)noteName+="bb";
|
||
noteName+=octave.textContent;
|
||
var freq=edNoteToFreq(noteName);
|
||
if(freq>0){
|
||
var lane=edPitchToLane(freq);
|
||
notes.push({t:parseFloat(t.toFixed(4)),l:lane,f:freq,d:parseFloat(durSec.toFixed(4))});
|
||
}
|
||
if(!isChord)t+=durSec;
|
||
}
|
||
}
|
||
return notes;
|
||
}
|
||
|
||
// --- MIDI IMPORT ---
|
||
function edHandleMIDI(e){
|
||
var file=e.target.files[0];if(!file)return;
|
||
var status=document.getElementById("ed-import-status");
|
||
var reader=new FileReader();
|
||
reader.onload=function(ev){
|
||
try{
|
||
var buf=new Uint8Array(ev.target.result);
|
||
var notes=edParseMIDI(buf);
|
||
if(notes.length===0){status.textContent="No notes found in MIDI file";return;}
|
||
var append=document.getElementById("ed-midi-append").checked;
|
||
if(append){
|
||
var offset=0;
|
||
edMelody.forEach(function(m){var end=m.t+(m.d||0);if(end>offset)offset=end;});
|
||
notes.forEach(function(n){n.t=parseFloat((n.t+offset).toFixed(4));});
|
||
edMelody=edMelody.concat(notes);
|
||
}else{
|
||
edMelody=notes;
|
||
}
|
||
edMelody.sort(function(a,b){return a.t-b.t;});
|
||
edSelected=-1;edDraw();edUpdateCode();
|
||
status.textContent="Imported "+notes.length+" notes from MIDI!";
|
||
}catch(err){
|
||
status.textContent="MIDI error: "+err.message;
|
||
}
|
||
};
|
||
reader.readAsArrayBuffer(file);
|
||
e.target.value="";
|
||
}
|
||
|
||
function edParseMIDI(buf){
|
||
// Minimal MIDI parser — handles format 0 and 1 single/multi track
|
||
var pos=0;
|
||
function readU16(){return(buf[pos++]<<8)|buf[pos++];}
|
||
function readU32(){return(buf[pos++]<<24)|(buf[pos++]<<16)|(buf[pos++]<<8)|buf[pos++];}
|
||
function readVarLen(){
|
||
var v=0;
|
||
for(var i=0;i<4;i++){
|
||
var b=buf[pos++];
|
||
v=(v<<7)|(b&0x7F);
|
||
if(!(b&0x80))break;
|
||
}
|
||
return v;
|
||
}
|
||
// Header: MThd
|
||
if(buf[0]!==0x4D||buf[1]!==0x54||buf[2]!==0x68||buf[3]!==0x64)throw new Error("Not a MIDI file");
|
||
pos=4;
|
||
var hdrLen=readU32(); // should be 6
|
||
var format=readU16();
|
||
var nTracks=readU16();
|
||
var ticksPerBeat=readU16();
|
||
pos=8+hdrLen; // skip to first track
|
||
|
||
var usPerBeat=500000; // default 120 BPM
|
||
var allEvents=[];
|
||
|
||
for(var tr=0;tr<nTracks;tr++){
|
||
// MTrk
|
||
if(buf[pos]!==0x4D||buf[pos+1]!==0x54||buf[pos+2]!==0x72||buf[pos+3]!==0x6B){
|
||
// Skip non-track chunks
|
||
pos+=4;var cLen=readU32();pos+=cLen;continue;
|
||
}
|
||
pos+=4;
|
||
var trkLen=readU32();
|
||
var trkEnd=pos+trkLen;
|
||
var tickTime=0;
|
||
var running=0;
|
||
while(pos<trkEnd){
|
||
var delta=readVarLen();
|
||
tickTime+=delta;
|
||
var byte1=buf[pos];
|
||
if(byte1>=0x80){running=byte1;pos++;}
|
||
var cmd=running&0xF0;
|
||
if(cmd===0x90){// Note on
|
||
var note=buf[pos++];var vel=buf[pos++];
|
||
allEvents.push({type:vel>0?"on":"off",tick:tickTime,note:note,vel:vel,track:tr});
|
||
}else if(cmd===0x80){// Note off
|
||
var note2=buf[pos++];pos++;// skip vel
|
||
allEvents.push({type:"off",tick:tickTime,note:note2,track:tr});
|
||
}else if(cmd===0xA0||cmd===0xB0||cmd===0xE0){pos+=2;}
|
||
else if(cmd===0xC0||cmd===0xD0){pos+=1;}
|
||
else if(byte1===0xFF){// Meta event
|
||
var metaType=buf[pos++];
|
||
var metaLen=readVarLen();
|
||
if(metaType===0x51&&metaLen===3){// Tempo
|
||
usPerBeat=(buf[pos]<<16)|(buf[pos+1]<<8)|buf[pos+2];
|
||
}
|
||
pos+=metaLen;
|
||
}else if(byte1===0xF0||byte1===0xF7){// SysEx
|
||
var sysLen=readVarLen();pos+=sysLen;
|
||
}else{
|
||
// Unknown — try to skip gracefully
|
||
pos++;
|
||
}
|
||
}
|
||
pos=trkEnd;
|
||
}
|
||
|
||
// Set BPM from tempo
|
||
var bpmFromMidi=Math.round(60000000/usPerBeat);
|
||
if(bpmFromMidi>0){edBpm=bpmFromMidi;document.getElementById("ed-bpm").value=edBpm;}
|
||
|
||
// Convert tick events to notes with time/duration
|
||
allEvents.sort(function(a,b){return a.tick-b.tick||(a.type==="off"?-1:1);});
|
||
var secPerTick=usPerBeat/(ticksPerBeat*1000000);
|
||
var openNotes={}; // key: noteNum → {tick, event}
|
||
var notes=[];
|
||
for(var i=0;i<allEvents.length;i++){
|
||
var ev=allEvents[i];
|
||
var timeSec=ev.tick*secPerTick;
|
||
var key=ev.note;
|
||
if(ev.type==="on"){
|
||
openNotes[key]={tick:ev.tick,time:timeSec};
|
||
}else if(ev.type==="off"&&openNotes[key]){
|
||
var start=openNotes[key];
|
||
var dur=timeSec-start.time;
|
||
if(dur<0.01)dur=0.05;
|
||
var freq=parseFloat((440*Math.pow(2,(key-69)/12)).toFixed(2));
|
||
var lane=edPitchToLane(freq);
|
||
notes.push({t:parseFloat(start.time.toFixed(4)),l:lane,f:freq,d:parseFloat(dur.toFixed(4))});
|
||
delete openNotes[key];
|
||
}
|
||
}
|
||
return notes;
|
||
}
|
||
|
||
// Download all edits as a JSON file (for transferring between machines)
|
||
function edDownloadAllEdits(){
|
||
var all=edGetAllSaved();
|
||
var keys=Object.keys(all);
|
||
if(keys.length===0){document.getElementById("ed-status").textContent="No edits to download";return;}
|
||
var json=JSON.stringify(all,null,2);
|
||
var blob=new Blob([json],{type:"application/json"});
|
||
var a=document.createElement("a");
|
||
a.href=URL.createObjectURL(blob);
|
||
a.download="drum-game-edits.json";
|
||
a.click();URL.revokeObjectURL(a.href);
|
||
document.getElementById("ed-status").textContent="Downloaded "+keys.length+" edit(s)";
|
||
}
|
||
|
||
// Load edits from a JSON file into localStorage
|
||
function edLoadEditsFile(e){
|
||
var file=e.target.files[0];if(!file)return;
|
||
var reader=new FileReader();
|
||
reader.onload=function(ev){
|
||
try{
|
||
var imported=JSON.parse(ev.target.result);
|
||
var all={}; try{all=JSON.parse(localStorage.getItem(ED_STORAGE_KEY))||{};}catch(x){}
|
||
var count=0;
|
||
for(var k in imported){all[k]=imported[k];count++;}
|
||
localStorage.setItem(ED_STORAGE_KEY,JSON.stringify(all));
|
||
document.getElementById("ed-status").textContent="Loaded "+count+" edit(s) from file";
|
||
// Refresh current editor view if open
|
||
if(edPiece){var saved=edLoadSaved(edPiece.n);if(saved){edMelody=saved.melody.map(function(m){var n={t:m.t,l:m.l};if(m.d>0)n.d=m.d;return n;});edBpm=saved.bpm||edBpm;document.getElementById("ed-bpm").value=edBpm;edDraw();}}
|
||
}catch(err){document.getElementById("ed-status").textContent="Error: "+err.message;}
|
||
};
|
||
reader.readAsText(file);
|
||
e.target.value="";
|
||
}
|
||
|
||
// Bake all edits into HTML — downloads a copy of index.html with edits embedded
|
||
function edBakeIntoHtml(){
|
||
var all=edGetAllSaved();
|
||
var keys=Object.keys(all);
|
||
if(keys.length===0){document.getElementById("ed-status").textContent="No edits to bake";return;}
|
||
// Get the current page HTML
|
||
var html=document.documentElement.outerHTML;
|
||
// Replace the BAKED_EDITS variable between the markers
|
||
var bakedJson=JSON.stringify(all);
|
||
html=html.replace(
|
||
/\/\/ ── BAKED EDITS[^\n]*\nvar BAKED_EDITS\s*=\s*[^;]*;\n\/\/ ── END BAKED EDITS ──/,
|
||
"// ── BAKED EDITS (auto-populated by \"Bake into HTML\") ──\nvar BAKED_EDITS = "+bakedJson+";\n// ── END BAKED EDITS ──"
|
||
);
|
||
var blob=new Blob(["<!DOCTYPE html>\n"+document.documentElement.outerHTML],{type:"text/html"});
|
||
var a=document.createElement("a");
|
||
a.href=URL.createObjectURL(blob);
|
||
a.download="index.html";
|
||
a.click();URL.revokeObjectURL(a.href);
|
||
document.getElementById("ed-status").textContent="Downloaded index.html with "+keys.length+" baked edit(s)";
|
||
}
|
||
|
||
// Show/hide edit button — works for all genres
|
||
function updateEditBtn(){
|
||
var pieces=SYNTH_GENRES[curGenre]||GENRES[curGenre];
|
||
var btn=document.getElementById("edit-btn");
|
||
btn.style.display=(pieces&&pieces[curIdx])?"":"none";
|
||
}
|
||
|
||
// ── GAME START ─────────────────────────────────────────
|
||
function startGame(){
|
||
stopPreview();
|
||
// Pre-warm AudioContext on Linux — resume early so it's ready after countdown
|
||
var warmAc=getAc();warmAc.resume();
|
||
gamePlayers=players.map(p=>({...p,score:0,streak:0,maxStreak:0,total:0,good:0}));
|
||
activePIdx=0;resetStats();show("sg");resizeCv();
|
||
if(mode==="builtin"){
|
||
gameRhythm=SYNTH_GENRES[curGenre]?SYNTH_GENRES[curGenre][curIdx]:GENRES[curGenre][curIdx];
|
||
document.getElementById("hud-title").textContent=curGenre+" — "+gameRhythm.n+" ("+gameRhythm.bpm+" bpm)";
|
||
notes=gameRhythm.isClassical?buildClassicalNotes(gameRhythm):buildNotes(gameRhythm);
|
||
countdown(()=>gameRhythm.isClassical?beginClassical():beginBuiltin());
|
||
} else {
|
||
gameRhythm=null;notes=[];
|
||
document.getElementById("hud-title").textContent=mp3Buffer?"MP3 Jam":"Jam";
|
||
countdown(()=>beginMp3());
|
||
}
|
||
updateHud();
|
||
}
|
||
function stopGame(){playing=false;if(mp3Src){try{mp3Src.stop();}catch(e){}mp3Src=null;}gameSynthNodes.forEach(function(n){try{n.stop();}catch(e){}});gameSynthNodes=[];}
|
||
function resetStats(){cScore=0;cStreak=0;cTotal=0;cGood=0;cMaxStreak=0;gameEndTime=null;}
|
||
|
||
function buildNotes(r){
|
||
var saved=edLoadSaved(r.n);
|
||
var pattern=saved?saved.melody:r.p;
|
||
var bpm=saved?saved.bpm:r.bpm;
|
||
const bs=60/bpm/gameSpeed,bars=r.bars||8,res=[];
|
||
for(let bar=0;bar<bars;bar++)
|
||
for(const p of pattern)
|
||
if(activePads[p.l])
|
||
res.push({time:(bar*4+p.t)*bs,lane:p.l,hit:false,missed:false});
|
||
var sorted=res.sort((a,b)=>a.time-b.time);
|
||
return simplifyNotes(sorted);
|
||
}
|
||
|
||
function countdown(cb){
|
||
const el=document.getElementById("cdown");let n=3;
|
||
el.style.opacity=1;el.textContent=n;
|
||
const iv=setInterval(()=>{n--;if(n<=0){clearInterval(iv);el.style.opacity=0;cb();}else el.textContent=n;},1000);
|
||
}
|
||
|
||
function beginBuiltin(){
|
||
playing=true;
|
||
const ac=getAc(),r=gameRhythm,bs=60/r.bpm/gameSpeed,bars=r.bars||8;
|
||
gameStartTime=ac.currentTime;
|
||
for(let bar=0;bar<bars;bar++)
|
||
for(const p of r.p)
|
||
playDrumAt(ac,p.l,gameStartTime+bar*4*bs+p.t*bs,4,[]);
|
||
const totalSec=bars*4*bs;
|
||
gameEndTime=totalSec;
|
||
setTimeout(()=>{if(playing)endTurn();},totalSec*1000+5000);
|
||
}
|
||
|
||
function beginMp3(){
|
||
playing=true;gameStartTime=getAc().currentTime;
|
||
if(mp3Buffer){
|
||
mp3Src=getAc().createBufferSource();mp3Src.buffer=mp3Buffer;
|
||
mp3Src.connect(getAc().destination);mp3Src.start();
|
||
mp3Src.onended=()=>{if(playing)endTurn();};
|
||
} else setTimeout(()=>{if(playing)endTurn();},30000);
|
||
}
|
||
|
||
function endTurn(){
|
||
playing=false;
|
||
const p=gamePlayers[activePIdx];
|
||
p.score=cScore;p.streak=cMaxStreak;p.total=cTotal;p.good=cGood;
|
||
const acc=cTotal?Math.round(cGood/cTotal*100):0;
|
||
savedScores.push({player:p.name,rhythm:gameRhythm?curGenre+" — "+gameRhythm.n:"MP3 Jam",score:cScore,acc,streak:cMaxStreak,date:new Date().toLocaleDateString()});
|
||
lsSave();
|
||
activePIdx++;
|
||
if(activePIdx<gamePlayers.length){
|
||
resetStats();notes=gameRhythm?(gameRhythm.isClassical?buildClassicalNotes(gameRhythm):buildNotes(gameRhythm)):[];updateHud();
|
||
const banner=document.getElementById("pbanner");
|
||
banner.textContent="Next: "+gamePlayers[activePIdx].name+"!";banner.style.opacity=1;
|
||
setTimeout(()=>{banner.style.opacity=0;setTimeout(()=>countdown(()=>gameRhythm.isClassical?beginClassical():beginBuiltin()),400);},2200);
|
||
} else {
|
||
const sorted=[...gamePlayers].sort((a,b)=>b.score-a.score);
|
||
const lines=sorted.map((p,i)=>`${["🥇","🥈","🥉"][i]||" "} ${p.name}: ${p.score.toLocaleString()} (${p.total?Math.round(p.good/p.total*100):0}%)`);
|
||
setTimeout(()=>{alert("Results:\n\n"+lines.join("\n"));goMenu();},500);
|
||
}
|
||
}
|
||
|
||
// ── HIT LOGIC ──────────────────────────────────────────
|
||
function triggerPad(lane){
|
||
var isClassical=gameRhythm&&gameRhythm.isClassical;
|
||
var isClassicalOrch=isClassical&&classicalHitMode==='orchestra';
|
||
// Drum hit sound — quieter in classical so orchestra background is audible
|
||
if(!isClassicalOrch){
|
||
playDrumAt(getAc(),lane,getAc().currentTime,isClassical?3:8,[]);
|
||
}
|
||
laneFlash[lane]=1;setTimeout(()=>laneFlash[lane]=0,120);
|
||
if(!playing)return;
|
||
if(mode==="mp3"){showMsg("♪",LANES[lane].color);return;}
|
||
if(!activePads[lane])return; // inactive pad — sound only
|
||
const now=getAc().currentTime-gameStartTime;
|
||
let best=null,bestD=HIT_WIN;
|
||
for(const n of notes){
|
||
if(n.lane!==lane||n.hit||n.missed)continue;
|
||
const d=Math.abs(n.time-now);
|
||
if(d<bestD){bestD=d;best=n;}
|
||
}
|
||
cTotal++;
|
||
if(best){
|
||
best.hit=true;cStreak++;cGood++;cScore+=100*Math.min(cStreak,10);
|
||
if(cStreak>cMaxStreak)cMaxStreak=cStreak;
|
||
// Play orchestral hit sound for classical mode
|
||
if(isClassicalOrch&&best.freq){
|
||
var ac=getAc(),ct=ac.currentTime;
|
||
playSynth(ac,best.freq,ct,0.35,'strings',0.7,[]);
|
||
playSynth(ac,best.freq,ct,0.35,'brass',0.4,[]);
|
||
} else if(isClassicalOrch){
|
||
playDrumAt(getAc(),lane,getAc().currentTime,3,[]);
|
||
}
|
||
showMsg(bestD<0.05?"PERFECT!":"GOOD",bestD<0.05?"#68d391":"#d69e2e");
|
||
} else {
|
||
// Miss — quiet drum thud as feedback
|
||
if(isClassicalOrch)playDrumAt(getAc(),lane,getAc().currentTime,2,[]);
|
||
cStreak=0;showMsg("MISS","#fc8181");
|
||
}
|
||
updateHud();
|
||
}
|
||
function updateHud(){
|
||
document.getElementById("hs").textContent=cScore.toLocaleString();
|
||
document.getElementById("hst").textContent=cStreak;
|
||
document.getElementById("ha").textContent=cTotal?Math.round(cGood/cTotal*100)+"%":"-";
|
||
document.getElementById("hp").textContent=gamePlayers[activePIdx]?.name||"-";
|
||
}
|
||
function showMsg(txt,color){
|
||
const el=document.getElementById("msg");el.textContent=txt;el.style.color=color;el.style.opacity=1;
|
||
clearTimeout(msgTmr);msgTmr=setTimeout(()=>el.style.opacity=0,500);
|
||
}
|
||
|
||
// ── CANVAS ─────────────────────────────────────────────
|
||
const canvas=document.getElementById("c");
|
||
const wrap=document.getElementById("cwrap");
|
||
const cx=canvas.getContext("2d");
|
||
function resizeCv(){canvas.width=wrap.clientWidth;canvas.height=wrap.clientHeight;}
|
||
resizeCv();window.addEventListener("resize",resizeCv);
|
||
|
||
function draw(){
|
||
requestAnimationFrame(draw);
|
||
const W=canvas.width,H=canvas.height;
|
||
if(!W||!H)return;
|
||
cx.clearRect(0,0,W,H);
|
||
|
||
// Pad lanes (everything except kick) in columns; kick centered at bottom
|
||
const padLanes=LANES.map((l,i)=>i).filter(i=>i!==4&&activePads[i]);
|
||
const hasKick=activePads[4];
|
||
const PAD_H=hasKick?Math.floor(H*0.78):H;
|
||
const KICK_H=H-PAD_H;
|
||
const nLanes=padLanes.length;
|
||
const LANE_W=nLanes>0?W/nLanes:W;
|
||
const HIT_Y=Math.floor(PAD_H*0.76);
|
||
const KICK_HIT_Y=PAD_H+Math.floor(KICK_H*0.55);
|
||
const now=(playing&&gameStartTime!=null)?getAc().currentTime-gameStartTime:-9999;
|
||
|
||
// Lane positions map: lane index -> x center
|
||
const laneX={};
|
||
padLanes.forEach((li,col)=>{laneX[li]=col*LANE_W+LANE_W/2;});
|
||
|
||
// Draw pad lanes
|
||
padLanes.forEach((li,col)=>{
|
||
const x=col*LANE_W;
|
||
cx.fillStyle=laneFlash[li]?LANES[li].color+"33":"#0d0d16";
|
||
cx.fillRect(x,0,LANE_W,PAD_H);
|
||
if(col>0){cx.strokeStyle="#1e1e2e";cx.lineWidth=1;cx.beginPath();cx.moveTo(x,0);cx.lineTo(x,PAD_H);cx.stroke();}
|
||
cx.fillStyle=LANES[li].color;cx.font="bold 12px sans-serif";cx.textAlign="center";
|
||
cx.fillText(LANES[li].label,x+LANE_W/2,PAD_H-20);
|
||
cx.fillStyle="#999";cx.font="10px sans-serif";
|
||
var keyLabel=LANES[li].key===" "?"SPACE":LANES[li].key.toUpperCase();
|
||
cx.fillText(keyLabel,x+LANE_W/2,PAD_H-7);
|
||
});
|
||
|
||
// Kick zone — centered at bottom, notes fall from top of screen
|
||
if(hasKick){
|
||
cx.fillStyle=laneFlash[4]?LANES[4].color+"33":"#0a0a12";
|
||
cx.fillRect(0,PAD_H,W,KICK_H);
|
||
cx.strokeStyle="#1e1e2e";cx.lineWidth=1;cx.beginPath();cx.moveTo(0,PAD_H);cx.lineTo(W,PAD_H);cx.stroke();
|
||
cx.fillStyle=LANES[4].color;cx.font="bold 13px sans-serif";cx.textAlign="center";
|
||
cx.fillText("Kick",W/2,PAD_H+KICK_H*0.42);
|
||
cx.fillStyle="#999";cx.font="10px sans-serif";
|
||
cx.fillText("SPACE",W/2,PAD_H+KICK_H*0.72);
|
||
}
|
||
|
||
if(nLanes===0&&!hasKick)return;
|
||
|
||
// Pad hit line
|
||
if(nLanes>0){
|
||
cx.strokeStyle="rgba(255,255,255,0.5)";cx.lineWidth=2;
|
||
cx.beginPath();cx.moveTo(0,HIT_Y);cx.lineTo(W,HIT_Y);cx.stroke();
|
||
cx.fillStyle="rgba(255,255,255,0.03)";
|
||
cx.fillRect(0,HIT_Y-SPEED*HIT_WIN,W,SPEED*HIT_WIN*2);
|
||
}
|
||
|
||
// Kick hit line
|
||
if(hasKick){
|
||
cx.strokeStyle=LANES[4].color+"88";cx.lineWidth=2;
|
||
cx.beginPath();cx.moveTo(W*0.15,KICK_HIT_Y);cx.lineTo(W*0.85,KICK_HIT_Y);cx.stroke();
|
||
}
|
||
|
||
// Target rings — pad lanes
|
||
padLanes.forEach(li=>{
|
||
const x=laneX[li],r=Math.min(LANE_W*0.33,26);
|
||
cx.beginPath();cx.arc(x,HIT_Y,r,0,Math.PI*2);
|
||
cx.strokeStyle=laneFlash[li]?LANES[li].color:LANES[li].color+"55";
|
||
cx.lineWidth=laneFlash[li]?3:2;cx.stroke();
|
||
if(laneFlash[li]){cx.beginPath();cx.arc(x,HIT_Y,r,0,Math.PI*2);cx.fillStyle=LANES[li].color+"44";cx.fill();}
|
||
});
|
||
// Kick ring
|
||
if(hasKick){
|
||
const kr=Math.min(W*0.08,34);
|
||
cx.beginPath();cx.arc(W/2,KICK_HIT_Y,kr,0,Math.PI*2);
|
||
cx.strokeStyle=laneFlash[4]?LANES[4].color:LANES[4].color+"55";
|
||
cx.lineWidth=laneFlash[4]?3:2;cx.stroke();
|
||
if(laneFlash[4]){cx.beginPath();cx.arc(W/2,KICK_HIT_Y,kr,0,Math.PI*2);cx.fillStyle=LANES[4].color+"44";cx.fill();}
|
||
}
|
||
|
||
// Auto-end when all notes resolved and past expected end time
|
||
if(playing&&mode==="builtin"&&gameEndTime!=null&&now>=gameEndTime){
|
||
if(notes.length===0||notes.every(n=>n.hit||n.missed))endTurn();
|
||
}
|
||
|
||
// Notes — pad lanes fall in columns, kick falls centered from top
|
||
if(mode==="builtin"&&now>-9000){
|
||
for(const n of notes){
|
||
if(n.hit)continue;
|
||
const dt=n.time-now;
|
||
if(n.lane===4){
|
||
// Kick — falls from top of screen, centered, landing on KICK_HIT_Y
|
||
if(!hasKick)continue;
|
||
const ky=KICK_HIT_Y-dt*SPEED;
|
||
if(ky<-40||ky>H+40){
|
||
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
|
||
continue;
|
||
}
|
||
const kr=Math.min(W*0.08,34);
|
||
cx.globalAlpha=n.missed?0.2:1;
|
||
cx.fillStyle=LANES[4].color;cx.beginPath();cx.arc(W/2,ky,kr,0,Math.PI*2);cx.fill();
|
||
cx.strokeStyle="#fff";cx.lineWidth=2;cx.beginPath();cx.arc(W/2,ky,kr,0,Math.PI*2);cx.stroke();
|
||
cx.globalAlpha=1;
|
||
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
|
||
} else {
|
||
// Pad lanes — fall in columns
|
||
if(!laneX.hasOwnProperty(n.lane))continue;
|
||
const ny=HIT_Y-dt*SPEED;
|
||
if(ny<-40||ny>PAD_H+40){
|
||
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
|
||
continue;
|
||
}
|
||
const x=laneX[n.lane],r=Math.min(LANE_W*0.33,26);
|
||
cx.globalAlpha=n.missed?0.2:1;
|
||
const g=cx.createRadialGradient(x,ny,0,x,ny,r*2);
|
||
g.addColorStop(0,LANES[n.lane].color+"aa");g.addColorStop(1,LANES[n.lane].color+"00");
|
||
cx.fillStyle=g;cx.beginPath();cx.arc(x,ny,r*2,0,Math.PI*2);cx.fill();
|
||
cx.fillStyle=LANES[n.lane].color;cx.beginPath();cx.arc(x,ny,r,0,Math.PI*2);cx.fill();
|
||
cx.strokeStyle="#fff";cx.lineWidth=2;cx.beginPath();cx.arc(x,ny,r,0,Math.PI*2);cx.stroke();
|
||
cx.globalAlpha=1;
|
||
if(!n.missed&&dt<-HIT_WIN){n.missed=true;if(cStreak>0){cStreak=0;updateHud();}}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
draw();
|
||
|
||
// ── DRUM SOUNDS ────────────────────────────────────────
|
||
function playDrumAt(ac,lane,t,vol,arr){
|
||
vol=vol*masterVolume;
|
||
const type=LANES[lane].type;
|
||
function mk(n){if(arr)arr.push(n);}
|
||
if(type==="kick"||type==="bass"){
|
||
const o=ac.createOscillator(),g=ac.createGain();
|
||
o.connect(g);g.connect(ac.destination);
|
||
o.frequency.setValueAtTime(180,t);o.frequency.exponentialRampToValueAtTime(40,t+0.15);
|
||
g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
|
||
o.start(t);o.stop(t+0.35);mk(o);
|
||
} else if(type==="snare"){
|
||
const sz=ac.sampleRate*0.12,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||
f.type="highpass";f.frequency.value=1500;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
||
g.gain.setValueAtTime(vol*.75,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
|
||
s.start(t);s.stop(t+0.15);mk(s);
|
||
const o=ac.createOscillator(),g2=ac.createGain();
|
||
o.connect(g2);g2.connect(ac.destination);o.frequency.value=180;
|
||
g2.gain.setValueAtTime(vol*.35,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.07);
|
||
o.start(t);o.stop(t+0.08);mk(o);
|
||
} else if(type==="hihat"){
|
||
const sz=ac.sampleRate*0.05,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||
f.type="highpass";f.frequency.value=7000;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
||
g.gain.setValueAtTime(vol*.5,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.05);
|
||
s.start(t);s.stop(t+0.06);mk(s);
|
||
} else if(type==="crash"){
|
||
// Crash cymbal — long noise burst with resonance
|
||
const sz=ac.sampleRate*0.4,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||
f.type="bandpass";f.frequency.value=4000;f.Q.value=0.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
||
g.gain.setValueAtTime(vol*.6,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.4);
|
||
s.start(t);s.stop(t+0.45);mk(s);
|
||
} else if(type==="ride"){
|
||
// Ride cymbal — shorter metallic ping
|
||
const sz=ac.sampleRate*0.15,buf=ac.createBuffer(1,sz,ac.sampleRate),d=buf.getChannelData(0);
|
||
for(let i=0;i<sz;i++)d[i]=Math.random()*2-1;
|
||
const s=ac.createBufferSource(),f=ac.createBiquadFilter(),g=ac.createGain();
|
||
f.type="bandpass";f.frequency.value=6000;f.Q.value=1.5;s.buffer=buf;s.connect(f);f.connect(g);g.connect(ac.destination);
|
||
g.gain.setValueAtTime(vol*.45,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.12);
|
||
s.start(t);s.stop(t+0.15);mk(s);
|
||
// Add a subtle pitched ping
|
||
const o=ac.createOscillator(),g2=ac.createGain();
|
||
o.type="triangle";o.connect(g2);g2.connect(ac.destination);o.frequency.value=800;
|
||
g2.gain.setValueAtTime(vol*.15,t);g2.gain.exponentialRampToValueAtTime(0.001,t+0.08);
|
||
o.start(t);o.stop(t+0.1);mk(o);
|
||
} else { // tom / tom2 / tom3
|
||
const freq=type==="tom"?160:type==="tom2"?120:90;
|
||
const o=ac.createOscillator(),g=ac.createGain();
|
||
o.connect(g);g.connect(ac.destination);
|
||
o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(freq*0.4,t+0.2);
|
||
g.gain.setValueAtTime(vol*.85,t);g.gain.exponentialRampToValueAtTime(0.001,t+0.25);
|
||
o.start(t);o.stop(t+0.28);mk(o);
|
||
}
|
||
}
|
||
|
||
// ── PAUSE ─────────────────────────────────────────────
|
||
function togglePause(){
|
||
if(!playing&&!paused)return;
|
||
var ac=getAc();
|
||
if(!paused){
|
||
paused=true;playing=false;pauseTime=ac.currentTime;
|
||
ac.suspend();
|
||
document.getElementById("pause-overlay").classList.add("show");
|
||
} else {
|
||
paused=false;playing=true;
|
||
ac.resume();
|
||
document.getElementById("pause-overlay").classList.remove("show");
|
||
}
|
||
}
|
||
|
||
// ── REMAP ─────────────────────────────────────────────
|
||
var remapLanes=LANES.map(function(ln,i){return{l:i,name:ln.label.toUpperCase(),color:ln.color};});
|
||
function startRemap(){
|
||
remapping=true;remapStep=0;remapResult={};
|
||
document.getElementById("remap-overlay").classList.add("show");
|
||
showRemapStep();
|
||
}
|
||
function cancelRemap(){
|
||
remapping=false;document.getElementById("remap-overlay").classList.remove("show");
|
||
}
|
||
function showRemapStep(){
|
||
if(remapStep>=remapLanes.length){
|
||
// Done — save mapping
|
||
gpMap={};
|
||
for(var k in remapResult)gpMap[k]=remapResult[k];
|
||
localStorage.setItem("drgameGpMap",JSON.stringify(gpMap));
|
||
remapping=false;
|
||
document.getElementById("remap-overlay").classList.remove("show");
|
||
document.getElementById("remap-prompt").textContent="";
|
||
return;
|
||
}
|
||
var rl=remapLanes[remapStep];
|
||
document.getElementById("remap-prompt").innerHTML="Hit your <span style='color:"+rl.color+";font-size:24px'>"+rl.name+"</span> pad";
|
||
document.getElementById("remap-status").textContent="Step "+(remapStep+1)+" of "+remapLanes.length+(remapStep===4?" (or press foot pedal)":"");
|
||
}
|
||
var remapCooldown=false;
|
||
function handleRemapButton(bi){
|
||
if(!remapping||remapCooldown)return false;
|
||
remapResult[bi]=remapLanes[remapStep].l;
|
||
remapStep++;
|
||
remapCooldown=true;
|
||
setTimeout(function(){remapCooldown=false;},300);
|
||
showRemapStep();
|
||
return true;
|
||
}
|
||
|
||
// ── KEYBOARD REMAP ────────────────────────────────────
|
||
var keyRemapping=false,keyRemapStep=0,keyRemapResult={};
|
||
var customKeyMap=JSON.parse(localStorage.getItem("drgameKeyMap")||"null");
|
||
// Apply saved key mappings on load
|
||
if(customKeyMap){
|
||
LANES.forEach(function(ln,i){if(customKeyMap[i]!==undefined)ln.key=customKeyMap[i];});
|
||
}
|
||
function startKeyRemap(){
|
||
keyRemapping=true;keyRemapStep=0;keyRemapResult={};
|
||
document.getElementById("keyremap-overlay").classList.add("show");
|
||
showKeyRemapStep();
|
||
}
|
||
function cancelKeyRemap(){
|
||
keyRemapping=false;document.getElementById("keyremap-overlay").classList.remove("show");
|
||
}
|
||
function showKeyRemapStep(){
|
||
if(keyRemapStep>=LANES.length){
|
||
// Done — apply and save
|
||
LANES.forEach(function(ln,i){if(keyRemapResult[i]!==undefined)ln.key=keyRemapResult[i];});
|
||
var map={};LANES.forEach(function(ln,i){map[i]=ln.key;});
|
||
localStorage.setItem("drgameKeyMap",JSON.stringify(map));
|
||
customKeyMap=map;
|
||
keyRemapping=false;
|
||
document.getElementById("keyremap-overlay").classList.remove("show");
|
||
buildPadToggles();
|
||
return;
|
||
}
|
||
var ln=LANES[keyRemapStep];
|
||
document.getElementById("keyremap-prompt").innerHTML="Press key for <span style='color:"+ln.color+";font-size:24px'>"+ln.label.toUpperCase()+"</span>";
|
||
document.getElementById("keyremap-status").textContent="Step "+(keyRemapStep+1)+" of "+LANES.length+" (press any key)";
|
||
}
|
||
var keyRemapCooldown=false;
|
||
window.addEventListener("keydown",function(e){
|
||
var aeTag=document.activeElement&&document.activeElement.tagName;
|
||
if(aeTag==="INPUT"||aeTag==="TEXTAREA"||aeTag==="SELECT")return;
|
||
if(keyRemapping){
|
||
if(e.key==="Escape"){cancelKeyRemap();e.preventDefault();return;}
|
||
if(keyRemapCooldown)return;
|
||
e.preventDefault();
|
||
keyRemapResult[keyRemapStep]=e.key===" "?" ":e.key.toLowerCase();
|
||
keyRemapStep++;
|
||
keyRemapCooldown=true;
|
||
setTimeout(function(){keyRemapCooldown=false;},300);
|
||
showKeyRemapStep();
|
||
return;
|
||
}
|
||
},true);
|
||
|
||
// ── INPUT ──────────────────────────────────────────────
|
||
window.addEventListener("keydown",e=>{
|
||
var aeTag=document.activeElement&&document.activeElement.tagName;
|
||
if(aeTag==="INPUT"||aeTag==="TEXTAREA"||aeTag==="SELECT")return;
|
||
if(keyRemapping)return;
|
||
if(e.key==="Escape"){e.preventDefault();togglePause();return;}
|
||
if(paused)return;
|
||
const i=LANES.findIndex(l=>l.key===e.key.toLowerCase()||(l.key===" "&&e.key===" "));
|
||
if(i>=0){e.preventDefault();triggerPad(i);}
|
||
});
|
||
|
||
// ── GAMEPAD AUTO-DETECT ──────────────────────────────
|
||
var gpAutoDetected=false;
|
||
function autoDetectGamepad(gp){
|
||
if(gpAutoDetected)return;
|
||
gpAutoDetected=true;
|
||
var nButtons=gp.buttons.length;
|
||
// Enable lanes based on detected button count (5 = standard Rock Band, more = extended kit)
|
||
var lanesToEnable=Math.min(nButtons,LANES.length);
|
||
// Always enable at least the first 5 (standard kit) if we have 5+ buttons
|
||
if(lanesToEnable>=5){
|
||
for(var i=0;i<5;i++)activePads[i]=true;
|
||
// Enable extra lanes for extended kits (6+ buttons)
|
||
for(var j=5;j<lanesToEnable;j++)activePads[j]=true;
|
||
}
|
||
buildPadToggles();
|
||
var gpd=document.getElementById("gpd");
|
||
if(gpd)gpd.textContent="🎮 "+gp.id.slice(0,25)+" ("+nButtons+" buttons)";
|
||
}
|
||
|
||
function pollGP(){
|
||
const gps=navigator.getGamepads?navigator.getGamepads():[];
|
||
let found=false;
|
||
for(const gp of gps){
|
||
if(!gp)continue;found=true;
|
||
if(!gpAutoDetected)autoDetectGamepad(gp);
|
||
else document.getElementById("gpd").textContent="🎮 "+gp.id.slice(0,20);
|
||
var remapHandled=false;
|
||
gp.buttons.forEach((btn,bi)=>{
|
||
if(btn.pressed&&!prevBtns[bi]){
|
||
if(remapping){
|
||
if(!remapHandled){remapHandled=true;handleRemapButton(bi);}
|
||
}
|
||
else if(!paused&&gpMap[bi]!==undefined)triggerPad(gpMap[bi]);
|
||
}
|
||
prevBtns[bi]=btn.pressed;
|
||
});
|
||
}
|
||
if(!found)document.getElementById("gpd").textContent="No gamepad";
|
||
requestAnimationFrame(pollGP);
|
||
}
|
||
pollGP();
|
||
</script>
|
||
</body>
|
||
</html>
|