kolaslab commited on
Commit
081e896
·
verified ·
1 Parent(s): 797cfe7

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +373 -228
index.html CHANGED
@@ -3,336 +3,481 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Multi-Device Audio Analysis System</title>
 
7
  <style>
8
  :root {
9
- --primary: #2196F3;
10
- --secondary: #4CAF50;
11
- --bg-dark: #1a1a1a;
12
- --bg-light: #2a2a2a;
13
- --text: #ffffff;
14
  }
15
 
16
  * {
17
  margin: 0;
18
  padding: 0;
19
  box-sizing: border-box;
20
- font-family: 'Segoe UI', system-ui, sans-serif;
21
  }
22
 
23
  body {
24
- background: var(--bg-dark);
25
- color: var(--text);
26
  min-height: 100vh;
 
27
  }
28
 
29
- .app-container {
 
 
 
 
 
 
 
 
 
30
  display: grid;
31
  grid-template-columns: 300px 1fr;
32
- gap: 20px;
33
- padding: 20px;
34
- height: 100vh;
35
  }
36
 
37
- .sidebar {
38
- background: var(--bg-light);
39
  border-radius: 12px;
40
- padding: 20px;
41
- display: flex;
42
- flex-direction: column;
43
- gap: 20px;
44
  }
45
 
46
- .main-content {
47
- display: grid;
48
- grid-template-rows: auto 1fr;
49
- gap: 20px;
50
  }
51
 
52
- .visualization-container {
53
- display: grid;
54
- grid-template-columns: 2fr 1fr;
55
- gap: 20px;
56
  }
57
 
58
- .canvas-container {
59
- background: var(--bg-light);
60
- border-radius: 12px;
61
- padding: 20px;
62
- position: relative;
 
 
 
 
63
  }
64
 
65
- canvas {
66
  width: 100%;
67
- height: 100%;
68
- background: rgba(0,0,0,0.2);
69
- border-radius: 8px;
 
70
  }
71
 
72
- .control-panel {
73
- background: var(--bg-light);
74
- border-radius: 12px;
75
- padding: 20px;
76
  }
77
 
78
- .btn {
79
- background: var(--primary);
80
- color: white;
81
- border: none;
82
- padding: 12px 24px;
83
- border-radius: 6px;
84
  cursor: pointer;
85
- transition: opacity 0.2s;
86
- width: 100%;
87
- margin-bottom: 10px;
88
  }
89
 
90
- .btn:hover {
91
- opacity: 0.9;
 
 
92
  }
93
 
94
- .btn.secondary {
95
- background: var(--secondary);
 
 
96
  }
97
 
98
- .input-group {
99
- margin-bottom: 15px;
 
 
 
 
100
  }
101
 
102
- .input-group label {
103
- display: block;
104
- margin-bottom: 5px;
105
- color: #888;
 
 
 
 
 
106
  }
107
 
108
- input[type="number"],
109
- select {
110
- width: 100%;
111
- padding: 8px;
112
- background: rgba(255,255,255,0.1);
113
- border: 1px solid rgba(255,255,255,0.2);
114
- border-radius: 4px;
115
- color: white;
116
  }
117
 
118
- .device-list {
119
- display: flex;
120
- flex-direction: column;
121
- gap: 10px;
122
  }
123
 
124
- .device-item {
125
- background: rgba(255,255,255,0.1);
126
- padding: 10px;
127
- border-radius: 4px;
 
128
  display: flex;
129
- justify-content: space-between;
130
- align-items: center;
 
 
131
  }
132
 
133
- .status-indicator {
134
- width: 10px;
135
- height: 10px;
136
- border-radius: 50%;
137
- background: var(--secondary);
 
 
 
138
  }
139
 
140
- #frequencyDisplay {
141
- position: absolute;
142
- top: 10px;
143
- right: 10px;
144
- background: rgba(0,0,0,0.7);
145
- padding: 5px 10px;
146
- border-radius: 4px;
147
  }
148
 
149
- .preset-list {
150
- display: grid;
151
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
152
- gap: 10px;
153
  }
154
 
155
- .preset-item {
156
- background: rgba(255,255,255,0.1);
157
- padding: 10px;
158
- border-radius: 4px;
 
 
 
 
 
 
 
159
  cursor: pointer;
160
- transition: background 0.2s;
161
  }
162
 
163
- .preset-item:hover {
164
- background: rgba(255,255,255,0.2);
 
 
165
  }
166
  </style>
167
  </head>
168
  <body>
