SREAL commited on
Commit
c031132
·
verified ·
1 Parent(s): 713398f

Upload 2 files

Browse files
Files changed (2) hide show
  1. MIDIGenerator.js +381 -0
  2. MS12 - Copy.html +0 -0
MIDIGenerator.js ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as Tonal from 'tonal';
2
+
3
+ export class MIDIGenerator {
4
+ constructor(container, getSynth) {
5
+ this.container = container;
6
+ this.getSynth = getSynth;
7
+ this.notes = []; // Array of {time, duration, note, velocity}
8
+ this.currentTime = 0;
9
+ this.isPlaying = false;
10
+ this.part = null;
11
+ this.playheadPosition = 0;
12
+ this.bars = 4;
13
+ this.barLength = 4; // beats per bar
14
+ this.totalBeats = this.bars * this.barLength;
15
+ this.pixelsPerBeat = 20;
16
+ this.pixelsPerSemitone = 8;
17
+ this.canvasWidth = 300;
18
+ this.canvasHeight = 200;
19
+ this.scrollX = 0;
20
+ this.scrollY = 60; // Start at C4
21
+ this.zoomX = 1;
22
+ this.zoomY = 1;
23
+
24
+ this.createUI();
25
+ this.generateMIDI();
26
+ }
27
+
28
+ createUI() {
29
+ this.container.innerHTML = `
30
+ <h3 class="panel-title">MIDI GENERATOR</h3>
31
+ <div class="midi-window">
32
+ <div class="midi-controls">
33
+ <div class="control-group">
34
+ <label>Key:</label>
35
+ <select id="midiKey">
36
+ <option value="C">C</option>
37
+ <option value="C#">C#</option>
38
+ <option value="D">D</option>
39
+ <option value="D#">D#</option>
40
+ <option value="E">E</option>
41
+ <option value="F">F</option>
42
+ <option value="F#">F#</option>
43
+ <option value="G">G</option>
44
+ <option value="G#">G#</option>
45
+ <option value="A">A</option>
46
+ <option value="A#">A#</option>
47
+ <option value="B">B</option>
48
+ </select>
49
+ </div>
50
+ <div class="control-group">
51
+ <label>Scale:</label>
52
+ <select id="midiScale">
53
+ <option value="major">Major</option>
54
+ <option value="minor">Minor</option>
55
+ <option value="dorian">Dorian</option>
56
+ <option value="phrygian">Phrygian</option>
57
+ <option value="lydian">Lydian</option>
58
+ <option value="mixolydian">Mixolydian</option>
59
+ <option value="aeolian">Aeolian</option>
60
+ <option value="locrian">Locrian</option>
61
+ </select>
62
+ </div>
63
+ <div class="control-group">
64
+ <label>Progression:</label>
65
+ <input type="text" id="midiProgression" value="I-IV-V-I" placeholder="I-IV-V-I">
66
+ </div>
67
+ <div class="control-group">
68
+ <label>Complexity:</label>
69
+ <input type="range" id="midiComplexity" min="1" max="10" value="5">
70
+ <span id="complexityValue">5</span>
71
+ </div>
72
+ <div class="control-group">
73
+ <label>Octave Range:</label>
74
+ <input type="number" id="minOctave" min="1" max="7" value="3"> -
75
+ <input type="number" id="maxOctave" min="1" max="7" value="5">
76
+ </div>
77
+ <div class="control-group">
78
+ <label>Bars:</label>
79
+ <input type="number" id="midiBars" min="1" max="16" value="4">
80
+ </div>
81
+ <div class="control-group">
82
+ <label>Style:</label>
83
+ <select id="midiStyle">
84
+ <option value="classical">Classical</option>
85
+ <option value="jazz">Jazz</option>
86
+ <option value="ambient">Ambient</option>
87
+ </select>
88
+ </div>
89
+ </div>
90
+ <div class="midi-generation-grid">
91
+ <button class="midi-generation-btn" id="generateBtn">Generate</button>
92
+ <button class="midi-generation-btn" id="clearBtn">Clear</button>
93
+ <button class="midi-generation-btn" id="playBtn">Play</button>
94
+ <button class="midi-generation-btn" id="stopBtn">Stop</button>
95
+ </div>
96
+ <div class="piano-roll-container">
97
+ <canvas class="piano-roll-canvas" width="${this.canvasWidth}" height="${this.canvasHeight}"></canvas>
98
+ </div>
99
+ </div>
100
+ `;
101
+
102
+ this.canvas = this.container.querySelector('.piano-roll-canvas');
103
+ this.ctx = this.canvas.getContext('2d');
104
+
105
+ this.bindEvents();
106
+ this.drawPianoRoll();
107
+ }
108
+
109
+ bindEvents() {
110
+ this.container.querySelector('#generateBtn').addEventListener('click', () => this.generateMIDI());
111
+ this.container.querySelector('#clearBtn').addEventListener('click', () => this.clearNotes());
112
+ this.container.querySelector('#playBtn').addEventListener('click', () => this.play());
113
+ this.container.querySelector('#stopBtn').addEventListener('click', () => this.stop());
114
+ this.container.querySelector('#midiComplexity').addEventListener('input', (e) => {
115
+ this.container.querySelector('#complexityValue').textContent = e.target.value;
116
+ });
117
+ this.container.querySelector('#midiBars').addEventListener('input', (e) => {
118
+ this.bars = parseInt(e.target.value);
119
+ this.totalBeats = this.bars * this.barLength;
120
+ this.generateMIDI();
121
+ });
122
+
123
+ // Canvas events for editing
124
+ this.canvas.addEventListener('mousedown', (e) => this.handleCanvasClick(e));
125
+ }
126
+
127
+ generateMIDI() {
128
+ const key = this.container.querySelector('#midiKey').value;
129
+ const scale = this.container.querySelector('#midiScale').value;
130
+ const progressionStr = this.container.querySelector('#midiProgression').value;
131
+ const complexity = parseInt(this.container.querySelector('#midiComplexity').value);
132
+ const minOctave = parseInt(this.container.querySelector('#minOctave').value);
133
+ const maxOctave = parseInt(this.container.querySelector('#maxOctave').value);
134
+ const style = this.container.querySelector('#midiStyle').value;
135
+
136
+ this.notes = [];
137
+ this.bars = parseInt(this.container.querySelector('#midiBars').value);
138
+ this.totalBeats = this.bars * this.barLength;
139
+
140
+ const scaleNotes = Tonal.Scale.notes(key + ' ' + scale);
141
+ const progression = progressionStr.split('-').map(roman => roman.trim());
142
+
143
+ // Generate chord progression
144
+ const chords = progression.map(roman => {
145
+ const chord = Tonal.Chord.get(Tonal.RomanChord.toChord(roman, key + ' ' + scale));
146
+ return chord.notes.map(note => Tonal.Note.simplify(note));
147
+ });
148
+
149
+ // Generate notes based on style and complexity
150
+ this.generateNotes(chords, scaleNotes, complexity, minOctave, maxOctave, style);
151
+
152
+ this.drawPianoRoll();
153
+ }
154
+
155
+ generateNotes(chords, scaleNotes, complexity, minOctave, maxOctave, style) {
156
+ const beatsPerBar = this.barLength;
157
+ const totalBars = this.bars;
158
+
159
+ for (let bar = 0; bar < totalBars; bar++) {
160
+ const chord = chords[bar % chords.length];
161
+ const barStart = bar * beatsPerBar;
162
+
163
+ // Generate bass line
164
+ if (complexity >= 3) {
165
+ this.generateBassLine(chord, barStart, beatsPerBar, minOctave, style);
166
+ }
167
+
168
+ // Generate harmony
169
+ if (complexity >= 5) {
170
+ this.generateHarmony(chord, barStart, beatsPerBar, minOctave + 1, maxOctave, complexity);
171
+ }
172
+
173
+ // Generate melody
174
+ this.generateMelody(scaleNotes, chord, barStart, beatsPerBar, minOctave + 1, maxOctave, complexity, style);
175
+ }
176
+ }
177
+
178
+ generateBassLine(chord, barStart, beatsPerBar, minOctave, style) {
179
+ const root = chord[0] + minOctave;
180
+ let currentNote = root;
181
+
182
+ for (let beat = 0; beat < beatsPerBar; beat += 0.5) {
183
+ if (style === 'jazz') {
184
+ // Walking bass
185
+ const possibleNotes = [root, chord[2] + minOctave, chord[4] + minOctave, Tonal.Note.transpose(root, '2M')];
186
+ currentNote = possibleNotes[Math.floor(Math.random() * possibleNotes.length)];
187
+ }
188
+ this.notes.push({
189
+ time: barStart + beat,
190
+ duration: 0.5,
191
+ note: currentNote,
192
+ velocity: 0.7
193
+ });
194
+ }
195
+ }
196
+
197
+ generateHarmony(chord, barStart, beatsPerBar, minOctave, maxOctave, complexity) {
198
+ const harmonyNotes = chord.map(note => note + (minOctave + Math.floor(Math.random() * (maxOctave - minOctave + 1))));
199
+
200
+ for (let beat = 0; beat < beatsPerBar; beat += 1) {
201
+ const notesToPlay = complexity >= 7 ? harmonyNotes : [harmonyNotes[0], harmonyNotes[2]]; // Full chord or partial
202
+ notesToPlay.forEach(note => {
203
+ this.notes.push({
204
+ time: barStart + beat,
205
+ duration: 1,
206
+ note: note,
207
+ velocity: 0.5
208
+ });
209
+ });
210
+ }
211
+ }
212
+
213
+ generateMelody(scaleNotes, chord, barStart, beatsPerBar, minOctave, maxOctave, complexity, style) {
214
+ let currentBeat = 0;
215
+ const maxNoteLength = complexity >= 8 ? 0.25 : 0.5;
216
+
217
+ while (currentBeat < beatsPerBar) {
218
+ const noteLength = Math.random() < 0.7 ? 0.25 : (Math.random() < 0.5 ? 0.5 : 1);
219
+ const clampedLength = Math.min(noteLength, maxNoteLength, beatsPerBar - currentBeat);
220
+
221
+ let note;
222
+ if (style === 'classical' && complexity >= 6) {
223
+ // More complex melody with ornamentation
224
+ note = this.generateOrnamentedNote(scaleNotes, chord, minOctave, maxOctave);
225
+ } else {
226
+ // Simple scale-based melody
227
+ const octave = minOctave + Math.floor(Math.random() * (maxOctave - minOctave + 1));
228
+ note = scaleNotes[Math.floor(Math.random() * scaleNotes.length)] + octave;
229
+ }
230
+
231
+ this.notes.push({
232
+ time: barStart + currentBeat,
233
+ duration: clampedLength,
234
+ note: note,
235
+ velocity: 0.8
236
+ });
237
+
238
+ currentBeat += clampedLength;
239
+ }
240
+ }
241
+
242
+ generateOrnamentedNote(scaleNotes, chord, minOctave, maxOctave) {
243
+ // Simple ornamentation: trill or mordent
244
+ const baseNote = chord[Math.floor(Math.random() * chord.length)] + (minOctave + Math.floor(Math.random() * (maxOctave - minOctave + 1)));
245
+ // For now, just return base note. Could expand to add ornaments
246
+ return baseNote;
247
+ }
248
+
249
+ clearNotes() {
250
+ this.notes = [];
251
+ this.drawPianoRoll();
252
+ }
253
+
254
+ handleCanvasClick(e) {
255
+ const rect = this.canvas.getBoundingClientRect();
256
+ const x = (e.clientX - rect.left + this.scrollX) / this.zoomX;
257
+ const y = (e.clientY - rect.top + this.scrollY) / this.zoomY;
258
+
259
+ const beat = x / this.pixelsPerBeat;
260
+ const midiNote = 127 - Math.floor(y / this.pixelsPerSemitone);
261
+
262
+ // Check if there's a note at this position
263
+ const existingNoteIndex = this.notes.findIndex(note =>
264
+ Math.abs(note.time - beat) < 0.1 && Tonal.Note.midi(note.note) === midiNote
265
+ );
266
+
267
+ if (existingNoteIndex !== -1) {
268
+ // Remove note
269
+ this.notes.splice(existingNoteIndex, 1);
270
+ } else {
271
+ // Add note
272
+ this.notes.push({
273
+ time: Math.round(beat * 4) / 4, // Snap to 16th notes
274
+ duration: 0.25,
275
+ note: Tonal.Note.fromMidi(midiNote),
276
+ velocity: 0.8
277
+ });
278
+ }
279
+
280
+ this.drawPianoRoll();
281
+ }
282
+
283
+ drawPianoRoll() {
284
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
285
+
286
+ // Draw grid
287
+ this.ctx.strokeStyle = '#39ff1430';
288
+ this.ctx.lineWidth = 1;
289
+
290
+ // Vertical lines (beats)
291
+ for (let beat = 0; beat <= this.totalBeats; beat++) {
292
+ const x = (beat * this.pixelsPerBeat - this.scrollX) * this.zoomX;
293
+ if (x >= 0 && x <= this.canvas.width) {
294
+ this.ctx.beginPath();
295
+ this.ctx.moveTo(x, 0);
296
+ this.ctx.lineTo(x, this.canvas.height);
297
+ this.ctx.stroke();
298
+ }
299
+ }
300
+
301
+ // Horizontal lines (semitones)
302
+ for (let note = 0; note <= 127; note++) {
303
+ const y = ((127 - note) * this.pixelsPerSemitone - this.scrollY) * this.zoomY;
304
+ if (y >= 0 && y <= this.canvas.height) {
305
+ this.ctx.beginPath();
306
+ this.ctx.moveTo(0, y);
307
+ this.ctx.lineTo(this.canvas.width, y);
308
+ this.ctx.stroke();
309
+ }
310
+ }
311
+
312
+ // Draw notes
313
+ this.ctx.fillStyle = '#39ff14';
314
+ this.notes.forEach(note => {
315
+ const midiNote = Tonal.Note.midi(note.note);
316
+ const x = (note.time * this.pixelsPerBeat - this.scrollX) * this.zoomX;
317
+ const y = ((127 - midiNote) * this.pixelsPerSemitone - this.scrollY) * this.zoomY;
318
+ const width = note.duration * this.pixelsPerBeat * this.zoomX;
319
+ const height = this.pixelsPerSemitone * this.zoomY;
320
+
321
+ if (x + width >= 0 && x <= this.canvas.width && y + height >= 0 && y <= this.canvas.height) {
322
+ this.ctx.fillRect(x, y, width, height);
323
+ }
324
+ });
325
+
326
+ // Draw playhead
327
+ if (this.isPlaying) {
328
+ const playheadX = (this.playheadPosition * this.pixelsPerBeat - this.scrollX) * this.zoomX;
329
+ this.ctx.strokeStyle = '#ff3939';
330
+ this.ctx.lineWidth = 2;
331
+ this.ctx.beginPath();
332
+ this.ctx.moveTo(playheadX, 0);
333
+ this.ctx.lineTo(playheadX, this.canvas.height);
334
+ this.ctx.stroke();
335
+ }
336
+ }
337
+
338
+ play() {
339
+ if (this.isPlaying) return;
340
+ this.isPlaying = true;
341
+ this.playheadPosition = 0;
342
+
343
+ this.part = new Tone.Part((time, note) => {
344
+ const synth = this.getSynth();
345
+ if (synth.triggerAttackRelease) {
346
+ synth.triggerAttackRelease(note.note, note.duration, time, note.velocity);
347
+ } else {
348
+ // For FM synth, trigger attack/release
349
+ synth.triggerAttack(note.note, time);
350
+ synth.triggerRelease(time + note.duration);
351
+ }
352
+ }, this.notes).start();
353
+
354
+ Tone.Transport.start();
355
+
356
+ // Animate playhead
357
+ const animate = () => {
358
+ if (!this.isPlaying) return;
359
+ this.playheadPosition += 0.1; // Adjust speed as needed
360
+ if (this.playheadPosition > this.totalBeats) {
361
+ this.stop();
362
+ return;
363
+ }
364
+ this.drawPianoRoll();
365
+ requestAnimationFrame(animate);
366
+ };
367
+ animate();
368
+ }
369
+
370
+ stop() {
371
+ this.isPlaying = false;
372
+ if (this.part) {
373
+ this.part.stop();
374
+ this.part.dispose();
375
+ this.part = null;
376
+ }
377
+ Tone.Transport.stop();
378
+ this.playheadPosition = 0;
379
+ this.drawPianoRoll();
380
+ }
381
+ }
MS12 - Copy.html ADDED
The diff for this file is too large to render. See raw diff