SREAL commited on
Commit
1d34a9e
·
verified ·
1 Parent(s): d4639ec

Delete MIDIGenerator.js

Browse files
Files changed (1) hide show
  1. MIDIGenerator.js +0 -381
MIDIGenerator.js DELETED
@@ -1,381 +0,0 @@
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
- }