Midi-Simple-Generator / index.html
Kkcd106's picture
Update index.html
d7217d5 verified
Raw History Blame Contribute Delete
59.3 kB
<!DOCTYPE html>
<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>