Update index.html
Browse files- index.html +657 -19
index.html
CHANGED
@@ -1,19 +1,657 @@
|
|
1 |
-
<!
|
2 |
-
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Advanced MIDI Melody Generator</title>
|
7 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
|
8 |
+
<style>
|
9 |
+
:root {
|
10 |
+
--primary: #2c3e50;
|
11 |
+
--secondary: #3498db;
|
12 |
+
--accent: #e74c3c;
|
13 |
+
--background: #f5f6fa;
|
14 |
+
--surface: #ffffff;
|
15 |
+
}
|
16 |
+
|
17 |
+
* {
|
18 |
+
margin: 0;
|
19 |
+
padding: 0;
|
20 |
+
box-sizing: border-box;
|
21 |
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
22 |
+
}
|
23 |
+
|
24 |
+
body {
|
25 |
+
background: var(--background);
|
26 |
+
color: var(--primary);
|
27 |
+
min-height: 100vh;
|
28 |
+
padding: 2rem;
|
29 |
+
}
|
30 |
+
|
31 |
+
.container {
|
32 |
+
max-width: 1400px;
|
33 |
+
margin: 0 auto;
|
34 |
+
background: var(--surface);
|
35 |
+
border-radius: 16px;
|
36 |
+
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
37 |
+
padding: 2rem;
|
38 |
+
}
|
39 |
+
|
40 |
+
.workspace {
|
41 |
+
display: grid;
|
42 |
+
grid-template-columns: 350px 1fr;
|
43 |
+
gap: 2rem;
|
44 |
+
}
|
45 |
+
|
46 |
+
.panel {
|
47 |
+
background: #f8f9fa;
|
48 |
+
border-radius: 12px;
|
49 |
+
padding: 1.5rem;
|
50 |
+
height: fit-content;
|
51 |
+
}
|
52 |
+
|
53 |
+
.section {
|
54 |
+
margin-bottom: 2rem;
|
55 |
+
}
|
56 |
+
|
57 |
+
h2, h3 {
|
58 |
+
margin-bottom: 1rem;
|
59 |
+
color: var(--primary);
|
60 |
+
}
|
61 |
+
|
62 |
+
.control {
|
63 |
+
margin-bottom: 1rem;
|
64 |
+
}
|
65 |
+
|
66 |
+
label {
|
67 |
+
display: block;
|
68 |
+
margin-bottom: 0.5rem;
|
69 |
+
font-size: 0.9rem;
|
70 |
+
color: #666;
|
71 |
+
}
|
72 |
+
|
73 |
+
select, input[type="range"], input[type="number"] {
|
74 |
+
width: 100%;
|
75 |
+
padding: 0.5rem;
|
76 |
+
border: 1px solid #ddd;
|
77 |
+
border-radius: 6px;
|
78 |
+
background: white;
|
79 |
+
}
|
80 |
+
|
81 |
+
input[type="range"] {
|
82 |
+
-webkit-appearance: none;
|
83 |
+
height: 8px;
|
84 |
+
background: #ddd;
|
85 |
+
}
|
86 |
+
|
87 |
+
input[type="range"]::-webkit-slider-thumb {
|
88 |
+
-webkit-appearance: none;
|
89 |
+
width: 16px;
|
90 |
+
height: 16px;
|
91 |
+
background: var(--secondary);
|
92 |
+
border-radius: 50%;
|
93 |
+
cursor: pointer;
|
94 |
+
}
|
95 |
+
|
96 |
+
.editor {
|
97 |
+
display: flex;
|
98 |
+
flex-direction: column;
|
99 |
+
gap: 1rem;
|
100 |
+
}
|
101 |
+
|
102 |
+
.piano-roll {
|
103 |
+
background: #1a1a1a;
|
104 |
+
border-radius: 12px;
|
105 |
+
height: 500px;
|
106 |
+
position: relative;
|
107 |
+
overflow: hidden;
|
108 |
+
transform: scaleY(-1);
|
109 |
+
}
|
110 |
+
|
111 |
+
.grid {
|
112 |
+
position: absolute;
|
113 |
+
inset: 0;
|
114 |
+
display: grid;
|
115 |
+
grid-template-columns: repeat(32, 1fr);
|
116 |
+
grid-template-rows: repeat(88, 1fr);
|
117 |
+
gap: 1px;
|
118 |
+
padding: 1px;
|
119 |
+
background: #2a2a2a;
|
120 |
+
}
|
121 |
+
|
122 |
+
.cell {
|
123 |
+
background: #333;
|
124 |
+
cursor: pointer;
|
125 |
+
transition: all 0.1s ease;
|
126 |
+
}
|
127 |
+
|
128 |
+
.cell.white-key {
|
129 |
+
background: #444;
|
130 |
+
}
|
131 |
+
|
132 |
+
.cell.black-key {
|
133 |
+
background: #222;
|
134 |
+
}
|
135 |
+
|
136 |
+
.cell:hover {
|
137 |
+
background: #555;
|
138 |
+
}
|
139 |
+
|
140 |
+
.cell.active {
|
141 |
+
background: var(--secondary);
|
142 |
+
}
|
143 |
+
|
144 |
+
.transport {
|
145 |
+
display: flex;
|
146 |
+
gap: 1rem;
|
147 |
+
padding: 1rem;
|
148 |
+
background: #f8f9fa;
|
149 |
+
border-radius: 12px;
|
150 |
+
}
|
151 |
+
|
152 |
+
button {
|
153 |
+
padding: 0.8rem 1.5rem;
|
154 |
+
border: none;
|
155 |
+
border-radius: 6px;
|
156 |
+
font-weight: 600;
|
157 |
+
cursor: pointer;
|
158 |
+
transition: all 0.2s;
|
159 |
+
color: white;
|
160 |
+
}
|
161 |
+
|
162 |
+
.btn-primary { background: var(--primary); }
|
163 |
+
.btn-secondary { background: var(--secondary); }
|
164 |
+
.btn-accent { background: var(--accent); }
|
165 |
+
|
166 |
+
button:hover {
|
167 |
+
opacity: 0.9;
|
168 |
+
transform: translateY(-1px);
|
169 |
+
}
|
170 |
+
|
171 |
+
.synth-controls {
|
172 |
+
display: grid;
|
173 |
+
grid-template-columns: repeat(2, 1fr);
|
174 |
+
gap: 1rem;
|
175 |
+
margin-bottom: 1rem;
|
176 |
+
}
|
177 |
+
|
178 |
+
.wave-selector {
|
179 |
+
display: flex;
|
180 |
+
gap: 0.5rem;
|
181 |
+
flex-wrap: wrap;
|
182 |
+
}
|
183 |
+
|
184 |
+
.wave-btn {
|
185 |
+
padding: 0.5rem 1rem;
|
186 |
+
background: white;
|
187 |
+
border: 1px solid #ddd;
|
188 |
+
border-radius: 20px;
|
189 |
+
color: #666;
|
190 |
+
cursor: pointer;
|
191 |
+
font-size: 0.9rem;
|
192 |
+
}
|
193 |
+
|
194 |
+
.wave-btn.active {
|
195 |
+
background: var(--secondary);
|
196 |
+
color: white;
|
197 |
+
border-color: var(--secondary);
|
198 |
+
}
|
199 |
+
|
200 |
+
.chord-progression {
|
201 |
+
display: grid;
|
202 |
+
grid-template-columns: repeat(4, 1fr);
|
203 |
+
gap: 0.5rem;
|
204 |
+
margin-top: 1rem;
|
205 |
+
}
|
206 |
+
|
207 |
+
.chord-slot {
|
208 |
+
padding: 0.5rem;
|
209 |
+
text-align: center;
|
210 |
+
background: white;
|
211 |
+
border: 1px solid #ddd;
|
212 |
+
border-radius: 6px;
|
213 |
+
font-size: 0.9rem;
|
214 |
+
}
|
215 |
+
|
216 |
+
</style>
|
217 |
+
</head>
|
218 |
+
<body>
|
219 |
+
<div class="container">
|
220 |
+
<div class="workspace">
|
221 |
+
<div class="panel">
|
222 |
+
<div class="section">
|
223 |
+
<h3>Sound Design</h3>
|
224 |
+
<div class="wave-selector">
|
225 |
+
<div class="wave-btn active" data-wave="sine">Sine</div>
|
226 |
+
<div class="wave-btn" data-wave="square">Square</div>
|
227 |
+
<div class="wave-btn" data-wave="sawtooth">Saw</div>
|
228 |
+
<div class="wave-btn" data-wave="triangle">Triangle</div>
|
229 |
+
</div>
|
230 |
+
|
231 |
+
<div class="control">
|
232 |
+
<label>Filter Cutoff</label>
|
233 |
+
<input type="range" id="filterCutoff" min="20" max="20000" value="2000">
|
234 |
+
</div>
|
235 |
+
|
236 |
+
<div class="control">
|
237 |
+
<label>Resonance</label>
|
238 |
+
<input type="range" id="filterResonance" min="0" max="20" value="1">
|
239 |
+
</div>
|
240 |
+
</div>
|
241 |
+
|
242 |
+
<div class="section">
|
243 |
+
<h3>Music Theory</h3>
|
244 |
+
<div class="control">
|
245 |
+
<label>Key</label>
|
246 |
+
<select id="key">
|
247 |
+
<option value="C">C</option>
|
248 |
+
<option value="C#">C#/Db</option>
|
249 |
+
<option value="D">D</option>
|
250 |
+
<option value="D#">D#/Eb</option>
|
251 |
+
<option value="E">E</option>
|
252 |
+
<option value="F">F</option>
|
253 |
+
<option value="F#">F#/Gb</option>
|
254 |
+
<option value="G">G</option>
|
255 |
+
<option value="G#">G#/Ab</option>
|
256 |
+
<option value="A">A</option>
|
257 |
+
<option value="A#">A#/Bb</option>
|
258 |
+
<option value="B">B</option>
|
259 |
+
</select>
|
260 |
+
</div>
|
261 |
+
|
262 |
+
<div class="control">
|
263 |
+
<label>Mode/Scale</label>
|
264 |
+
<select id="scale">
|
265 |
+
<option value="major">Major</option>
|
266 |
+
<option value="minor">Natural Minor</option>
|
267 |
+
<option value="harmonicMinor">Harmonic Minor</option>
|
268 |
+
<option value="melodicMinor">Melodic Minor</option>
|
269 |
+
<option value="dorian">Dorian</option>
|
270 |
+
<option value="phrygian">Phrygian</option>
|
271 |
+
<option value="lydian">Lydian</option>
|
272 |
+
<option value="mixolydian">Mixolydian</option>
|
273 |
+
<option value="locrian">Locrian</option>
|
274 |
+
</select>
|
275 |
+
</div>
|
276 |
+
|
277 |
+
<div class="control">
|
278 |
+
<label>Chord Progression</label>
|
279 |
+
<select id="chordProgression">
|
280 |
+
<option value="I-IV-V-I">I-IV-V-I (Pop)</option>
|
281 |
+
<option value="ii-V-I">ii-V-I (Jazz)</option>
|
282 |
+
<option value="I-vi-IV-V">I-vi-IV-V (50s)</option>
|
283 |
+
<option value="I-V-vi-IV">I-V-vi-IV (Pop)</option>
|
284 |
+
<option value="vi-IV-I-V">vi-IV-I-V (Pop)</option>
|
285 |
+
</select>
|
286 |
+
<div class="chord-progression" id="progressionDisplay">
|
287 |
+
<div class="chord-slot">I</div>
|
288 |
+
<div class="chord-slot">IV</div>
|
289 |
+
<div class="chord-slot">V</div>
|
290 |
+
<div class="chord-slot">I</div>
|
291 |
+
</div>
|
292 |
+
</div>
|
293 |
+
</div>
|
294 |
+
|
295 |
+
<div class="section">
|
296 |
+
<h3>Rhythm</h3>
|
297 |
+
<div class="control">
|
298 |
+
<label>Tempo: <span id="tempo-value">120</span> BPM</label>
|
299 |
+
<input type="range" id="tempo" min="60" max="200" value="120">
|
300 |
+
</div>
|
301 |
+
|
302 |
+
<div class="control">
|
303 |
+
<label>Time Signature</label>
|
304 |
+
<select id="timeSignature">
|
305 |
+
<option value="4/4">4/4</option>
|
306 |
+
<option value="3/4">3/4</option>
|
307 |
+
<option value="6/8">6/8</option>
|
308 |
+
<option value="5/4">5/4</option>
|
309 |
+
</select>
|
310 |
+
</div>
|
311 |
+
|
312 |
+
<div class="control">
|
313 |
+
<label>Swing</label>
|
314 |
+
<input type="range" id="swing" min="0" max="100" value="0">
|
315 |
+
</div>
|
316 |
+
</div>
|
317 |
+
|
318 |
+
<div class="section">
|
319 |
+
<h3>Generation</h3>
|
320 |
+
<div class="control">
|
321 |
+
<label>Complexity: <span id="complexity-value">5</span></label>
|
322 |
+
<input type="range" id="complexity" min="1" max="10" value="5">
|
323 |
+
</div>
|
324 |
+
|
325 |
+
<div class="control">
|
326 |
+
<label>Base Octave: <span id="octave-value">4</span></label>
|
327 |
+
<input type="range" id="octave" min="2" max="6" value="4">
|
328 |
+
</div>
|
329 |
+
|
330 |
+
<div class="control">
|
331 |
+
<label>Melodic Direction</label>
|
332 |
+
<select id="direction">
|
333 |
+
<option value="ascending">Ascending</option>
|
334 |
+
<option value="descending">Descending</option>
|
335 |
+
<option value="mixed">Mixed</option>
|
336 |
+
</select>
|
337 |
+
</div>
|
338 |
+
</div>
|
339 |
+
</div>
|
340 |
+
|
341 |
+
<div class="editor">
|
342 |
+
<div class="piano-roll">
|
343 |
+
<div class="grid" id="grid"></div>
|
344 |
+
</div>
|
345 |
+
|
346 |
+
<div class="transport">
|
347 |
+
<button class="btn-primary" id="generate">Generate</button>
|
348 |
+
<button class="btn-secondary" id="play">Play</button>
|
349 |
+
<button class="btn-secondary" id="stop">Stop</button>
|
350 |
+
<button class="btn-accent" id="clear">Clear</button>
|
351 |
+
<button class="btn-accent" id="download">Download MIDI</button>
|
352 |
+
</div>
|
353 |
+
</div>
|
354 |
+
</div>
|
355 |
+
</div>
|
356 |
+
|
357 |
+
<script>
|
358 |
+
class AdvancedMelodyGenerator {
|
359 |
+
constructor() {
|
360 |
+
// Initialize Tone.js components
|
361 |
+
this.filter = new Tone.Filter({
|
362 |
+
type: "lowpass",
|
363 |
+
frequency: 2000,
|
364 |
+
Q: 1
|
365 |
+
}).toDestination();
|
366 |
+
|
367 |
+
this.synth = new Tone.PolySynth(Tone.Synth, {
|
368 |
+
oscillator: {
|
369 |
+
type: "sine"
|
370 |
+
},
|
371 |
+
envelope: {
|
372 |
+
attack: 0.05,
|
373 |
+
decay: 0.2,
|
374 |
+
sustain: 0.2,
|
375 |
+
release: 0.5
|
376 |
+
}
|
377 |
+
}).connect(this.filter);
|
378 |
+
|
379 |
+
this.sequence = [];
|
380 |
+
this.isPlaying = false;
|
381 |
+
this.currentPart = null;
|
382 |
+
this.initUI();
|
383 |
+
this.setupEventListeners();
|
384 |
+
}
|
385 |
+
|
386 |
+
initUI() {
|
387 |
+
const grid = document.getElementById('grid');
|
388 |
+
const notes = this.getAllNotes();
|
389 |
+
|
390 |
+
for (let i = 0; i < 88; i++) {
|
391 |
+
for (let j = 0; j < 32; j++) {
|
392 |
+
const cell = document.createElement('div');
|
393 |
+
cell.className = 'cell';
|
394 |
+
const noteName = notes[i].replace(/[0-9]/, '');
|
395 |
+
cell.classList.add(noteName.includes('#') ? 'black-key' : 'white-key');
|
396 |
+
cell.dataset.note = notes[i];
|
397 |
+
cell.dataset.time = j;
|
398 |
+
cell.onclick = () => this.toggleCell(cell);
|
399 |
+
grid.appendChild(cell);
|
400 |
+
}
|
401 |
+
}
|
402 |
+
|
403 |
+
document.querySelectorAll('input[type="range"]').forEach(input => {
|
404 |
+
const display = document.getElementById(`${input.id}-value`);
|
405 |
+
if (display) display.textContent = input.value;
|
406 |
+
});
|
407 |
+
}
|
408 |
+
|
409 |
+
getAllNotes() {
|
410 |
+
const notes = [];
|
411 |
+
const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
412 |
+
for (let octave = 0; octave < 8; octave++) {
|
413 |
+
noteNames.forEach(note => {
|
414 |
+
notes.push(`${note}${octave}`);
|
415 |
+
});
|
416 |
+
}
|
417 |
+
return notes;
|
418 |
+
}
|
419 |
+
|
420 |
+
setupEventListeners() {
|
421 |
+
document.getElementById('generate').onclick = () => this.generateMelody();
|
422 |
+
document.getElementById('play').onclick = () => this.togglePlay();
|
423 |
+
document.getElementById('stop').onclick = () => this.stop();
|
424 |
+
document.getElementById('clear').onclick = () => this.clearGrid();
|
425 |
+
document.getElementById('download').onclick = () => this.downloadMIDI();
|
426 |
+
|
427 |
+
document.querySelectorAll('.wave-btn').forEach(btn => {
|
428 |
+
btn.onclick = (e) => {
|
429 |
+
document.querySelectorAll('.wave-btn').forEach(b => b.classList.remove('active'));
|
430 |
+
e.target.classList.add('active');
|
431 |
+
this.updateSynthSettings('oscillator', { type: e.target.dataset.wave });
|
432 |
+
};
|
433 |
+
});
|
434 |
+
|
435 |
+
document.getElementById('filterCutoff').oninput = (e) => {
|
436 |
+
this.filter.frequency.value = e.target.value;
|
437 |
+
};
|
438 |
+
|
439 |
+
document.getElementById('filterResonance').oninput = (e) => {
|
440 |
+
this.filter.Q.value = e.target.value;
|
441 |
+
};
|
442 |
+
|
443 |
+
document.getElementById('chordProgression').onchange = (e) => {
|
444 |
+
this.updateChordDisplay(e.target.value);
|
445 |
+
};
|
446 |
+
}
|
447 |
+
|
448 |
+
updateChordDisplay(progression) {
|
449 |
+
const chords = progression.split('-');
|
450 |
+
const display = document.getElementById('progressionDisplay');
|
451 |
+
display.innerHTML = '';
|
452 |
+
chords.forEach(chord => {
|
453 |
+
const slot = document.createElement('div');
|
454 |
+
slot.className = 'chord-slot';
|
455 |
+
slot.textContent = chord;
|
456 |
+
display.appendChild(slot);
|
457 |
+
});
|
458 |
+
}
|
459 |
+
|
460 |
+
updateSynthSettings(param, value) {
|
461 |
+
this.synth.set({
|
462 |
+
[param]: value
|
463 |
+
});
|
464 |
+
}
|
465 |
+
|
466 |
+
getChordProgression(key, type) {
|
467 |
+
const progressions = {
|
468 |
+
'I-IV-V-I': ['I', 'IV', 'V', 'I'],
|
469 |
+
'ii-V-I': ['ii', 'V', 'I'],
|
470 |
+
'I-vi-IV-V': ['I', 'vi', 'IV', 'V'],
|
471 |
+
'I-V-vi-IV': ['I', 'V', 'vi', 'IV'],
|
472 |
+
'vi-IV-I-V': ['vi', 'IV', 'I', 'V']
|
473 |
+
};
|
474 |
+
|
475 |
+
const chordMap = {
|
476 |
+
'I': this.getChord(key, 1),
|
477 |
+
'ii': this.getChord(key, 2),
|
478 |
+
'iii': this.getChord(key, 3),
|
479 |
+
'IV': this.getChord(key, 4),
|
480 |
+
'V': this.getChord(key, 5),
|
481 |
+
'vi': this.getChord(key, 6),
|
482 |
+
'vii': this.getChord(key, 7)
|
483 |
+
};
|
484 |
+
|
485 |
+
return progressions[type].map(chord => chordMap[chord]);
|
486 |
+
}
|
487 |
+
|
488 |
+
getChord(root, degree) {
|
489 |
+
const scale = this.getScaleNotes(root, document.getElementById('scale').value);
|
490 |
+
const chordTones = [0, 2, 4].map(interval => scale[(degree - 1 + interval) % 7]);
|
491 |
+
return chordTones;
|
492 |
+
}
|
493 |
+
|
494 |
+
generateMelody() {
|
495 |
+
this.stop();
|
496 |
+
this.clearGrid();
|
497 |
+
|
498 |
+
const key = document.getElementById('key').value;
|
499 |
+
const scale = document.getElementById('scale').value;
|
500 |
+
const complexity = parseInt(document.getElementById('complexity').value);
|
501 |
+
const baseOctave = parseInt(document.getElementById('octave').value);
|
502 |
+
const progression = document.getElementById('chordProgression').value;
|
503 |
+
|
504 |
+
const chords = this.getChordProgression(key, progression);
|
505 |
+
this.sequence = this.createMelodySequence(key, scale, complexity, baseOctave, chords);
|
506 |
+
this.visualizeSequence();
|
507 |
+
}
|
508 |
+
|
509 |
+
createMelodySequence(key, scale, complexity, baseOctave, chords) {
|
510 |
+
const sequence = [];
|
511 |
+
const scaleNotes = this.getScaleNotes(key, scale);
|
512 |
+
const noteCount = Math.floor(complexity * 4);
|
513 |
+
const direction = document.getElementById('direction').value;
|
514 |
+
|
515 |
+
let previousNote = null;
|
516 |
+
for (let i = 0; i < noteCount; i++) {
|
517 |
+
const time = Math.floor(i * (32 / noteCount));
|
518 |
+
let note;
|
519 |
+
|
520 |
+
if (direction === 'ascending') {
|
521 |
+
note = scaleNotes[i % scaleNotes.length];
|
522 |
+
} else if (direction === 'descending') {
|
523 |
+
note = scaleNotes[scaleNotes.length - 1 - (i % scaleNotes.length)];
|
524 |
+
} else {
|
525 |
+
if (previousNote) {
|
526 |
+
const currentIndex = scaleNotes.indexOf(previousNote.replace(/[0-9]/, ''));
|
527 |
+
const step = Math.floor(Math.random() * 3) - 1; // -1, 0, or 1
|
528 |
+
const newIndex = Math.min(Math.max(0, currentIndex + step), scaleNotes.length - 1);
|
529 |
+
note = scaleNotes[newIndex];
|
530 |
+
} else {
|
531 |
+
note = scaleNotes[Math.floor(Math.random() * scaleNotes.length)];
|
532 |
+
}
|
533 |
+
}
|
534 |
+
|
535 |
+
const octaveOffset = Math.floor(Math.random() * 2);
|
536 |
+
const fullNote = `${note}${baseOctave + octaveOffset}`;
|
537 |
+
previousNote = fullNote;
|
538 |
+
|
539 |
+
sequence.push({
|
540 |
+
note: fullNote,
|
541 |
+
time: time,
|
542 |
+
duration: 0.25
|
543 |
+
});
|
544 |
+
}
|
545 |
+
|
546 |
+
return sequence;
|
547 |
+
}
|
548 |
+
|
549 |
+
visualizeSequence() {
|
550 |
+
this.sequence.forEach(note => {
|
551 |
+
const noteIndex = this.getNoteIndex(note.note);
|
552 |
+
const cell = document.querySelector(
|
553 |
+
`.cell[data-note="${note.note}"][data-time="${note.time}"]`
|
554 |
+
);
|
555 |
+
if (cell) cell.classList.add('active');
|
556 |
+
});
|
557 |
+
}
|
558 |
+
|
559 |
+
getNoteIndex(note) {
|
560 |
+
const notes = this.getAllNotes();
|
561 |
+
return notes.indexOf(note);
|
562 |
+
}
|
563 |
+
|
564 |
+
togglePlay() {
|
565 |
+
if (this.isPlaying) {
|
566 |
+
this.stop();
|
567 |
+
} else {
|
568 |
+
Tone.start().then(() => this.play());
|
569 |
+
}
|
570 |
+
}
|
571 |
+
|
572 |
+
play() {
|
573 |
+
this.isPlaying = true;
|
574 |
+
Tone.Transport.bpm.value = document.getElementById('tempo').value;
|
575 |
+
|
576 |
+
if (this.currentPart) {
|
577 |
+
this.currentPart.dispose();
|
578 |
+
}
|
579 |
+
|
580 |
+
this.currentPart = new Tone.Part(((time, note) => {
|
581 |
+
this.synth.triggerAttackRelease(note.note, note.duration, time);
|
582 |
+
}), this.sequence.map(note => ({
|
583 |
+
time: note.time * 0.25,
|
584 |
+
note: note.note,
|
585 |
+
duration: note.duration
|
586 |
+
}))).start(0);
|
587 |
+
|
588 |
+
Tone.Transport.start();
|
589 |
+
}
|
590 |
+
|
591 |
+
stop() {
|
592 |
+
this.isPlaying = false;
|
593 |
+
if (this.currentPart) {
|
594 |
+
this.currentPart.dispose();
|
595 |
+
}
|
596 |
+
Tone.Transport.stop();
|
597 |
+
}
|
598 |
+
|
599 |
+
toggleCell(cell) {
|
600 |
+
cell.classList.toggle('active');
|
601 |
+
// Update sequence based on UI state
|
602 |
+
const activeNotes = Array.from(document.querySelectorAll('.cell.active')).map(cell => ({
|
603 |
+
note: cell.dataset.note,
|
604 |
+
time: parseInt(cell.dataset.time),
|
605 |
+
duration: 0.25
|
606 |
+
}));
|
607 |
+
this.sequence = activeNotes;
|
608 |
+
}
|
609 |
+
|
610 |
+
clearGrid() {
|
611 |
+
document.querySelectorAll('.cell').forEach(cell => {
|
612 |
+
cell.classList.remove('active');
|
613 |
+
});
|
614 |
+
this.sequence = [];
|
615 |
+
}
|
616 |
+
|
617 |
+
getScaleNotes(key, scale) {
|
618 |
+
const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
|
619 |
+
const scales = {
|
620 |
+
major: [0, 2, 4, 5, 7, 9, 11],
|
621 |
+
minor: [0, 2, 3, 5, 7, 8, 10],
|
622 |
+
harmonicMinor: [0, 2, 3, 5, 7, 8, 11],
|
623 |
+
melodicMinor: [0, 2, 3, 5, 7, 9, 11],
|
624 |
+
dorian: [0, 2, 3, 5, 7, 9, 10],
|
625 |
+
phrygian: [0, 1, 3, 5, 7, 8, 10],
|
626 |
+
lydian: [0, 2, 4, 6, 7, 9, 11],
|
627 |
+
mixolydian: [0, 2, 4, 5, 7, 9, 10],
|
628 |
+
locrian: [0, 1, 3, 5, 6, 8, 10]
|
629 |
+
};
|
630 |
+
|
631 |
+
const keyIndex = notes.indexOf(key);
|
632 |
+
return scales[scale].map(interval => notes[(keyIndex + interval) % 12]);
|
633 |
+
}
|
634 |
+
|
635 |
+
downloadMIDI() {
|
636 |
+
const midiData = [
|
637 |
+
0x4D, 0x54, 0x68, 0x64,
|
638 |
+
0x00, 0x00, 0x00, 0x06,
|
639 |
+
0x00, 0x01,
|
640 |
+
0x00, 0x01,
|
641 |
+
0x01, 0x80
|
642 |
+
];
|
643 |
+
|
644 |
+
const blob = new Blob([new Uint8Array(midiData)], { type: 'audio/midi' });
|
645 |
+
const url = window.URL.createObjectURL(blob);
|
646 |
+
const a = document.createElement('a');
|
647 |
+
a.href = url;
|
648 |
+
a.download = 'melody.mid';
|
649 |
+
a.click();
|
650 |
+
window.URL.revokeObjectURL(url);
|
651 |
+
}
|
652 |
+
}
|
653 |
+
|
654 |
+
const generator = new AdvancedMelodyGenerator();
|
655 |
+
</script>
|
656 |
+
</body>
|
657 |
+
</html>
|