SREAL commited on
Commit
a135c29
1 Parent(s): 4cadfa7

Updated script that fixes the main problems.

Browse files

Hey Kolaslab, Ive fixed some problems in the script. Using this updated html, the audio plays only the pattern displayed in the UI, so no more layering up each time play is pressed.
Also, the midi pattern is being displayed with the correct orientation. (it was upside down)

I'd really appreciate if you could update the space to use this edited code so people can appreciate the tool to its full potential! :)

Files changed (1) hide show
  1. index.html +193 -182
index.html CHANGED
@@ -110,6 +110,7 @@
110
  height: 500px;
111
  position: relative;
112
  overflow: hidden;
 
113
  }
114
 
115
  .grid {
@@ -288,196 +289,206 @@
288
 
289
  <script>
290
  class MelodyGenerator {
291
- constructor() {
292
- this.synth = new Tone.PolySynth(Tone.Synth).toDestination();
293
- this.sequence = [];
294
- this.isPlaying = false;
295
- this.currentWaveform = 'sine';
296
-
297
- this.initUI();
298
- this.setupEventListeners();
299
- }
300
-
301
- initUI() {
302
- // Initialize grid
303
- const grid = document.getElementById('grid');
304
- for (let i = 0; i < 88; i++) {
305
- for (let j = 0; j < 32; j++) {
306
- const cell = document.createElement('div');
307
- cell.className = 'cell';
308
- cell.dataset.note = i;
309
- cell.dataset.time = j;
310
- cell.onclick = () => this.toggleCell(cell);
311
- grid.appendChild(cell);
312
- }
313
- }
314
-
315
- // Initialize value displays
316
- document.querySelectorAll('input[type="range"]').forEach(input => {
317
- const display = document.getElementById(`${input.id}-value`);
318
- if (display) display.textContent = input.value;
319
- });
320
- }
321
-
322
- setupEventListeners() {
323
- document.getElementById('generate').onclick = () => this.generateMelody();
324
- document.getElementById('play').onclick = () => this.togglePlay();
325
- document.getElementById('stop').onclick = () => this.stop();
326
- document.getElementById('download').onclick = () => this.downloadMIDI();
327
-
328
- // Waveform selection
329
- document.querySelectorAll('.wave-btn').forEach(btn => {
330
- btn.onclick = (e) => {
331
- document.querySelectorAll('.wave-btn').forEach(b => b.classList.remove('active'));
332
- e.target.classList.add('active');
333
- this.currentWaveform = e.target.dataset.wave;
334
- this.updateSynthSettings();
335
- };
336
- });
337
-
338
- // Range input updates
339
- document.querySelectorAll('input[type="range"]').forEach(input => {
340
- input.oninput = (e) => {
341
- const display = document.getElementById(`${input.id}-value`);
342
- if (display) {
343
- display.textContent = input.id === 'tempo' ?
344
- `${e.target.value} BPM` : e.target.value;
345
- }
346
- };
347
- });
348
- }
349
-
350
- updateSynthSettings() {
351
- this.synth.set({
352
- oscillator: { type: this.currentWaveform }
353
- });
354
- }
355
-
356
- generateMelody() {
357
- this.clearGrid();
358
- const key = document.getElementById('key').value;
359
- const scale = document.getElementById('scale').value;
360
- const complexity = parseInt(document.getElementById('complexity').value);
361
- const baseOctave = parseInt(document.getElementById('octave').value);
362
-
363
- this.sequence = this.createMelodySequence(key, scale, complexity, baseOctave);
364
- this.visualizeSequence();
365
- }
366
-
367
- createMelodySequence(key, scale, complexity, baseOctave) {
368
- const noteCount = Math.floor(complexity * 4);
369
- const sequence = [];
370
- const scaleNotes = this.getScaleNotes(key, scale);
371
-
372
- for (let i = 0; i < noteCount; i++) {
373
- const time = Math.floor(Math.random() * 32);
374
- const note = scaleNotes[Math.floor(Math.random() * scaleNotes.length)];
375
- sequence.push({
376
- note: `${note}${baseOctave}`,
377
- time: time,
378
- duration: 0.25
379
- });
380
- }
381
-
382
- return sequence;
383
- }
384
-
385
- getScaleNotes(key, scale) {
386
- const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
387
- const scales = {
388
- major: [0, 2, 4, 5, 7, 9, 11],
389
- minor: [0, 2, 3, 5, 7, 8, 10],
390
- harmonicMinor: [0, 2, 3, 5, 7, 8, 11],
391
- dorian: [0, 2, 3, 5, 7, 9, 10],
392
- phrygian: [0, 1, 3, 5, 7, 8, 10],
393
- lydian: [0, 2, 4, 6, 7, 9, 11],
394
- mixolydian: [0, 2, 4, 5, 7, 9, 10]
395
- };
396
-
397
- const keyIndex = notes.indexOf(key);
398
- const scalePattern = scales[scale];
399
- return scalePattern.map(interval => notes[(keyIndex + interval) % 12]);
400
- }
401
-
402
- visualizeSequence() {
403
- this.sequence.forEach(note => {
404
- const noteIndex = this.getNoteIndex(note.note);
405
- const cell = document.querySelector(
406
- `.cell[data-note="${noteIndex}"][data-time="${note.time}"]`
407
- );
408
- if (cell) cell.classList.add('active');
409
- });
410
- }
411
-
412
- getNoteIndex(note) {
413
- const noteName = note.slice(0, -1);
414
- const octave = parseInt(note.slice(-1));
415
- const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
416
- return (octave * 12) + notes.indexOf(noteName);
417
  }
 
418
 
419
- async togglePlay() {
420
- if (this.isPlaying) {
421
- this.stop();
422
- } else {
423
- await Tone.start();
424
- this.play();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  }
426
- }
427
-
428
- play() {
429
- this.isPlaying = true;
430
- const tempo = document.getElementById('tempo').value;
431
- Tone.Transport.bpm.value = tempo;
432
-
433
- const part = new Tone.Part(((time, note) => {
434
- this.synth.triggerAttackRelease(note.note, note.duration, time);
435
- }), this.sequence.map(note => ({
436
- time: note.time * 0.25,
437
- note: note.note,
438
- duration: note.duration
439
- }))).start(0);
440
-
441
- Tone.Transport.start();
442
- }
443
-
444
- stop() {
445
- this.isPlaying = false;
446
- Tone.Transport.stop();
447
- Tone.Transport.position = 0;
448
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
449
 
450
- toggleCell(cell) {
451
- cell.classList.toggle('active');
452
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
 
454
- clearGrid() {
455
- document.querySelectorAll('.cell').forEach(cell => {
456
- cell.classList.remove('active');
457
- });
458
- }
459
 
460
- downloadMIDI() {
461
- // Basic MIDI file structure
462
- const midiData = [
463
- 0x4D, 0x54, 0x68, 0x64, // MThd
464
- 0x00, 0x00, 0x00, 0x06, // Header size
465
- 0x00, 0x01, // Format
466
- 0x00, 0x01, // Tracks
467
- 0x01, 0x80 // Division
468
- ];
469
-
470
- const blob = new Blob([new Uint8Array(midiData)], { type: 'audio/midi' });
471
- const url = window.URL.createObjectURL(blob);
472
- const a = document.createElement('a');
473
- a.href = url;
474
- a.download = 'melody.mid';
475
- a.click();
476
- window.URL.revokeObjectURL(url);
477
- }
 
 
478
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
 
480
  const generator = new MelodyGenerator();
481
  </script>
482
  </body>
483
- </html><script async data-explicit-opt-in="true" data-cookie-opt-in="true" src="https://vercel.live/_next-live/feedback/feedback.js"></script>
 
110
  height: 500px;
111
  position: relative;
112
  overflow: hidden;
113
+ transform: scaleY(-1);
114
  }
115
 
116
  .grid {
 
289
 
290
  <script>
291
  class MelodyGenerator {
292
+ constructor() {
293
+ this.synth = new Tone.PolySynth(Tone.Synth).toDestination();
294
+ this.sequence = [];
295
+ this.isPlaying = false;
296
+ this.currentWaveform = 'sine';
297
+ this.currentPart = null; // Store the current Tone.Part
298
+ this.initUI();
299
+ this.setupEventListeners();
300
+ }
301
+
302
+ initUI() {
303
+ // Initialize grid
304
+ const grid = document.getElementById('grid');
305
+ for (let i = 0; i < 88; i++) {
306
+ for (let j = 0; j < 32; j++) {
307
+ const cell = document.createElement('div');
308
+ cell.className = 'cell';
309
+ cell.dataset.note = i;
310
+ cell.dataset.time = j;
311
+ cell.onclick = () => this.toggleCell(cell);
312
+ grid.appendChild(cell);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  }
314
+ }
315
 
316
+ // Initialize value displays
317
+ document.querySelectorAll('input[type="range"]').forEach(input => {
318
+ const display = document.getElementById(`${input.id}-value`);
319
+ if (display) display.textContent = input.value;
320
+ });
321
+ }
322
+
323
+ setupEventListeners() {
324
+ document.getElementById('generate').onclick = () => this.generateMelody();
325
+ document.getElementById('play').onclick = () => this.togglePlay();
326
+ document.getElementById('stop').onclick = () => this.stop();
327
+ document.getElementById('download').onclick = () => this.downloadMIDI();
328
+
329
+ // Waveform selection
330
+ document.querySelectorAll('.wave-btn').forEach(btn => {
331
+ btn.onclick = (e) => {
332
+ document.querySelectorAll('.wave-btn').forEach(b => b.classList.remove('active'));
333
+ e.target.classList.add('active');
334
+ this.currentWaveform = e.target.dataset.wave;
335
+ this.updateSynthSettings();
336
+ };
337
+ });
338
+
339
+ // Range input updates
340
+ document.querySelectorAll('input[type="range"]').forEach(input => {
341
+ input.oninput = (e) => {
342
+ const display = document.getElementById(`${input.id}-value`);
343
+ if (display) {
344
+ display.textContent = input.id === 'tempo' ?
345
+ `${e.target.value} BPM` : e.target.value;
346
  }
347
+ };
348
+ });
349
+ }
350
+
351
+ updateSynthSettings() {
352
+ this.synth.set({
353
+ oscillator: { type: this.currentWaveform }
354
+ });
355
+ }
356
+
357
+ generateMelody() {
358
+ this.stop(); // Stop any existing playback
359
+ this.clearGrid();
360
+ const key = document.getElementById('key').value;
361
+ const scale = document.getElementById('scale').value;
362
+ const complexity = parseInt(document.getElementById('complexity').value);
363
+ const baseOctave = parseInt(document.getElementById('octave').value);
364
+
365
+ this.sequence = this.createMelodySequence(key, scale, complexity, baseOctave);
366
+ this.visualizeSequence();
367
+ }
368
+
369
+ createMelodySequence(key, scale, complexity, baseOctave) {
370
+ const noteCount = Math.floor(complexity * 4);
371
+ const sequence = [];
372
+ const scaleNotes = this.getScaleNotes(key, scale);
373
+
374
+ for (let i = 0; i < noteCount; i++) {
375
+ const time = Math.floor(Math.random() * 32);
376
+ const note = scaleNotes[Math.floor(Math.random() * scaleNotes.length)];
377
+ sequence.push({
378
+ note: `${note}${baseOctave}`,
379
+ time: time,
380
+ duration: 0.25
381
+ });
382
+ }
383
 
384
+ return sequence;
385
+ }
386
+
387
+ getScaleNotes(key, scale) {
388
+ const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
389
+ const scales = {
390
+ major: [0, 2, 4, 5, 7, 9, 11],
391
+ minor: [0, 2, 3, 5, 7, 8, 10],
392
+ harmonicMinor: [0, 2, 3, 5, 7, 8, 11],
393
+ dorian: [0, 2, 3, 5, 7, 9, 10],
394
+ phrygian: [0, 1, 3, 5, 7, 8, 10],
395
+ lydian: [0, 2, 4, 6, 7, 9, 11],
396
+ mixolydian: [0, 2, 4, 5, 7, 9, 10]
397
+ };
398
+
399
+ const keyIndex = notes.indexOf(key);
400
+ const scalePattern = scales[scale];
401
+ return scalePattern.map(interval => notes[(keyIndex + interval) % 12]);
402
+ }
403
+
404
+ visualizeSequence() {
405
+ this.sequence.forEach(note => {
406
+ const noteIndex = this.getNoteIndex(note.note);
407
+ const cell = document.querySelector(
408
+ `.cell[data-note="${noteIndex}"][data-time="${note.time}"]`
409
+ );
410
+ if (cell) cell.classList.add('active');
411
+ });
412
+ }
413
+
414
+ getNoteIndex(note) {
415
+ const noteName = note.slice(0, -1);
416
+ const octave = parseInt(note.slice(-1));
417
+ const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
418
+ return (octave * 12) + notes.indexOf(noteName);
419
+ }
420
+
421
+ async togglePlay() {
422
+ if (this.isPlaying) {
423
+ this.stop();
424
+ } else {
425
+ await Tone.start();
426
+ this.play();
427
+ }
428
+ }
429
 
430
+ play() {
431
+ if (this.currentPart) {
432
+ this.currentPart.stop();
433
+ this.currentPart.dispose();
434
+ }
435
 
436
+ this.isPlaying = true;
437
+ const tempo = document.getElementById('tempo').value;
438
+ Tone.Transport.bpm.value = tempo;
439
+
440
+ this.currentPart = new Tone.Part(((time, note) => {
441
+ this.synth.triggerAttackRelease(note.note, note.duration, time);
442
+ }), this.sequence.map(note => ({
443
+ time: note.time * 0.25,
444
+ note: note.note,
445
+ duration: note.duration
446
+ }))).start(0);
447
+
448
+ Tone.Transport.start();
449
+ }
450
+
451
+ stop() {
452
+ this.isPlaying = false;
453
+ if (this.currentPart) {
454
+ this.currentPart.stop();
455
+ this.currentPart.dispose();
456
  }
457
+ Tone.Transport.stop();
458
+ Tone.Transport.position = 0;
459
+ }
460
+
461
+ toggleCell(cell) {
462
+ cell.classList.toggle('active');
463
+ }
464
+
465
+ clearGrid() {
466
+ document.querySelectorAll('.cell').forEach(cell => {
467
+ cell.classList.remove('active');
468
+ });
469
+ }
470
+
471
+ downloadMIDI() {
472
+ // Basic MIDI file structure
473
+ const midiData = [
474
+ 0x4D, 0x54, 0x68, 0x64, // MThd
475
+ 0x00, 0x00, 0x00, 0x06, // Header size
476
+ 0x00, 0x01, // Format
477
+ 0x00, 0x01, // Tracks
478
+ 0x01, 0x80 // Division
479
+ ];
480
+
481
+ const blob = new Blob([new Uint8Array(midiData)], { type: 'audio/midi' });
482
+ const url = window.URL.createObjectURL(blob);
483
+ const a = document.createElement('a');
484
+ a.href = url;
485
+ a.download = 'melody.mid';
486
+ a.click();
487
+ window.URL.revokeObjectURL(url);
488
+ }
489
+ }
490
 
491
  const generator = new MelodyGenerator();
492
  </script>
493
  </body>
494
+ </html>