169
- <div class="app-container">
170
- <aside class="sidebar">
171
- <div class="control-panel">
172
- <h3>Device Settings</h3>
173
- <div class="input-group">
174
- <label>Device Role</label>
175
- <select id="deviceRole">
176
- <option value="primary">Primary</option>
177
- <option value="left">Left Channel</option>
178
- <option value="right">Right Channel</option>
179
- </select>
180
  </div>
181
- <button class="btn" id="startCapture">Start Capture</button>
182
- <button class="btn secondary" id="calibrate">Calibrate</button>
183
- </div>
184
 
185
- <div class="device-list">
186
- <h3>Connected Devices</h3>
187
- <div class="device-item">
188
- <span>This Device</span>
189
- <div class="status-indicator"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  </div>
191
- </div>
192
- </aside>
193
-
194
- <main class="main-content">
195
- <div class="control-panel">
196
- <h2>Binaural Beat Generator</h2>
197
- <div class="input-group">
198
- <label>Base Frequency (Hz)</label>
199
- <input type="number" id="baseFreq" value="432" min="20" max="1000">
 
 
 
 
 
 
 
 
 
200
  </div>
201
- <div class="input-group">
202
- <label>Beat Frequency (Hz)</label>
203
- <input type="number" id="beatFreq" value="7" min="1" max="40">
 
 
 
 
 
 
 
 
 
204
  </div>
205
- <button class="btn" id="generateBeat">Generate Beat</button>
206
  </div>
207
 
208
- <div class="visualization-container">
209
- <div class="canvas-container">
210
- <canvas id="spectrumAnalyzer"></canvas>
211
- <div id="frequencyDisplay">0 Hz</div>
212
  </div>
213
- <div class="canvas-container">
214
- <canvas id="roomAnalysis"></canvas>
 
 
 
 
215
  </div>
216
  </div>
217
- </main>
218
  </div>
219
 
220
  <script>
