Spaces:
Running
Running
Download index.html from Kkcd106/Midi-Simple-Generator: direct link, hf CLI and curl.
- Browser
- Download file 59.3 kB
-
https://huggingface.co/spaces/Kkcd106/Midi-Simple-Generator/resolve/main/index.html
- Command line
-
hf download hf://spaces/Kkcd106/Midi-Simple-Generator/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Kkcd106/Midi-Simple-Generator/resolve/main/index.html
59.3 kB
| <html lang="th"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1.0"> | |
| <title>AI Random Hit Hip-Hop MIDI Producer</title> | |
| <style> | |
| *{box-sizing:border-box} | |
| body{ | |
| margin:0; | |
| background:#08090d; | |
| color:#fff; | |
| font-family:Arial,Helvetica,sans-serif; | |
| } | |
| .container{ | |
| width:min(960px,100%); | |
| margin:auto; | |
| padding:18px; | |
| } | |
| header{ | |
| text-align:center; | |
| padding:15px 5px 22px; | |
| } | |
| header h1{ | |
| margin:0; | |
| font-size:28px; | |
| } | |
| header p{ | |
| color:#92929d; | |
| margin:8px 0; | |
| } | |
| .card{ | |
| background:#14151c; | |
| border:1px solid #292b35; | |
| border-radius:18px; | |
| padding:18px; | |
| margin-bottom:16px; | |
| } | |
| .grid{ | |
| display:grid; | |
| grid-template-columns:repeat(2,1fr); | |
| gap:14px; | |
| } | |
| @media(max-width:650px){ | |
| .grid{ | |
| grid-template-columns:1fr; | |
| } | |
| header h1{ | |
| font-size:23px; | |
| } | |
| } | |
| label{ | |
| display:block; | |
| font-weight:bold; | |
| margin-bottom:7px; | |
| } | |
| select, | |
| input, | |
| textarea{ | |
| width:100%; | |
| background:#0b0c10; | |
| color:#fff; | |
| border:1px solid #353742; | |
| border-radius:10px; | |
| padding:12px; | |
| font-size:15px; | |
| outline:none; | |
| } | |
| textarea{ | |
| min-height:105px; | |
| resize:vertical; | |
| } | |
| button{ | |
| width:100%; | |
| padding:15px; | |
| margin-top:10px; | |
| border:0; | |
| border-radius:12px; | |
| font-size:16px; | |
| font-weight:bold; | |
| cursor:pointer; | |
| } | |
| .primary{ | |
| background:#fff; | |
| color:#08090d; | |
| } | |
| .secondary{ | |
| background:#292b35; | |
| color:#fff; | |
| } | |
| .success{ | |
| background:#193b2a; | |
| color:#fff; | |
| } | |
| .info{ | |
| background:#101117; | |
| border:1px solid #292b35; | |
| border-radius:12px; | |
| padding:13px; | |
| line-height:1.7; | |
| } | |
| .progress{ | |
| height:8px; | |
| background:#292b35; | |
| border-radius:20px; | |
| overflow:hidden; | |
| margin-top:12px; | |
| } | |
| .progressBar{ | |
| width:0%; | |
| height:100%; | |
| background:#fff; | |
| transition:.2s; | |
| } | |
| .track{ | |
| display:flex; | |
| justify-content:space-between; | |
| padding:10px 0; | |
| border-bottom:1px solid #282a33; | |
| } | |
| .track:last-child{ | |
| border-bottom:0; | |
| } | |
| .small{ | |
| color:#888; | |
| font-size:13px; | |
| } | |
| .badge{ | |
| display:inline-block; | |
| padding:5px 9px; | |
| border-radius:20px; | |
| background:#252732; | |
| margin:3px; | |
| font-size:12px; | |
| } | |
| .highlight{ | |
| font-size:18px; | |
| font-weight:bold; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1> AI RANDOM HIT HIP-HOP MIDI PRODUCER</h1> | |
| <p>Progression First โข Smart Random โข No Immediate Repetition</p> | |
| </header> | |
| <!-- CONTROL --> | |
| <div class="card"> | |
| <div class="grid"> | |
| <div> | |
| <label>๐ฅ Hit Progression</label> | |
| <select id="progressionSelect"> | |
| <option value="RANDOM">๐ฒ RANDOM HIT PROGRESSION</option> | |
| <optgroup label="๐ฅ Popular Minor"> | |
| <option value="i-VI-III-VII">iโVIโIIIโVII โ Dark Emotional</option> | |
| <option value="i-VII-VI-VII">iโVIIโVIโVII โ Dark Rap</option> | |
| <option value="i-VI-v-i">iโVIโvโi โ Modern Trap</option> | |
| <option value="i-VI-VII-iv">iโVIโVIIโiv โ Melodic Rap</option> | |
| <option value="i-VII-VI-iv">iโVIIโVIโiv โ Dark Descent</option> | |
| <option value="i-VII-VI">iโVIIโVI โ Minimal Dark</option> | |
| <option value="i-VI-VII">iโVIโVII โ Trap Loop</option> | |
| <option value="i-III-VI-VII">iโIIIโVIโVII โ Melodic Trap</option> | |
| <option value="i-III-VII-VI">iโIIIโVIIโVI โ Emotional Trap</option> | |
| <option value="i-VII-iv-VI">iโVIIโivโVI โ Moody Rap</option> | |
| <option value="i-v-VI-VII">iโvโVIโVII โ Dark Cinematic</option> | |
| <option value="i-v-VII-iv">iโvโVIIโiv โ Aggressive Minor</option> | |
| <option value="i-v-iv-VII">iโvโivโVII โ Dark Underground</option> | |
| <option value="i-iv-v">iโivโv โ Classic Minor</option> | |
| <option value="i-iv-VII">iโivโVII โ Dark Loop</option> | |
| <option value="VI-VII-i">VIโVIIโi โ Build Into Hook</option> | |
| </optgroup> | |
| <optgroup label="๐น Simple / Minimal"> | |
| <option value="III-i">IIIโi โ Minimal Melodic</option> | |
| <option value="i-V">iโV โ Dark Two Chord</option> | |
| <option value="i-VII">iโVII โ Street Minimal</option> | |
| <option value="i-VI">iโVI โ Emotional Two Chord</option> | |
| <option value="i-v">iโv โ Dark Tension</option> | |
| <option value="i">i โ One Chord Loop</option> | |
| </optgroup> | |
| <optgroup label="๐ท Jazz / Soul Hip-Hop"> | |
| <option value="ii-V-I">iiโVโI โ Jazz Rap</option> | |
| <option value="ii-VI-I-V">iiโVIโIโV โ Soul Rap</option> | |
| <option value="I-IV-I">IโIVโI โ Soul Anthem</option> | |
| <option value="I-IV-V">IโIVโV โ Classic Hip-Hop</option> | |
| <option value="I-VI-IV-V">IโVIโIVโV โ Melodic Soul</option> | |
| </optgroup> | |
| <optgroup label="๐ Atmospheric"> | |
| <option value="i-VI-iv-VII">iโVIโivโVII โ Atmospheric</option> | |
| <option value="i-III-iv-VII">iโIIIโivโVII โ Dreamy</option> | |
| <option value="i-VII-III-VI">iโVIIโIIIโVI โ Cinematic</option> | |
| <option value="i-iv-VI-VII">iโivโVIโVII โ Emotional Dark</option> | |
| </optgroup> | |
| </select> | |
| </div> | |
| <div> | |
| <label>๐ค Style</label> | |
| <select id="style"> | |
| <option value="RANDOM">๐ฒ Random Style</option> | |
| <option>Modern Trap</option> | |
| <option>Dark Trap</option> | |
| <option>Melodic Trap</option> | |
| <option>Heavy 808 Trap</option> | |
| <option>Drill</option> | |
| <option>UK Drill</option> | |
| <option>Dark Drill</option> | |
| <option>Phonk</option> | |
| <option>Memphis Phonk</option> | |
| <option>Boom Bap</option> | |
| <option>Golden Era</option> | |
| <option>East Coast</option> | |
| <option>West Coast</option> | |
| <option>G-Funk</option> | |
| <option>Lo-Fi</option> | |
| <option>Chillhop</option> | |
| <option>Jazz Rap</option> | |
| <option>Soul Hip-Hop</option> | |
| <option>Emotional Rap</option> | |
| <option>Cloud Rap</option> | |
| <option>Hardcore Rap</option> | |
| <option>Street Rap</option> | |
| <option>Thai Hip-Hop</option> | |
| <option>Thai Trap</option> | |
| <option>Thai Drill</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label>โฑ Song Length</label> | |
| <select id="duration"> | |
| <option value="180">3:00</option> | |
| <option value="150">2:30</option> | |
| <option value="210">3:30</option> | |
| <option value="240">4:00</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label>๐ Humanization</label> | |
| <input | |
| type="range" | |
| id="human" | |
| min="0" | |
| max="100" | |
| value="35" | |
| oninput="humanValue.textContent=this.value+'%'"> | |
| <div | |
| id="humanValue" | |
| class="small"> | |
| 35% | |
| </div> | |
| </div> | |
| </div> | |
| <button | |
| class="secondary" | |
| onclick="showProgressionInfo()"> | |
| ๐น เธเธนเธฃเธฒเธขเธฅเธฐเนเธญเธตเธขเธ Progression | |
| </button> | |
| </div> | |
| <!-- CREATIVE --> | |
| <div class="card"> | |
| <label>๐ง Creative Direction</label> | |
| <textarea | |
| id="prompt" | |
| placeholder="เธเธฅเนเธญเธขเธงเนเธฒเธเนเธเธทเนเธญเธชเธธเนเธกเธเธฑเนเธเธซเธกเธ เธซเธฃเธทเธญเนเธชเนเธเธณเธญเธเธดเธเธฒเธข เนเธเนเธ dark, emotional, catchy hook..."></textarea> | |
| <button | |
| class="primary" | |
| onclick="generateMusic()"> | |
| ๐ฒ GENERATE HIT HIP-HOP MIDI | |
| </button> | |
| <button | |
| class="secondary" | |
| onclick="randomizeAll()"> | |
| ๐ฅ RANDOMIZE EVERYTHING | |
| </button> | |
| </div> | |
| <!-- BLUEPRINT --> | |
| <div class="card"> | |
| <h3>๐ฏ Current Song Blueprint</h3> | |
| <div | |
| id="blueprint" | |
| class="info"> | |
| เธขเธฑเธเนเธกเนเนเธเน Generate | |
| </div> | |
| </div> | |
| <!-- STATUS --> | |
| <div class="card"> | |
| <div | |
| id="status" | |
| class="info"> | |
| Ready. | |
| </div> | |
| <div class="progress"> | |
| <div | |
| id="progressBar" | |
| class="progressBar"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TRACKS --> | |
| <div | |
| class="card" | |
| id="tracksCard" | |
| style="display:none"> | |
| <h3>๐ MIDI Tracks</h3> | |
| <div class="track"> | |
| <span>๐น Piano</span> | |
| <span id="pianoInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ธ Guitar</span> | |
| <span id="guitarInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ต Bass</span> | |
| <span id="bassInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ 808</span> | |
| <span id="808Info" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ฅ Kick</span> | |
| <span id="kickInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ฅ Snare</span> | |
| <span id="snareInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ฉ Hi-Hat</span> | |
| <span id="hatInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ช Percussion</span> | |
| <span id="percInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ป Strings</span> | |
| <span id="stringsInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐น Lead</span> | |
| <span id="leadInfo" class="small"></span> | |
| </div> | |
| <div class="track"> | |
| <span>๐ถ Hook</span> | |
| <span id="hookInfo" class="small"></span> | |
| </div> | |
| </div> | |
| <!-- DOWNLOAD --> | |
| <div class="card"> | |
| <button | |
| id="download1" | |
| class="success" | |
| style="display:none" | |
| onclick="downloadType1()"> | |
| โฌ๏ธ Download MIDI Type 1 โ Multi Track | |
| </button> | |
| <button | |
| id="download0" | |
| class="secondary" | |
| style="display:none" | |
| onclick="downloadType0()"> | |
| โฌ๏ธ Download MIDI Type 0 โ Compatibility | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| /* ========================================================= | |
| CORE | |
| ========================================================= */ | |
| const PPQ=480; | |
| const BAR=PPQ*4; | |
| let generatedTracks=[]; | |
| let midiType1=null; | |
| let midiType0=null; | |
| let lastProgression=null; | |
| let lastKey=null; | |
| let lastStyle=null; | |
| let progressionBag=[]; | |
| /* ========================================================= | |
| PROGRESSION LIBRARY | |
| ========================================================= */ | |
| const PROGRESSIONS={ | |
| "i-VI-III-VII":{ | |
| name:"Dark Emotional", | |
| roman:["i","VI","III","VII"], | |
| mode:"minor", | |
| bpm:[88,105], | |
| moods:["dark","emotional","melodic"] | |
| }, | |
| "i-VII-VI-VII":{ | |
| name:"Dark Rap Loop", | |
| roman:["i","VII","VI","VII"], | |
| mode:"minor", | |
| bpm:[88,105], | |
| moods:["dark","street","confident"] | |
| }, | |
| "i-VI-v-i":{ | |
| name:"Modern Trap", | |
| roman:["i","VI","v","i"], | |
| mode:"minor", | |
| bpm:[125,145], | |
| moods:["dark","modern","emotional"] | |
| }, | |
| "i-VI-VII-iv":{ | |
| name:"Melodic Rap", | |
| roman:["i","VI","VII","iv"], | |
| mode:"minor", | |
| bpm:[110,140], | |
| moods:["emotional","melodic","dramatic"] | |
| }, | |
| "i-VII-VI-iv":{ | |
| name:"Dark Descent", | |
| roman:["i","VII","VI","iv"], | |
| mode:"minor", | |
| bpm:[85,105], | |
| moods:["dark","cinematic"] | |
| }, | |
| "i-VII-VI":{ | |
| name:"Minimal Dark", | |
| roman:["i","VII","VI"], | |
| mode:"minor", | |
| bpm:[80,105], | |
| moods:["dark","minimal"] | |
| }, | |
| "i-VI-VII":{ | |
| name:"Trap Loop", | |
| roman:["i","VI","VII"], | |
| mode:"minor", | |
| bpm:[125,145], | |
| moods:["trap","dark","energetic"] | |
| }, | |
| "i-III-VI-VII":{ | |
| name:"Melodic Trap", | |
| roman:["i","III","VI","VII"], | |
| mode:"minor", | |
| bpm:[125,150], | |
| moods:["melodic","emotional"] | |
| }, | |
| "i-III-VII-VI":{ | |
| name:"Emotional Trap", | |
| roman:["i","III","VII","VI"], | |
| mode:"minor", | |
| bpm:[120,145], | |
| moods:["emotional","dark"] | |
| }, | |
| "i-VII-iv-VI":{ | |
| name:"Moody Rap", | |
| roman:["i","VII","iv","VI"], | |
| mode:"minor", | |
| bpm:[85,105], | |
| moods:["moody","dark"] | |
| }, | |
| "i-v-VI-VII":{ | |
| name:"Dark Cinematic", | |
| roman:["i","v","VI","VII"], | |
| mode:"minor", | |
| bpm:[85,105], | |
| moods:["cinematic","dark"] | |
| }, | |
| "i-v-VII-iv":{ | |
| name:"Aggressive Minor", | |
| roman:["i","v","VII","iv"], | |
| mode:"minor", | |
| bpm:[90,110], | |
| moods:["aggressive","dark"] | |
| }, | |
| "i-v-iv-VII":{ | |
| name:"Dark Underground", | |
| roman:["i","v","iv","VII"], | |
| mode:"minor", | |
| bpm:[82,100], | |
| moods:["underground","dark"] | |
| }, | |
| "i-iv-v":{ | |
| name:"Classic Minor", | |
| roman:["i","iv","v"], | |
| mode:"minor", | |
| bpm:[82,100], | |
| moods:["classic","dark"] | |
| }, | |
| "i-iv-VII":{ | |
| name:"Dark Loop", | |
| roman:["i","iv","VII"], | |
| mode:"minor", | |
| bpm:[85,105], | |
| moods:["dark","minimal"] | |
| }, | |
| "VI-VII-i":{ | |
| name:"Build Into Hook", | |
| roman:["VI","VII","i"], | |
| mode:"minor", | |
| bpm:[90,130], | |
| moods:["emotional","uplifting"] | |
| }, | |
| "III-i":{ | |
| name:"Minimal Melodic", | |
| roman:["III","i"], | |
| mode:"minor", | |
| bpm:[85,135], | |
| moods:["melodic","minimal"] | |
| }, | |
| "i-V":{ | |
| name:"Dark Two Chord", | |
| roman:["i","V"], | |
| mode:"minor", | |
| bpm:[80,105], | |
| moods:["dark","tension"] | |
| }, | |
| "i-VII":{ | |
| name:"Street Minimal", | |
| roman:["i","VII"], | |
| mode:"minor", | |
| bpm:[80,105], | |
| moods:["street","minimal"] | |
| }, | |
| "i-VI":{ | |
| name:"Emotional Two Chord", | |
| roman:["i","VI"], | |
| mode:"minor", | |
| bpm:[75,105], | |
| moods:["emotional","melodic"] | |
| }, | |
| "i-v":{ | |
| name:"Dark Tension", | |
| roman:["i","v"], | |
| mode:"minor", | |
| bpm:[80,105], | |
| moods:["dark","tense"] | |
| }, | |
| "i":{ | |
| name:"One Chord Loop", | |
| roman:["i"], | |
| mode:"minor", | |
| bpm:[75,105], | |
| moods:["minimal","hypnotic"] | |
| }, | |
| "ii-V-I":{ | |
| name:"Jazz Rap", | |
| roman:["ii","V","I"], | |
| mode:"major", | |
| bpm:[80,105], | |
| moods:["jazz","soul","smooth"] | |
| }, | |
| "ii-VI-I-V":{ | |
| name:"Soul Rap", | |
| roman:["ii","VI","I","V"], | |
| mode:"major", | |
| bpm:[80,105], | |
| moods:["soul","smooth"] | |
| }, | |
| "I-IV-I":{ | |
| name:"Soul Anthem", | |
| roman:["I","IV","I"], | |
| mode:"major", | |
| bpm:[85,110], | |
| moods:["anthem","soul","uplifting"] | |
| }, | |
| "I-IV-V":{ | |
| name:"Classic Hip-Hop", | |
| roman:["I","IV","V"], | |
| mode:"major", | |
| bpm:[85,110], | |
| moods:["classic","uplifting"] | |
| }, | |
| "I-VI-IV-V":{ | |
| name:"Melodic Soul", | |
| roman:["I","VI","IV","V"], | |
| mode:"major", | |
| bpm:[85,115], | |
| moods:["soul","melodic"] | |
| }, | |
| "i-VI-iv-VII":{ | |
| name:"Atmospheric", | |
| roman:["i","VI","iv","VII"], | |
| mode:"minor", | |
| bpm:[75,100], | |
| moods:["atmospheric","dark"] | |
| }, | |
| "i-III-iv-VII":{ | |
| name:"Dreamy", | |
| roman:["i","III","iv","VII"], | |
| mode:"minor", | |
| bpm:[75,105], | |
| moods:["dreamy","emotional"] | |
| }, | |
| "i-VII-III-VI":{ | |
| name:"Cinematic", | |
| roman:["i","VII","III","VI"], | |
| mode:"minor", | |
| bpm:[80,105], | |
| moods:["cinematic","dramatic"] | |
| }, | |
| "i-iv-VI-VII":{ | |
| name:"Emotional Dark", | |
| roman:["i","iv","VI","VII"], | |
| mode:"minor", | |
| bpm:[80,105], | |
| moods:["emotional","dark"] | |
| } | |
| }; | |
| /* ========================================================= | |
| STYLE LIBRARY | |
| ========================================================= */ | |
| const STYLES={ | |
| "Modern Trap":{ | |
| bpm:[130,150], | |
| drums:"trap", | |
| energy:90, | |
| melody:"bell", | |
| bass:"808" | |
| }, | |
| "Dark Trap":{ | |
| bpm:[128,145], | |
| drums:"darktrap", | |
| energy:92, | |
| melody:"dark", | |
| bass:"808slide" | |
| }, | |
| "Melodic Trap":{ | |
| bpm:[125,145], | |
| drums:"trapmelodic", | |
| energy:80, | |
| melody:"piano", | |
| bass:"808" | |
| }, | |
| "Heavy 808 Trap":{ | |
| bpm:[138,155], | |
| drums:"heavytrap", | |
| energy:96, | |
| melody:"synth", | |
| bass:"808slide" | |
| }, | |
| "Drill":{ | |
| bpm:[138,150], | |
| drums:"drill", | |
| energy:92, | |
| melody:"piano", | |
| bass:"drill808" | |
| }, | |
| "UK Drill":{ | |
| bpm:[140,150], | |
| drums:"ukdrill", | |
| energy:90, | |
| melody:"bell", | |
| bass:"drill808" | |
| }, | |
| "Dark Drill":{ | |
| bpm:[142,152], | |
| drums:"darkdrill", | |
| energy:96, | |
| melody:"dark", | |
| bass:"drill808" | |
| }, | |
| "Phonk":{ | |
| bpm:[130,155], | |
| drums:"phonk", | |
| energy:94, | |
| melody:"bell", | |
| bass:"phonk808" | |
| }, | |
| "Memphis Phonk":{ | |
| bpm:[120,145], | |
| drums:"memphis", | |
| energy:90, | |
| melody:"bell", | |
| bass:"phonk808" | |
| }, | |
| "Boom Bap":{ | |
| bpm:[86,98], | |
| drums:"boom", | |
| energy:65, | |
| melody:"piano", | |
| bass:"warm" | |
| }, | |
| "Golden Era":{ | |
| bpm:[88,96], | |
| drums:"boom2", | |
| energy:68, | |
| melody:"piano", | |
| bass:"warm" | |
| }, | |
| "East Coast":{ | |
| bpm:[88,100], | |
| drums:"boom", | |
| energy:70, | |
| melody:"piano", | |
| bass:"warm" | |
| }, | |
| "West Coast":{ | |
| bpm:[90,105], | |
| drums:"west", | |
| energy:75, | |
| melody:"synth", | |
| bass:"funk" | |
| }, | |
| "G-Funk":{ | |
| bpm:[92,105], | |
| drums:"west", | |
| energy:78, | |
| melody:"lead", | |
| bass:"funk" | |
| }, | |
| "Lo-Fi":{ | |
| bpm:[72,88], | |
| drums:"lofi", | |
| energy:45, | |
| melody:"piano", | |
| bass:"warm" | |
| }, | |
| "Chillhop":{ | |
| bpm:[75,92], | |
| drums:"lofi", | |
| energy:48, | |
| melody:"piano", | |
| bass:"warm" | |
| }, | |
| "Jazz Rap":{ | |
| bpm:[82,100], | |
| drums:"jazz", | |
| energy:55, | |
| melody:"piano", | |
| bass:"jazz" | |
| }, | |
| "Soul Hip-Hop":{ | |
| bpm:[78,96], | |
| drums:"soul", | |
| energy:60, | |
| melody:"piano", | |
| bass:"warm" | |
| }, | |
| "Emotional Rap":{ | |
| bpm:[115,140], | |
| drums:"trapmelodic", | |
| energy:70, | |
| melody:"piano", | |
| bass:"808" | |
| }, | |
| "Cloud Rap":{ | |
| bpm:[110,135], | |
| drums:"cloud", | |
| energy:55, | |
| melody:"pad", | |
| bass:"808" | |
| }, | |
| "Hardcore Rap":{ | |
| bpm:[88,108], | |
| drums:"hard", | |
| energy:95, | |
| melody:"dark", | |
| bass:"heavy" | |
| }, | |
| "Street Rap":{ | |
| bpm:[84,102], | |
| drums:"street", | |
| energy:82, | |
| melody:"piano", | |
| bass:"heavy" | |
| }, | |
| "Thai Hip-Hop":{ | |
| bpm:[88,105], | |
| drums:"thai", | |
| energy:78, | |
| melody:"piano", | |
| bass:"thai" | |
| }, | |
| "Thai Trap":{ | |
| bpm:[130,150], | |
| drums:"trap", | |
| energy:90, | |
| melody:"bell", | |
| bass:"808slide" | |
| }, | |
| "Thai Drill":{ | |
| bpm:[140,150], | |
| drums:"drill", | |
| energy:94, | |
| melody:"piano", | |
| bass:"drill808" | |
| } | |
| }; | |
| /* ========================================================= | |
| RANDOM | |
| ========================================================= */ | |
| function rand(min,max){ | |
| return Math.random()*(max-min)+min; | |
| } | |
| function randi(min,max){ | |
| return Math.floor( | |
| Math.random()*(max-min+1) | |
| )+min; | |
| } | |
| function pick(arr){ | |
| return arr[ | |
| Math.floor( | |
| Math.random()*arr.length | |
| ) | |
| ]; | |
| } | |
| function clamp(v,min,max){ | |
| return Math.max( | |
| min, | |
| Math.min(max,v) | |
| ); | |
| } | |
| function wait(ms){ | |
| return new Promise( | |
| r=>setTimeout(r,ms) | |
| ); | |
| } | |
| /* ========================================================= | |
| PROGRESSION BAG | |
| ========================================================= */ | |
| function refillProgressionBag(){ | |
| progressionBag= | |
| Object.keys(PROGRESSIONS); | |
| } | |
| function getRandomProgression(){ | |
| if( | |
| progressionBag.length===0 | |
| ){ | |
| refillProgressionBag(); | |
| } | |
| let choices= | |
| progressionBag.filter( | |
| x=>x!==lastProgression | |
| ); | |
| if( | |
| choices.length===0 | |
| ){ | |
| choices=[...progressionBag]; | |
| } | |
| const selected= | |
| pick(choices); | |
| progressionBag= | |
| progressionBag.filter( | |
| x=>x!==selected | |
| ); | |
| lastProgression=selected; | |
| return selected; | |
| } | |
| /* ========================================================= | |
| RANDOM KEY | |
| ========================================================= */ | |
| const MINOR_KEYS=[ | |
| "C", | |
| "C#", | |
| "D", | |
| "D#", | |
| "E", | |
| "F", | |
| "F#", | |
| "G", | |
| "G#", | |
| "A", | |
| "A#", | |
| "B" | |
| ]; | |
| const MAJOR_KEYS=[ | |
| "C", | |
| "C#", | |
| "D", | |
| "D#", | |
| "E", | |
| "F", | |
| "F#", | |
| "G", | |
| "G#", | |
| "A", | |
| "A#", | |
| "B" | |
| ]; | |
| const KEY_MAP={ | |
| C:0, | |
| "C#":1, | |
| D:2, | |
| "D#":3, | |
| E:4, | |
| F:5, | |
| "F#":6, | |
| G:7, | |
| "G#":8, | |
| A:9, | |
| "A#":10, | |
| B:11 | |
| }; | |
| function randomKey(mode){ | |
| const pool= | |
| mode==="major" | |
| ?MAJOR_KEYS | |
| :MINOR_KEYS; | |
| let choices= | |
| pool.filter( | |
| k=>k!==lastKey | |
| ); | |
| if(!choices.length){ | |
| choices=[...pool]; | |
| } | |
| const key=pick(choices); | |
| lastKey=key; | |
| return { | |
| root:KEY_MAP[key], | |
| name:key+ | |
| ( | |
| mode==="major" | |
| ?" Major" | |
| :" Minor" | |
| ), | |
| minor:mode!=="major" | |
| }; | |
| } | |
| /* ========================================================= | |
| ROMAN TO SCALE DEGREE | |
| ========================================================= */ | |
| const MINOR_DEGREES={ | |
| "i":0, | |
| "ii":2, | |
| "III":3, | |
| "iv":5, | |
| "v":7, | |
| "VI":8, | |
| "VII":10, | |
| "V":7 | |
| }; | |
| const MAJOR_DEGREES={ | |
| "I":0, | |
| "ii":2, | |
| "iii":4, | |
| "IV":5, | |
| "V":7, | |
| "vi":9, | |
| "VII":11, | |
| "VI":9 | |
| }; | |
| function progressionToOffsets( | |
| progression, | |
| mode | |
| ){ | |
| const map= | |
| mode==="major" | |
| ?MAJOR_DEGREES | |
| :MINOR_DEGREES; | |
| return progression.roman.map( | |
| r=>map[r]??0 | |
| ); | |
| } | |
| /* ========================================================= | |
| CHORD | |
| ========================================================= */ | |
| function makeChord( | |
| root, | |
| offset, | |
| mode, | |
| roman | |
| ){ | |
| const r=root+offset; | |
| let intervals; | |
| if( | |
| roman==="i" || | |
| roman==="iv" || | |
| roman==="v" || | |
| roman==="ii" | |
| ){ | |
| intervals=[ | |
| 0, | |
| 3, | |
| 7 | |
| ]; | |
| }else{ | |
| intervals=[ | |
| 0, | |
| 4, | |
| 7 | |
| ]; | |
| } | |
| /* | |
| Major progression. | |
| */ | |
| if(mode==="major"){ | |
| if( | |
| roman==="ii" || | |
| roman==="iii" || | |
| roman==="vi" | |
| ){ | |
| intervals=[ | |
| 0, | |
| 3, | |
| 7 | |
| ]; | |
| }else{ | |
| intervals=[ | |
| 0, | |
| 4, | |
| 7 | |
| ]; | |
| } | |
| } | |
| return intervals.map( | |
| x=>r+x | |
| ); | |
| } | |
| /* ========================================================= | |
| NOTE | |
| ========================================================= */ | |
| function addNote( | |
| track, | |
| start, | |
| duration, | |
| note, | |
| velocity, | |
| channel=0 | |
| ){ | |
| track.push({ | |
| start:Math.max( | |
| 0, | |
| Math.round(start) | |
| ), | |
| end:Math.max( | |
| 1, | |
| Math.round( | |
| start+duration | |
| ) | |
| ), | |
| note:clamp( | |
| Math.round(note), | |
| 0, | |
| 127 | |
| ), | |
| velocity:clamp( | |
| Math.round(velocity), | |
| 1, | |
| 127 | |
| ), | |
| channel | |
| }); | |
| } | |
| /* ========================================================= | |
| PIANO | |
| ========================================================= */ | |
| function generatePiano( | |
| key, | |
| progression, | |
| bars | |
| ){ | |
| const notes=[]; | |
| const offsets= | |
| progressionToOffsets( | |
| progression, | |
| progression.mode | |
| ); | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| const base=bar*BAR; | |
| const index= | |
| bar%offsets.length; | |
| const c= | |
| makeChord( | |
| key.root, | |
| offsets[index], | |
| progression.mode, | |
| progression.roman[index] | |
| ); | |
| const section= | |
| Math.floor(bar/8)%4; | |
| if(section===0){ | |
| c.forEach(n=>{ | |
| addNote( | |
| notes, | |
| base, | |
| BAR*.8, | |
| n+48, | |
| 55 | |
| ); | |
| }); | |
| } | |
| else if(section===1){ | |
| [ | |
| 0, | |
| 1.5, | |
| 3 | |
| ].forEach(beat=>{ | |
| c.forEach((n,i)=>{ | |
| addNote( | |
| notes, | |
| base+beat*PPQ, | |
| PPQ*.45, | |
| n+48+ | |
| (i===2?12:0), | |
| randi(55,78) | |
| ); | |
| }); | |
| }); | |
| } | |
| else if(section===2){ | |
| for( | |
| let beat=0; | |
| beat<4; | |
| beat++ | |
| ){ | |
| c.forEach((n,i)=>{ | |
| addNote( | |
| notes, | |
| base+beat*PPQ, | |
| PPQ*.7, | |
| n+48+ | |
| (i===2?12:0), | |
| randi(70,92) | |
| ); | |
| }); | |
| } | |
| } | |
| else{ | |
| c.forEach(n=>{ | |
| addNote( | |
| notes, | |
| base, | |
| BAR*.9, | |
| n+48, | |
| 48 | |
| ); | |
| }); | |
| } | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| GUITAR | |
| ========================================================= */ | |
| function generateGuitar( | |
| key, | |
| progression, | |
| bars | |
| ){ | |
| const notes=[]; | |
| const offsets= | |
| progressionToOffsets( | |
| progression, | |
| progression.mode | |
| ); | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| const base=bar*BAR; | |
| const index= | |
| bar%offsets.length; | |
| const c= | |
| makeChord( | |
| key.root, | |
| offsets[index], | |
| progression.mode, | |
| progression.roman[index] | |
| ); | |
| [ | |
| .5, | |
| 1.5, | |
| 2.5, | |
| 3.5 | |
| ].forEach(beat=>{ | |
| if( | |
| Math.random()<.68 | |
| ){ | |
| c.forEach((n,i)=>{ | |
| addNote( | |
| notes, | |
| base+beat*PPQ, | |
| PPQ*.18, | |
| n+52+ | |
| (i===2?12:0), | |
| randi(42,68) | |
| ); | |
| }); | |
| } | |
| }); | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| BASS | |
| ========================================================= */ | |
| function generateBass( | |
| key, | |
| progression, | |
| bars, | |
| style | |
| ){ | |
| const notes=[]; | |
| const offsets= | |
| progressionToOffsets( | |
| progression, | |
| progression.mode | |
| ); | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| const base=bar*BAR; | |
| const index= | |
| bar%offsets.length; | |
| const root= | |
| key.root+ | |
| offsets[index]+ | |
| 36; | |
| let pattern; | |
| if( | |
| style.includes("Trap") || | |
| style.includes("Drill") || | |
| style==="Phonk" | |
| ){ | |
| pattern=[ | |
| 0, | |
| .75, | |
| 1.5, | |
| 2.5, | |
| 3.25 | |
| ]; | |
| }else{ | |
| pattern=[ | |
| 0, | |
| 1.5, | |
| 2.5, | |
| 3.5 | |
| ]; | |
| } | |
| pattern.forEach( | |
| (beat,i)=>{ | |
| if( | |
| Math.random()<.9 | |
| ){ | |
| let n=root; | |
| if( | |
| style.includes("Drill") && | |
| Math.random()<.25 | |
| ){ | |
| n+=7; | |
| } | |
| if( | |
| style.includes("Trap") && | |
| Math.random()<.2 | |
| ){ | |
| n+=12; | |
| } | |
| addNote( | |
| notes, | |
| base+beat*PPQ, | |
| PPQ* | |
| ( | |
| i%2 | |
| ?.5 | |
| :1 | |
| ), | |
| n, | |
| randi(72,105) | |
| ); | |
| } | |
| } | |
| ); | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| 808 | |
| ========================================================= */ | |
| function generate808( | |
| key, | |
| progression, | |
| bars, | |
| style | |
| ){ | |
| const notes=[]; | |
| const offsets= | |
| progressionToOffsets( | |
| progression, | |
| progression.mode | |
| ); | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| const base=bar*BAR; | |
| const index= | |
| bar%offsets.length; | |
| const root= | |
| key.root+ | |
| offsets[index]+ | |
| 24; | |
| let pattern; | |
| if( | |
| style.includes("Drill") | |
| ){ | |
| pattern=[ | |
| 0, | |
| .75, | |
| 1.5, | |
| 2.25, | |
| 3, | |
| 3.5 | |
| ]; | |
| }else{ | |
| pattern=[ | |
| 0, | |
| 1.5, | |
| 2.5, | |
| 3 | |
| ]; | |
| } | |
| pattern.forEach( | |
| (beat,i)=>{ | |
| if( | |
| Math.random()<.93 | |
| ){ | |
| let n=root; | |
| if( | |
| Math.random()<.25 | |
| ){ | |
| n+=12; | |
| } | |
| if( | |
| ( | |
| style.includes("Trap") || | |
| style.includes("Drill") | |
| ) && | |
| Math.random()<.25 | |
| ){ | |
| n+=7; | |
| } | |
| addNote( | |
| notes, | |
| base+beat*PPQ, | |
| PPQ* | |
| ( | |
| i%2 | |
| ?.48 | |
| :1.05 | |
| ), | |
| n, | |
| randi(90,120) | |
| ); | |
| } | |
| } | |
| ); | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| DRUMS | |
| ========================================================= */ | |
| function generateDrums( | |
| bars, | |
| style | |
| ){ | |
| const kick=[]; | |
| const snare=[]; | |
| const hat=[]; | |
| const perc=[]; | |
| let type="boom"; | |
| if( | |
| style.includes("Drill") | |
| ){ | |
| type="drill"; | |
| } | |
| else if( | |
| style.includes("Trap") | |
| ){ | |
| type="trap"; | |
| } | |
| else if( | |
| style.includes("Phonk") | |
| ){ | |
| type="phonk"; | |
| } | |
| else if( | |
| style.includes("Lo-Fi") || | |
| style==="Chillhop" | |
| ){ | |
| type="lofi"; | |
| } | |
| else if( | |
| style==="West Coast" || | |
| style==="G-Funk" | |
| ){ | |
| type="west"; | |
| } | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| const base=bar*BAR; | |
| let kicks; | |
| let snares; | |
| let hats; | |
| if(type==="trap"){ | |
| kicks=[ | |
| 0, | |
| .75, | |
| 1.5, | |
| 2.5, | |
| 3, | |
| 3.5 | |
| ]; | |
| snares=[2]; | |
| hats=[]; | |
| for( | |
| let i=0; | |
| i<16; | |
| i++ | |
| ){ | |
| hats.push(i*.25); | |
| } | |
| } | |
| else if(type==="drill"){ | |
| kicks=[ | |
| 0, | |
| .75, | |
| 1.75, | |
| 2.5, | |
| 3.25, | |
| 3.75 | |
| ]; | |
| snares=[ | |
| 1, | |
| 3 | |
| ]; | |
| hats=[]; | |
| for( | |
| let i=0; | |
| i<16; | |
| i++ | |
| ){ | |
| hats.push(i*.25); | |
| } | |
| } | |
| else if(type==="phonk"){ | |
| kicks=[ | |
| 0, | |
| .5, | |
| 1.5, | |
| 2.5, | |
| 3.25 | |
| ]; | |
| snares=[ | |
| 1, | |
| 3 | |
| ]; | |
| hats=[]; | |
| for( | |
| let i=0; | |
| i<16; | |
| i++ | |
| ){ | |
| hats.push(i*.25); | |
| } | |
| } | |
| else if(type==="lofi"){ | |
| kicks=[ | |
| 0, | |
| 1.5, | |
| 2.75 | |
| ]; | |
| snares=[ | |
| 1, | |
| 3 | |
| ]; | |
| hats=[ | |
| 0, | |
| .5, | |
| 1, | |
| 1.5, | |
| 2, | |
| 2.5, | |
| 3, | |
| 3.5 | |
| ]; | |
| } | |
| else{ | |
| kicks=[ | |
| 0, | |
| .75, | |
| 2.5, | |
| 3.25 | |
| ]; | |
| snares=[ | |
| 1, | |
| 3 | |
| ]; | |
| hats=[ | |
| 0, | |
| .5, | |
| 1, | |
| 1.5, | |
| 2, | |
| 2.5, | |
| 3, | |
| 3.5 | |
| ]; | |
| } | |
| kicks.forEach( | |
| beat=>{ | |
| if( | |
| Math.random()<.95 | |
| ){ | |
| addNote( | |
| kick, | |
| base+beat*PPQ, | |
| 70, | |
| 36, | |
| randi(95,120), | |
| 9 | |
| ); | |
| } | |
| } | |
| ); | |
| snares.forEach( | |
| beat=>{ | |
| addNote( | |
| snare, | |
| base+beat*PPQ, | |
| 70, | |
| 38, | |
| randi(88,115), | |
| 9 | |
| ); | |
| } | |
| ); | |
| hats.forEach( | |
| (beat,i)=>{ | |
| if( | |
| Math.random()<.82 | |
| ){ | |
| addNote( | |
| hat, | |
| base+beat*PPQ, | |
| 35, | |
| 42, | |
| i%4===0 | |
| ?randi(65,88) | |
| :randi(42,72), | |
| 9 | |
| ); | |
| } | |
| } | |
| ); | |
| if( | |
| Math.random()<.5 | |
| ){ | |
| addNote( | |
| perc, | |
| base+1.75*PPQ, | |
| 50, | |
| 39, | |
| randi(45,75), | |
| 9 | |
| ); | |
| } | |
| } | |
| return { | |
| kick, | |
| snare, | |
| hat, | |
| perc | |
| }; | |
| } | |
| /* ========================================================= | |
| MELODY | |
| ========================================================= */ | |
| function generateMelody( | |
| key, | |
| progression, | |
| bars, | |
| style | |
| ){ | |
| const notes=[]; | |
| const scale= | |
| progression.mode==="minor" | |
| ? | |
| [0,2,3,5,7,8,10] | |
| : | |
| [0,2,4,5,7,9,11]; | |
| let previous= | |
| key.root+72; | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| const base=bar*BAR; | |
| const section= | |
| Math.floor(bar/8)%4; | |
| const density= | |
| section===2 | |
| ?.8 | |
| :section===1 | |
| ?.42 | |
| :.5; | |
| const count= | |
| style.includes("Drill") | |
| ?4 | |
| :5; | |
| for( | |
| let i=0; | |
| i<count; | |
| i++ | |
| ){ | |
| if( | |
| Math.random()>density | |
| ){ | |
| continue; | |
| } | |
| const beat= | |
| i*(4/count); | |
| let note= | |
| key.root+ | |
| 60+ | |
| pick(scale); | |
| /* | |
| Control melodic jumps. | |
| */ | |
| if( | |
| Math.abs(note-previous)>10 | |
| ){ | |
| note= | |
| previous+ | |
| ( | |
| Math.random()<.5 | |
| ?2 | |
| :-2 | |
| ); | |
| } | |
| previous=note; | |
| addNote( | |
| notes, | |
| base+beat*PPQ, | |
| PPQ*(4/count)*.6, | |
| note, | |
| section===2 | |
| ?randi(68,98) | |
| :randi(50,82) | |
| ); | |
| } | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| HOOK | |
| ========================================================= */ | |
| function generateHook( | |
| key, | |
| progression, | |
| bars | |
| ){ | |
| const notes=[]; | |
| const scale= | |
| progression.mode==="minor" | |
| ? | |
| [0,3,5,7,10] | |
| : | |
| [0,2,4,7,9]; | |
| /* | |
| Create ONE motif and repeat it. | |
| This gives musical identity. | |
| */ | |
| const motif=[ | |
| pick(scale), | |
| pick(scale), | |
| pick(scale), | |
| pick(scale) | |
| ]; | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| /* | |
| Hook every 8 bars. | |
| */ | |
| if( | |
| bar%8>=4 | |
| ){ | |
| const base=bar*BAR; | |
| motif.forEach( | |
| (degree,i)=>{ | |
| addNote( | |
| notes, | |
| base+i*PPQ, | |
| PPQ*.68, | |
| key.root+ | |
| 72+ | |
| degree, | |
| randi(70,100) | |
| ); | |
| } | |
| ); | |
| } | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| STRINGS | |
| ========================================================= */ | |
| function generateStrings( | |
| key, | |
| progression, | |
| bars | |
| ){ | |
| const notes=[]; | |
| const offsets= | |
| progressionToOffsets( | |
| progression, | |
| progression.mode | |
| ); | |
| for( | |
| let bar=0; | |
| bar<bars; | |
| bar++ | |
| ){ | |
| if( | |
| bar%8!==0 | |
| ){ | |
| continue; | |
| } | |
| const index= | |
| bar%offsets.length; | |
| const c= | |
| makeChord( | |
| key.root, | |
| offsets[index], | |
| progression.mode, | |
| progression.roman[index] | |
| ); | |
| c.forEach(n=>{ | |
| addNote( | |
| notes, | |
| bar*BAR, | |
| BAR*3.7, | |
| n+60, | |
| randi(30,55) | |
| ); | |
| }); | |
| } | |
| return notes; | |
| } | |
| /* ========================================================= | |
| HUMANIZE | |
| ========================================================= */ | |
| function humanize( | |
| notes, | |
| amount, | |
| drums=false | |
| ){ | |
| const max= | |
| drums | |
| ?8 | |
| :16; | |
| const range= | |
| max* | |
| amount/100; | |
| notes.forEach( | |
| n=>{ | |
| const shift= | |
| rand( | |
| -range, | |
| range | |
| ); | |
| const length= | |
| n.end-n.start; | |
| n.start= | |
| Math.max( | |
| 0, | |
| Math.round( | |
| n.start+shift | |
| ) | |
| ); | |
| n.end= | |
| Math.max( | |
| n.start+1, | |
| Math.round( | |
| n.start+ | |
| length+ | |
| shift | |
| ) | |
| ); | |
| } | |
| ); | |
| } | |
| /* ========================================================= | |
| MIDI | |
| ========================================================= */ | |
| function write16(v){ | |
| return [ | |
| (v>>8)&255, | |
| v&255 | |
| ]; | |
| } | |
| function write32(v){ | |
| return [ | |
| (v>>>24)&255, | |
| (v>>>16)&255, | |
| (v>>>8)&255, | |
| v&255 | |
| ]; | |
| } | |
| function writeVLQ(value){ | |
| value=Math.max( | |
| 0, | |
| Math.floor(value) | |
| ); | |
| let buffer= | |
| value&0x7f; | |
| const bytes=[]; | |
| while( | |
| (value>>=7) | |
| ){ | |
| buffer<<=8; | |
| buffer|= | |
| (value&0x7f)|0x80; | |
| } | |
| while(true){ | |
| bytes.push( | |
| buffer&255 | |
| ); | |
| if( | |
| buffer&0x80 | |
| ){ | |
| buffer>>=8; | |
| }else{ | |
| break; | |
| } | |
| } | |
| return bytes; | |
| } | |
| /* ========================================================= | |
| MIDI TRACK | |
| ========================================================= */ | |
| function makeTrack( | |
| notes, | |
| name, | |
| program | |
| ){ | |
| const events=[]; | |
| const nameBytes= | |
| Array.from(name) | |
| .map( | |
| c=>c.charCodeAt(0)&255 | |
| ); | |
| events.push({ | |
| tick:0, | |
| priority:0, | |
| bytes:[ | |
| 0xff, | |
| 0x03, | |
| nameBytes.length, | |
| ...nameBytes | |
| ] | |
| }); | |
| if( | |
| program!==null | |
| ){ | |
| events.push({ | |
| tick:0, | |
| priority:1, | |
| bytes:[ | |
| 0xc0, | |
| program&127 | |
| ] | |
| }); | |
| } | |
| notes.forEach(n=>{ | |
| events.push({ | |
| tick:n.start, | |
| priority:2, | |
| bytes:[ | |
| 0x90+n.channel, | |
| n.note, | |
| n.velocity | |
| ] | |
| }); | |
| events.push({ | |
| tick:n.end, | |
| priority:1, | |
| bytes:[ | |
| 0x80+n.channel, | |
| n.note, | |
| 0 | |
| ] | |
| }); | |
| }); | |
| events.sort( | |
| (a,b)=>{ | |
| if( | |
| a.tick!==b.tick | |
| ){ | |
| return a.tick-b.tick; | |
| } | |
| return a.priority-b.priority; | |
| } | |
| ); | |
| const data=[]; | |
| let previous=0; | |
| events.forEach(e=>{ | |
| data.push( | |
| ...writeVLQ( | |
| e.tick-previous | |
| ) | |
| ); | |
| data.push( | |
| ...e.bytes | |
| ); | |
| previous=e.tick; | |
| }); | |
| data.push( | |
| 0, | |
| 0xff, | |
| 0x2f, | |
| 0 | |
| ); | |
| return data; | |
| } | |
| /* ========================================================= | |
| TEMPO TRACK | |
| ========================================================= */ | |
| function tempoTrack(bpm){ | |
| const data=[]; | |
| const tempo= | |
| Math.round( | |
| 60000000/bpm | |
| ); | |
| const name=[ | |
| 0x54, | |
| 0x65, | |
| 0x6d, | |
| 0x70, | |
| 0x6f | |
| ]; | |
| data.push( | |
| 0, | |
| 0xff, | |
| 0x03, | |
| 5, | |
| ...name | |
| ); | |
| data.push( | |
| 0, | |
| 0xff, | |
| 0x51, | |
| 3, | |
| (tempo>>16)&255, | |
| (tempo>>8)&255, | |
| tempo&255 | |
| ); | |
| data.push( | |
| 0, | |
| 0xff, | |
| 0x58, | |
| 4, | |
| 4, | |
| 2, | |
| 24, | |
| 8 | |
| ); | |
| data.push( | |
| 0, | |
| 0xff, | |
| 0x2f, | |
| 0 | |
| ); | |
| return data; | |
| } | |
| /* ========================================================= | |
| TYPE 1 | |
| ========================================================= */ | |
| function buildType1( | |
| tracks, | |
| bpm | |
| ){ | |
| const chunks=[]; | |
| chunks.push( | |
| tempoTrack(bpm) | |
| ); | |
| tracks.forEach( | |
| t=>{ | |
| chunks.push( | |
| makeTrack( | |
| t.notes, | |
| t.name, | |
| t.program | |
| ) | |
| ); | |
| } | |
| ); | |
| const header=[ | |
| 0x4d, | |
| 0x54, | |
| 0x68, | |
| 0x64, | |
| 0, | |
| 0, | |
| 0, | |
| 6, | |
| 0, | |
| 1, | |
| ...write16( | |
| chunks.length | |
| ), | |
| ...write16(PPQ) | |
| ]; | |
| const output=[ | |
| ...header | |
| ]; | |
| chunks.forEach( | |
| chunk=>{ | |
| output.push( | |
| 0x4d, | |
| 0x54, | |
| 0x72, | |
| 0x6b | |
| ); | |
| output.push( | |
| ...write32( | |
| chunk.length | |
| ) | |
| ); | |
| output.push( | |
| ...chunk | |
| ); | |
| } | |
| ); | |
| return new Uint8Array( | |
| output | |
| ); | |
| } | |
| /* ========================================================= | |
| TYPE 0 | |
| ========================================================= */ | |
| function buildType0( | |
| tracks, | |
| bpm | |
| ){ | |
| const events=[]; | |
| const tempo= | |
| Math.round( | |
| 60000000/bpm | |
| ); | |
| events.push({ | |
| tick:0, | |
| priority:0, | |
| bytes:[ | |
| 0xff, | |
| 0x51, | |
| 0x03, | |
| (tempo>>16)&255, | |
| (tempo>>8)&255, | |
| tempo&255 | |
| ] | |
| }); | |
| events.push({ | |
| tick:0, | |
| priority:0, | |
| bytes:[ | |
| 0xff, | |
| 0x58, | |
| 0x04, | |
| 4, | |
| 2, | |
| 24, | |
| 8 | |
| ] | |
| }); | |
| tracks.forEach( | |
| t=>{ | |
| t.notes.forEach( | |
| n=>{ | |
| events.push({ | |
| tick:n.start, | |
| priority:2, | |
| bytes:[ | |
| 0x90+n.channel, | |
| n.note, | |
| n.velocity | |
| ] | |
| }); | |
| events.push({ | |
| tick:n.end, | |
| priority:1, | |
| bytes:[ | |
| 0x80+n.channel, | |
| n.note, | |
| 0 | |
| ] | |
| }); | |
| } | |
| ); | |
| } | |
| ); | |
| events.sort( | |
| (a,b)=>{ | |
| if( | |
| a.tick!==b.tick | |
| ){ | |
| return a.tick-b.tick; | |
| } | |
| return a.priority-b.priority; | |
| } | |
| ); | |
| const data=[]; | |
| let previous=0; | |
| events.forEach( | |
| e=>{ | |
| data.push( | |
| ...writeVLQ( | |
| e.tick-previous | |
| ) | |
| ); | |
| data.push( | |
| ...e.bytes | |
| ); | |
| previous=e.tick; | |
| } | |
| ); | |
| data.push( | |
| 0, | |
| 0xff, | |
| 0x2f, | |
| 0 | |
| ); | |
| const header=[ | |
| 0x4d, | |
| 0x54, | |
| 0x68, | |
| 0x64, | |
| 0, | |
| 0, | |
| 0, | |
| 6, | |
| 0, | |
| 0, | |
| 0, | |
| 1, | |
| ...write16(PPQ) | |
| ]; | |
| return new Uint8Array([ | |
| ...header, | |
| 0x4d, | |
| 0x54, | |
| 0x72, | |
| 0x6b, | |
| ...write32( | |
| data.length | |
| ), | |
| ...data | |
| ]); | |
| } | |
| /* ========================================================= | |
| UI | |
| ========================================================= */ | |
| function status(text){ | |
| document.getElementById( | |
| "status" | |
| ).innerHTML=text; | |
| } | |
| function progress(value){ | |
| document.getElementById( | |
| "progressBar" | |
| ).style.width= | |
| value+"%"; | |
| } | |
| /* ========================================================= | |
| PROGRESSION INFO | |
| ========================================================= */ | |
| function showProgressionInfo(){ | |
| const id= | |
| document.getElementById( | |
| "progressionSelect" | |
| ).value; | |
| if(id==="RANDOM"){ | |
| alert( | |
| "เธฃเธฐเธเธเธเธฐเธชเธธเนเธก Hit Progression เนเธฅเธฐเธเธฐเนเธกเนเนเธเน progression เนเธเธดเธกเธเนเธณเธเธเธเธงเนเธฒเธเธฐเธชเธธเนเธกเธเธฃเธเธเธธเธ" | |
| ); | |
| return; | |
| } | |
| const p= | |
| PROGRESSIONS[id]; | |
| alert( | |
| p.name+ | |
| "\n\n"+ | |
| id+ | |
| "\n\n"+ | |
| "Mode: "+ | |
| p.mode+ | |
| "\n"+ | |
| "BPM: "+ | |
| p.bpm[0]+ | |
| "-"+ | |
| p.bpm[1]+ | |
| "\n\n"+ | |
| "เนเธซเธกเธฒเธฐเธเธฑเธ: "+ | |
| p.moods.join(", ") | |
| ); | |
| } | |
| /* ========================================================= | |
| RANDOMIZE EVERYTHING | |
| ========================================================= */ | |
| function randomizeAll(){ | |
| const progression= | |
| getRandomProgression(); | |
| document.getElementById( | |
| "progressionSelect" | |
| ).value=progression; | |
| const styles= | |
| Object.keys(STYLES); | |
| let choices= | |
| styles.filter( | |
| x=>x!==lastStyle | |
| ); | |
| if(!choices.length){ | |
| choices=styles; | |
| } | |
| const style= | |
| pick(choices); | |
| lastStyle=style; | |
| document.getElementById( | |
| "style" | |
| ).value=style; | |
| document.getElementById( | |
| "prompt" | |
| ).value=""; | |
| generateMusic(); | |
| } | |
| /* ========================================================= | |
| MAIN GENERATOR | |
| ========================================================= */ | |
| async function generateMusic(){ | |
| try{ | |
| generatedTracks=[]; | |
| midiType1=null; | |
| midiType0=null; | |
| document.getElementById( | |
| "download1" | |
| ).style.display="none"; | |
| document.getElementById( | |
| "download0" | |
| ).style.display="none"; | |
| document.getElementById( | |
| "tracksCard" | |
| ).style.display="none"; | |
| progress(0); | |
| /* | |
| PROGRESSION | |
| */ | |
| let progressionId= | |
| document.getElementById( | |
| "progressionSelect" | |
| ).value; | |
| if( | |
| progressionId==="RANDOM" | |
| ){ | |
| progressionId= | |
| getRandomProgression(); | |
| } | |
| const progression= | |
| PROGRESSIONS[ | |
| progressionId | |
| ]; | |
| /* | |
| STYLE | |
| */ | |
| let style= | |
| document.getElementById( | |
| "style" | |
| ).value; | |
| if( | |
| style==="RANDOM" | |
| ){ | |
| const styles= | |
| Object.keys(STYLES); | |
| let choices= | |
| styles.filter( | |
| x=>x!==lastStyle | |
| ); | |
| if(!choices.length){ | |
| choices=styles; | |
| } | |
| style=pick(choices); | |
| } | |
| lastStyle=style; | |
| const styleConfig= | |
| STYLES[style]; | |
| /* | |
| KEY AUTO | |
| */ | |
| const key= | |
| randomKey( | |
| progression.mode | |
| ); | |
| /* | |
| BPM | |
| blend progression and style ranges | |
| */ | |
| const minBpm= | |
| Math.max( | |
| progression.bpm[0], | |
| styleConfig.bpm[0] | |
| ); | |
| const maxBpm= | |
| Math.min( | |
| progression.bpm[1], | |
| styleConfig.bpm[1] | |
| ); | |
| let bpm; | |
| if( | |
| minBpm<=maxBpm | |
| ){ | |
| bpm= | |
| randi( | |
| minBpm, | |
| maxBpm | |
| ); | |
| }else{ | |
| bpm= | |
| randi( | |
| styleConfig.bpm[0], | |
| styleConfig.bpm[1] | |
| ); | |
| } | |
| /* | |
| DURATION | |
| */ | |
| const duration= | |
| parseInt( | |
| document.getElementById( | |
| "duration" | |
| ).value | |
| ); | |
| const human= | |
| parseInt( | |
| document.getElementById( | |
| "human" | |
| ).value | |
| ); | |
| const secondsPerBar= | |
| 240/bpm; | |
| const bars= | |
| Math.max( | |
| 1, | |
| Math.round( | |
| duration/ | |
| secondsPerBar | |
| ) | |
| ); | |
| /* | |
| Prompt | |
| */ | |
| let prompt= | |
| document.getElementById( | |
| "prompt" | |
| ).value.trim(); | |
| if(!prompt){ | |
| prompt= | |
| progression.name+ | |
| ", "+ | |
| style+ | |
| ", "+ | |
| pick([ | |
| "dark and catchy", | |
| "emotional and cinematic", | |
| "confident and energetic", | |
| "late-night and hypnotic", | |
| "street but polished", | |
| "melodic and atmospheric" | |
| ])+ | |
| ", punchy drums, controlled low end, memorable hook, modern polished production."; | |
| } | |
| /* | |
| BLUEPRINT | |
| */ | |
| document.getElementById( | |
| "blueprint" | |
| ).innerHTML= | |
| `<div class="highlight"> | |
| ${progression.name} | |
| </div>`+ | |
| `<span class="badge"> | |
| ${progressionId} | |
| </span>`+ | |
| `<span class="badge"> | |
| ${style} | |
| </span>`+ | |
| `<span class="badge"> | |
| ${key.name} | |
| </span>`+ | |
| `<span class="badge"> | |
| ${bpm} BPM | |
| </span>`+ | |
| `<span class="badge"> | |
| ${bars} Bars | |
| </span>`+ | |
| `<br><br>`+ | |
| `<strong>Creative Direction</strong><br>`+ | |
| prompt; | |
| status( | |
| "๐ง Building song architecture..." | |
| ); | |
| await wait(60); | |
| /* PIANO */ | |
| status( | |
| "๐น Generating progression-based piano..." | |
| ); | |
| const piano= | |
| generatePiano( | |
| key, | |
| progression, | |
| bars | |
| ); | |
| humanize( | |
| piano, | |
| human | |
| ); | |
| generatedTracks.push({ | |
| name:"Piano", | |
| notes:piano, | |
| program:0 | |
| }); | |
| progress(12); | |
| /* GUITAR */ | |
| status( | |
| "๐ธ Generating guitar..." | |
| ); | |
| const guitar= | |
| generateGuitar( | |
| key, | |
| progression, | |
| bars | |
| ); | |
| humanize( | |
| guitar, | |
| human*.8 | |
| ); | |
| generatedTracks.push({ | |
| name:"Guitar", | |
| notes:guitar, | |
| program:27 | |
| }); | |
| progress(22); | |
| /* BASS */ | |
| status( | |
| "๐ต Generating bass from progression..." | |
| ); | |
| const bass= | |
| generateBass( | |
| key, | |
| progression, | |
| bars, | |
| style | |
| ); | |
| humanize( | |
| bass, | |
| human*.6 | |
| ); | |
| generatedTracks.push({ | |
| name:"Bass", | |
| notes:bass, | |
| program:33 | |
| }); | |
| progress(32); | |
| /* 808 */ | |
| status( | |
| "๐ Generating 808..." | |
| ); | |
| const eight08= | |
| generate808( | |
| key, | |
| progression, | |
| bars, | |
| style | |
| ); | |
| humanize( | |
| eight08, | |
| human*.35 | |
| ); | |
| generatedTracks.push({ | |
| name:"808", | |
| notes:eight08, | |
| program:38 | |
| }); | |
| progress(42); | |
| /* DRUMS */ | |
| status( | |
| "๐ฅ Generating style-compatible drums..." | |
| ); | |
| const drums= | |
| generateDrums( | |
| bars, | |
| style | |
| ); | |
| humanize( | |
| drums.kick, | |
| human*.2, | |
| true | |
| ); | |
| humanize( | |
| drums.snare, | |
| human*.2, | |
| true | |
| ); | |
| humanize( | |
| drums.hat, | |
| human*.45, | |
| true | |
| ); | |
| humanize( | |
| drums.perc, | |
| human*.45, | |
| true | |
| ); | |
| generatedTracks.push({ | |
| name:"Kick", | |
| notes:drums.kick, | |
| program:null | |
| }); | |
| generatedTracks.push({ | |
| name:"Snare", | |
| notes:drums.snare, | |
| program:null | |
| }); | |
| generatedTracks.push({ | |
| name:"Hi-Hat", | |
| notes:drums.hat, | |
| program:null | |
| }); | |
| generatedTracks.push({ | |
| name:"Percussion", | |
| notes:drums.perc, | |
| program:null | |
| }); | |
| progress(58); | |
| /* STRINGS */ | |
| status( | |
| "๐ป Adding harmonic atmosphere..." | |
| ); | |
| const strings= | |
| generateStrings( | |
| key, | |
| progression, | |
| bars | |
| ); | |
| humanize( | |
| strings, | |
| human*.5 | |
| ); | |
| generatedTracks.push({ | |
| name:"Strings", | |
| notes:strings, | |
| program:48 | |
| }); | |
| progress(68); | |
| /* LEAD */ | |
| status( | |
| "๐น Generating melody..." | |
| ); | |
| const lead= | |
| generateMelody( | |
| key, | |
| progression, | |
| bars, | |
| style | |
| ); | |
| humanize( | |
| lead, | |
| human | |
| ); | |
| generatedTracks.push({ | |
| name:"Lead", | |
| notes:lead, | |
| program:81 | |
| }); | |
| progress(79); | |
| /* HOOK */ | |
| status( | |
| "๐ถ Generating memorable hook..." | |
| ); | |
| const hook= | |
| generateHook( | |
| key, | |
| progression, | |
| bars | |
| ); | |
| humanize( | |
| hook, | |
| human*.65 | |
| ); | |
| generatedTracks.push({ | |
| name:"Hook", | |
| notes:hook, | |
| program:80 | |
| }); | |
| progress(88); | |
| /* MIDI */ | |
| status( | |
| "๐ฟ Building MIDI Type 1..." | |
| ); | |
| midiType1= | |
| buildType1( | |
| generatedTracks, | |
| bpm | |
| ); | |
| progress(94); | |
| status( | |
| "๐ฟ Building MIDI Type 0..." | |
| ); | |
| midiType0= | |
| buildType0( | |
| generatedTracks, | |
| bpm | |
| ); | |
| progress(100); | |
| /* | |
| STATS | |
| */ | |
| const map={ | |
| Piano:"pianoInfo", | |
| Guitar:"guitarInfo", | |
| Bass:"bassInfo", | |
| "808":"808Info", | |
| Kick:"kickInfo", | |
| Snare:"snareInfo", | |
| "Hi-Hat":"hatInfo", | |
| Percussion:"percInfo", | |
| Strings:"stringsInfo", | |
| Lead:"leadInfo", | |
| Hook:"hookInfo" | |
| }; | |
| generatedTracks.forEach( | |
| track=>{ | |
| const el= | |
| document.getElementById( | |
| map[track.name] | |
| ); | |
| if(el){ | |
| el.textContent= | |
| track.notes.length+ | |
| " notes"; | |
| } | |
| } | |
| ); | |
| document.getElementById( | |
| "tracksCard" | |
| ).style.display="block"; | |
| document.getElementById( | |
| "download1" | |
| ).style.display="block"; | |
| document.getElementById( | |
| "download0" | |
| ).style.display="block"; | |
| const actualDuration= | |
| bars*secondsPerBar; | |
| status( | |
| `โ GENERATION COMPLETE<br><br>`+ | |
| `๐ฅ Progression: ${progressionId}<br>`+ | |
| `๐ผ ${progression.name}<br>`+ | |
| `๐ค Style: ${style}<br>`+ | |
| `๐น Key: ${key.name}<br>`+ | |
| `โฑ BPM: ${bpm}<br>`+ | |
| `๐ Bars: ${bars}<br>`+ | |
| `โณ Length: ${actualDuration.toFixed(1)} sec<br>`+ | |
| `๐ Tracks: ${generatedTracks.length}<br>`+ | |
| `๐ Humanization: ${human}%<br><br>`+ | |
| `เธเธฃเนเธญเธก Import เนเธเนเธฒ Revolution Audio` | |
| ); | |
| }catch(error){ | |
| console.error(error); | |
| status( | |
| "โ ERROR: "+ | |
| error.message | |
| ); | |
| } | |
| } | |
| /* ========================================================= | |
| DOWNLOAD | |
| ========================================================= */ | |
| function saveMidi( | |
| data, | |
| filename | |
| ){ | |
| const blob= | |
| new Blob( | |
| [data], | |
| { | |
| type:"audio/midi" | |
| } | |
| ); | |
| const url= | |
| URL.createObjectURL(blob); | |
| const a= | |
| document.createElement("a"); | |
| a.href=url; | |
| a.download=filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| setTimeout( | |
| ()=>{ | |
| URL.revokeObjectURL(url); | |
| }, | |
| 1000 | |
| ); | |
| } | |
| function safeName(){ | |
| const progression= | |
| lastProgression | |
| ||"Hit_Progression"; | |
| return progression | |
| .replace( | |
| /[^a-zA-Z0-9_-]/g, | |
| "_" | |
| ); | |
| } | |
| function downloadType1(){ | |
| if(!midiType1){ | |
| return; | |
| } | |
| saveMidi( | |
| midiType1, | |
| "AI_HipHop_"+ | |
| safeName()+ | |
| "_MULTI_TRACK.mid" | |
| ); | |
| } | |
| function downloadType0(){ | |
| if(!midiType0){ | |
| return; | |
| } | |
| saveMidi( | |
| midiType0, | |
| "AI_HipHop_"+ | |
| safeName()+ | |
| "_COMPATIBILITY.mid" | |
| ); | |
| } | |
| /* ========================================================= | |
| START | |
| ========================================================= */ | |
| refillProgressionBag(); | |
| </script> | |
| </body> | |
| </html> |