221
- class AudioSystem {
222
  constructor() {
223
- this.audioContext = null;
224
- this.analyser = null;
225
- this.gainNode = null;
226
- this.oscillators = {};
227
- this.isCapturing = false;
228
- this.isGenerating = false;
229
 
230
- this.initialize();
231
  this.setupEventListeners();
232
  }
233
 
234
- async initialize() {
235
- try {
236
- this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
237
- this.analyser = this.audioContext.createAnalyser();
238
- this.gainNode = this.audioContext.createGain();
239
-
240
- this.analyser.fftSize = 2048;
241
- this.gainNode.connect(this.audioContext.destination);
242
- this.analyser.connect(this.gainNode);
243
-
244
- await this.setupSpectrumVisualizer();
245
- } catch (error) {
246
- console.error('Audio initialization failed:', error);
247
  }
 
 
 
 
 
 
248
  }
249
 
250
- async startCapture() {
251
- if (this.isCapturing) return;
252
-
253
- try {
254
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
255
- const source = this.audioContext.createMediaStreamSource(stream);
256
- source.connect(this.analyser);
257
- this.isCapturing = true;
258
- } catch (error) {
259
- console.error('Capture failed:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  }
 
 
261
  }
262
 
263
- generateBinauralBeat(baseFreq, beatFreq) {
264
- if (this.isGenerating) this.stopBinauralBeat();
 
 
 
 
 
 
 
 
 
265
 
266
- const leftOsc = this.audioContext.createOscillator();
267
- const rightOsc = this.audioContext.createOscillator();
268
-
269
- leftOsc.frequency.value = baseFreq;
270
- rightOsc.frequency.value = baseFreq + beatFreq;
271
 
272
- const merger = this.audioContext.createChannelMerger(2);
273
-
274
- leftOsc.connect(merger, 0, 0);
275
- rightOsc.connect(merger, 0, 1);
276
- merger.connect(this.gainNode);
 
 
 
 
277
 
278
- leftOsc.start();
279
- rightOsc.start();
 
 
 
 
280
 
281
- this.oscillators = { left: leftOsc, right: rightOsc };
282
- this.isGenerating = true;
 
 
 
 
 
283
  }
284
 
285
- stopBinauralBeat() {
286
- if (!this.isGenerating) return;
 
 
 
 
 
 
 
 
 
 
287
 
288
- Object.values(this.oscillators).forEach(osc => osc.stop());
289
- this.oscillators = {};
290
- this.isGenerating = false;
291
  }
292
 
293
- async setupSpectrumVisualizer() {
294
- const canvas = document.getElementById('spectrumAnalyzer');
295
- const ctx = canvas.getContext('2d');
296
- const bufferLength = this.analyser.frequencyBinCount;
297
- const dataArray = new Uint8Array(bufferLength);
298
-
299
- const draw = () => {
300
- requestAnimationFrame(draw);
301
-
302
- this.analyser.getByteFrequencyData(dataArray);
303
-
304
- ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
305
- ctx.fillRect(0, 0, canvas.width, canvas.height);
306
-
307
- const barWidth = canvas.width / bufferLength;
308
- let x = 0;
309
-
310
- for(let i = 0; i < bufferLength; i++) {
311
- const barHeight = (dataArray[i] / 255) * canvas.height;
312
-
313
- const hue = i / bufferLength * 360;
314
- ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
315
-
316
- ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
317
- x += barWidth + 1;
318
- }
319
- };
320
 
321
- draw();
 
322
  }
323
 
324
- setupEventListeners() {
325
- document.getElementById('startCapture').onclick = () => this.startCapture();
326
- document.getElementById('generateBeat').onclick = () => {
327
- const baseFreq = parseFloat(document.getElementById('baseFreq').value);
328
- const beatFreq = parseFloat(document.getElementById('beatFreq').value);
329
- this.generateBinauralBeat(baseFreq, beatFreq);
330
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  }
332
  }
333
 
334
- // Initialize the system
335
- const audioSystem = new AudioSystem();
336
  </script>
337
  </body>
338
  </html><script async data-explicit-opt-in="true" data-cookie-opt-in="true" src="https://vercel.live/_next-live/feedback/feedback.js"></script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>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: 300px 1fr;
43
+ gap: 2rem;
 
 
44
  }
45
 
46
+ .panel {
47
+ background: #f8f9fa;
48
  border-radius: 12px;
49
+ padding: 1.5rem;
 
 
 
50
  }
51
 
52
+ .section {
53
+ margin-bottom: 2rem;
 
 
54
  }
55
 
56
+ h2, h3 {
57
+ margin-bottom: 1rem;
58
+ color: var(--primary);
 
59
  }
60
 
61
+ .control {
62
+ margin-bottom: 1rem;
63
+ }
64
+
65
+ label {
66
+ display: block;
67
+ margin-bottom: 0.5rem;
68
+ font-size: 0.9rem;
69
+ color: #666;
70
  }
71
 
72
+ select, input[type="range"], input[type="number"] {
73
  width: 100%;
74
+ padding: 0.5rem;
75
+ border: 1px solid #ddd;
76
+ border-radius: 6px;
77
+ background: white;
78
  }
79
 
80
+ input[type="range"] {
81
+ -webkit-appearance: none;
82
+ height: 8px;
83
+ background: #ddd;
84
  }
85
 
86
+ input[type="range"]::-webkit-slider-thumb {
87
+ -webkit-appearance: none;
88
+ width: 16px;
89
+ height: 16px;
90
+ background: var(--secondary);
91
+ border-radius: 50%;
92
  cursor: pointer;
 
 
 
93
  }
94
 
95
+ .value-display {
96
+ font-size: 0.8rem;
97
+ color: var(--secondary);
98
+ text-align: right;
99
  }
100
 
101
+ .editor {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: 1rem;
105
  }
106
 
107
+ .piano-roll {
108
+ background: #1a1a1a;
109
+ border-radius: 12px;
110
+ height: 500px;
111
+ position: relative;
112
+ overflow: hidden;
113
  }
114
 
115
+ .grid {
116
+ position: absolute;
117
+ inset: 0;
118
+ display: grid;
119
+ grid-template-columns: repeat(32, 1fr);
120
+ grid-template-rows: repeat(88, 1fr);
121
+ gap: 1px;
122
+ padding: 1px;
123
+ background: #2a2a2a;
124
  }
125
 
126
+ .cell {
127
+ background: #333;
128
+ cursor: pointer;
129
+ transition: all 0.1s ease;
 
 
 
 
130
  }
131
 
132
+ .cell:hover {
133
+ background: #444;
 
 
134
  }
135
 
136
+ .cell.active {
137
+ background: var(--secondary);
138
+ }
139
+
140
+ .transport {
141
  display: flex;
142
+ gap: 1rem;
143
+ padding: 1rem;
144
+ background: #f8f9fa;
145
+ border-radius: 12px;
146
  }
147
 
148
+ button {
149
+ padding: 0.8rem 1.5rem;
150
+ border: none;
151
+ border-radius: 6px;
152
+ font-weight: 600;
153
+ cursor: pointer;
154
+ transition: all 0.2s;
155
+ color: white;
156
  }
157
 
158
+ .btn-primary { background: var(--primary); }
159
+ .btn-secondary { background: var(--secondary); }
160
+ .btn-accent { background: var(--accent); }
161
+
162
+ button:hover {
163
+ opacity: 0.9;
164
+ transform: translateY(-1px);
165
  }
166
 
167
+ .synth-controls {
168
+ display: flex;
169
+ gap: 1rem;
170
+ margin-bottom: 1rem;
171
  }
172
 
173
+ .wave-selector {
174
+ display: flex;
175
+ gap: 0.5rem;
176
+ }
177
+
178
+ .wave-btn {
179
+ padding: 0.5rem 1rem;
180
+ background: white;
181
+ border: 1px solid #ddd;
182
+ border-radius: 20px;
183
+ color: #666;
184
  cursor: pointer;
 
185
  }
186
 
187
+ .wave-btn.active {
188
+ background: var(--secondary);
189
+ color: white;
190
+ border-color: var(--secondary);
191
  }
192
  </style>
193
  </head>
194
  <body>
195
+ <div class="container">
196
+ <div class="workspace">
197
+ <div class="panel">
198
+ <div class="section">
199
+ <h3>Sound</h3>
200
+ <div class="wave-selector">
201
+ <div class="wave-btn active" data-wave="sine">Sine</div>
202
+ <div class="wave-btn" data-wave="square">Square</div>
203
+ <div class="wave-btn" data-wave="sawtooth">Saw</div>
204
+ </div>
 
205
  </div>
 
 
 
206
 
207
+ <div class="section">
208
+ <h3>Key & Scale</h3>
209
+ <div class="control">
210
+ <label>Key</label>
211
+ <select id="key">
212
+ <option value="C">C</option>
213
+ <option value="C#">C#/Db</option>
214
+ <option value="D">D</option>
215
+ <option value="D#">D#/Eb</option>
216
+ <option value="E">E</option>
217
+ <option value="F">F</option>
218
+ <option value="F#">F#/Gb</option>
219
+ <option value="G">G</option>
220
+ <option value="G#">G#/Ab</option>
221
+ <option value="A">A</option>
222
+ <option value="A#">A#/Bb</option>
223
+ <option value="B">B</option>
224
+ </select>
225
+ </div>
226
+
227
+ <div class="control">
228
+ <label>Scale</label>
229
+ <select id="scale">
230
+ <option value="major">Major</option>
231
+ <option value="minor">Natural Minor</option>
232
+ <option value="harmonicMinor">Harmonic Minor</option>
233
+ <option value="dorian">Dorian</option>
234
+ <option value="phrygian">Phrygian</option>
235
+ <option value="lydian">Lydian</option>
236
+ <option value="mixolydian">Mixolydian</option>
237
+ </select>
238
+ </div>
239
  </div>
240
+
241
+ <div class="section">
242
+ <h3>Rhythm</h3>
243
+ <div class="control">
244
+ <label>Tempo: <span id="tempo-value">120</span> BPM</label>
245
+ <input type="range" id="tempo" min="60" max="200" value="120">
246
+ </div>
247
+
248
+ <div class="control">
249
+ <label>Note Length</label>
250
+ <select id="noteLength">
251
+ <option value="1">Whole</option>
252
+ <option value="2">Half</option>
253
+ <option value="4" selected>Quarter</option>
254
+ <option value="8">Eighth</option>
255
+ <option value="16">Sixteenth</option>
256
+ </select>
257
+ </div>
258
  </div>
259
+
260
+ <div class="section">
261
+ <h3>Melody</h3>
262
+ <div class="control">
263
+ <label>Complexity: <span id="complexity-value">5</span></label>
264
+ <input type="range" id="complexity" min="1" max="10" value="5">
265
+ </div>
266
+
267
+ <div class="control">
268
+ <label>Base Octave: <span id="octave-value">4</span></label>
269
+ <input type="range" id="octave" min="2" max="6" value="4">
270
+ </div>
271
  </div>
 
272
  </div>
273
 
274
+ <div class="editor">
275
+ <div class="piano-roll">
276
+ <div class="grid" id="grid"></div>
 
277
  </div>
278
+
279
+ <div class="transport">
280
+ <button class="btn-primary" id="generate">Generate</button>
281
+ <button class="btn-secondary" id="play">Play</button>
282
+ <button class="btn-secondary" id="stop">Stop</button>
283
+ <button class="btn-accent" id="download">Download MIDI</button>
284
  </div>
285
  </div>
286
+ </div>
287
  </div>
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>