mugnatto commited on
Commit
ed07889
·
verified ·
1 Parent(s): 7a46b38

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +533 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Midi Keyboard
3
- emoji: 🔥
4
- colorFrom: indigo
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: midi-keyboard
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,533 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Teclado Musical com MIDI</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .key {
11
+ transition: all 0.1s ease;
12
+ user-select: none;
13
+ }
14
+
15
+ .white-key {
16
+ background-color: white;
17
+ border: 1px solid #ccc;
18
+ z-index: 1;
19
+ }
20
+
21
+ .black-key {
22
+ background-color: black;
23
+ z-index: 2;
24
+ margin-left: -15px;
25
+ margin-right: -15px;
26
+ height: 60%;
27
+ }
28
+
29
+ .white-key.active {
30
+ background-color: #ddd;
31
+ box-shadow: inset 0 0 10px rgba(0,0,0,0.3);
32
+ }
33
+
34
+ .black-key.active {
35
+ background-color: #555;
36
+ box-shadow: inset 0 0 10px rgba(255,255,255,0.2);
37
+ }
38
+
39
+ .key-label {
40
+ pointer-events: none;
41
+ }
42
+
43
+ .keyboard-container {
44
+ perspective: 1000px;
45
+ }
46
+
47
+ .keyboard {
48
+ transform-style: preserve-3d;
49
+ transform: rotateX(10deg);
50
+ }
51
+
52
+ .midi-indicator {
53
+ animation: pulse 0.5s;
54
+ }
55
+
56
+ @keyframes pulse {
57
+ 0% { transform: scale(1); }
58
+ 50% { transform: scale(1.1); }
59
+ 100% { transform: scale(1); }
60
+ }
61
+ </style>
62
+ </head>
63
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col">
64
+ <div class="container mx-auto px-4 py-8 flex-grow">
65
+ <header class="text-center mb-8">
66
+ <h1 class="text-4xl font-bold mb-2">Teclado Musical MIDI</h1>
67
+ <p class="text-gray-400">Toque no teclado virtual ou conecte um controlador MIDI físico</p>
68
+ </header>
69
+
70
+ <div class="flex flex-col lg:flex-row gap-8">
71
+ <div class="lg:w-1/4 bg-gray-800 p-6 rounded-lg">
72
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
73
+ <i class="fas fa-cog mr-2"></i> Configurações
74
+ </h2>
75
+
76
+ <div class="mb-6">
77
+ <label class="block text-gray-400 mb-2">Dispositivo MIDI:</label>
78
+ <select id="midi-input" class="w-full bg-gray-700 text-white p-2 rounded">
79
+ <option value="">Nenhum selecionado</option>
80
+ </select>
81
+ </div>
82
+
83
+ <div class="mb-6">
84
+ <label class="block text-gray-400 mb-2">Oitavas:</label>
85
+ <input type="range" id="octave-range" min="1" max="6" value="3" class="w-full">
86
+ <div class="flex justify-between text-sm text-gray-400">
87
+ <span>1</span>
88
+ <span id="octave-value">3</span>
89
+ <span>6</span>
90
+ </div>
91
+ </div>
92
+
93
+ <div class="mb-6">
94
+ <label class="block text-gray-400 mb-2">Instrumento:</label>
95
+ <select id="instrument-select" class="w-full bg-gray-700 text-white p-2 rounded">
96
+ <option value="piano">Piano</option>
97
+ <option value="synth">Sintetizador</option>
98
+ <option value="organ">Órgão</option>
99
+ <option value="guitar">Guitarra</option>
100
+ <option value="strings">Cordas</option>
101
+ </select>
102
+ </div>
103
+
104
+ <div class="mb-6">
105
+ <label class="block text-gray-400 mb-2">Volume:</label>
106
+ <input type="range" id="volume-range" min="0" max="100" value="70" class="w-full">
107
+ <div class="flex justify-between text-sm text-gray-400">
108
+ <span>0</span>
109
+ <span id="volume-value">70</span>
110
+ <span>100</span>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="flex items-center mb-6">
115
+ <input type="checkbox" id="show-labels" class="mr-2" checked>
116
+ <label for="show-labels" class="text-gray-400">Mostrar notas</label>
117
+ </div>
118
+
119
+ <div class="bg-gray-700 p-4 rounded-lg">
120
+ <h3 class="font-medium mb-2 flex items-center">
121
+ <i class="fas fa-info-circle mr-2"></i> Status MIDI
122
+ </h3>
123
+ <div class="flex items-center">
124
+ <div id="midi-status" class="w-3 h-3 rounded-full bg-red-500 mr-2"></div>
125
+ <span id="midi-status-text">Desconectado</span>
126
+ </div>
127
+ <div id="midi-activity" class="mt-2 hidden">
128
+ <div class="flex items-center mb-1">
129
+ <div id="midi-indicator" class="w-3 h-3 rounded-full bg-green-500 mr-2"></div>
130
+ <span>Atividade MIDI</span>
131
+ </div>
132
+ <div class="text-sm text-gray-400">
133
+ Nota: <span id="current-note">-</span> |
134
+ Velocidade: <span id="current-velocity">-</span>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ <div class="lg:w-3/4">
141
+ <div class="keyboard-container mb-8">
142
+ <div class="keyboard bg-gray-800 p-6 rounded-lg">
143
+ <div class="flex relative" id="keyboard">
144
+ <!-- Teclas serão geradas pelo JavaScript -->
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <div class="bg-gray-800 p-6 rounded-lg">
150
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
151
+ <i class="fas fa-chart-bar mr-2"></i> Visualização MIDI
152
+ </h2>
153
+ <div class="h-40 bg-gray-900 rounded relative overflow-hidden">
154
+ <div id="midi-visualizer" class="absolute inset-0">
155
+ <!-- Ondas serão geradas pelo JavaScript -->
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <footer class="bg-gray-800 py-4 text-center text-gray-400 text-sm">
164
+ <p>Teclado Musical MIDI - Pressione as teclas do teclado físico ou clique nas teclas virtuais</p>
165
+ </footer>
166
+
167
+ <script>
168
+ document.addEventListener('DOMContentLoaded', async () => {
169
+ // Configuração inicial
170
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
171
+ let activeOscillators = {};
172
+ let currentInstrument = 'piano';
173
+ let volume = 0.7;
174
+ let octave = 3;
175
+ let showLabels = true;
176
+
177
+ // Elementos do DOM
178
+ const keyboardEl = document.getElementById('keyboard');
179
+ const midiInputSelect = document.getElementById('midi-input');
180
+ const octaveRange = document.getElementById('octave-range');
181
+ const octaveValue = document.getElementById('octave-value');
182
+ const instrumentSelect = document.getElementById('instrument-select');
183
+ const volumeRange = document.getElementById('volume-range');
184
+ const volumeValue = document.getElementById('volume-value');
185
+ const showLabelsCheckbox = document.getElementById('show-labels');
186
+ const midiStatus = document.getElementById('midi-status');
187
+ const midiStatusText = document.getElementById('midi-status-text');
188
+ const midiActivity = document.getElementById('midi-activity');
189
+ const currentNote = document.getElementById('current-note');
190
+ const currentVelocity = document.getElementById('current-velocity');
191
+ const midiVisualizer = document.getElementById('midi-visualizer');
192
+
193
+ // Notas musicais
194
+ const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
195
+ const whiteKeys = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
196
+ const blackKeys = ['C#', 'D#', 'F#', 'G#', 'A#'];
197
+
198
+ // Inicializa o teclado
199
+ function initKeyboard() {
200
+ keyboardEl.innerHTML = '';
201
+
202
+ // Cria 2 oitavas de teclas (14 teclas brancas)
203
+ for (let i = 0; i < 14; i++) {
204
+ const noteIndex = i % 7;
205
+ const noteName = whiteKeys[noteIndex];
206
+ const isSharp = false;
207
+
208
+ createKey(noteName, isSharp, i);
209
+
210
+ // Adiciona teclas pretas entre as brancas apropriadas
211
+ if (noteName !== 'E' && noteName !== 'B' && i < 13) {
212
+ const nextNoteIndex = (i + 1) % 7;
213
+ const nextNoteName = whiteKeys[nextNoteIndex];
214
+
215
+ if (blackKeys.includes(noteName + '#')) {
216
+ createKey(noteName + '#', true, i);
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ // Cria uma tecla individual
223
+ function createKey(noteName, isSharp, position) {
224
+ const key = document.createElement('div');
225
+ const keyClass = isSharp ? 'black-key' : 'white-key';
226
+ const width = isSharp ? 'w-8' : 'w-16';
227
+ const label = showLabels ? `<span class="key-label absolute bottom-2 text-xs ${isSharp ? 'text-white' : 'text-gray-700'}">${noteName}${octave + Math.floor(position/7)}</span>` : '';
228
+
229
+ key.className = `key ${keyClass} ${width} h-32 relative cursor-pointer flex items-end justify-center`;
230
+ key.innerHTML = label;
231
+ key.dataset.note = noteName;
232
+ key.dataset.octave = octave + Math.floor(position/7);
233
+
234
+ // Eventos de toque/clique
235
+ key.addEventListener('mousedown', () => playNote(noteName, octave + Math.floor(position/7), 100));
236
+ key.addEventListener('mouseup', () => stopNote(noteName, octave + Math.floor(position/7)));
237
+ key.addEventListener('mouseleave', () => stopNote(noteName, octave + Math.floor(position/7)));
238
+
239
+ keyboardEl.appendChild(key);
240
+ }
241
+
242
+ // Toca uma nota
243
+ function playNote(note, octave, velocity) {
244
+ const fullNote = `${note}${octave}`;
245
+ if (activeOscillators[fullNote]) return;
246
+
247
+ const freq = getNoteFrequency(note, octave);
248
+ const osc = audioContext.createOscillator();
249
+ const gainNode = audioContext.createGain();
250
+
251
+ // Configura o oscilador baseado no instrumento selecionado
252
+ configureOscillator(osc, gainNode);
253
+
254
+ gainNode.gain.value = volume * (velocity / 127);
255
+ osc.frequency.value = freq;
256
+ osc.connect(gainNode);
257
+ gainNode.connect(audioContext.destination);
258
+ osc.start();
259
+
260
+ activeOscillators[fullNote] = { oscillator: osc, gainNode: gainNode };
261
+
262
+ // Atualiza a interface
263
+ highlightKey(note, octave, true);
264
+ updateMidiVisualizer(freq, velocity);
265
+
266
+ // Atualiza o status MIDI
267
+ currentNote.textContent = `${note}${octave}`;
268
+ currentVelocity.textContent = velocity;
269
+ document.getElementById('midi-indicator').classList.add('midi-indicator');
270
+ setTimeout(() => {
271
+ document.getElementById('midi-indicator').classList.remove('midi-indicator');
272
+ }, 500);
273
+ }
274
+
275
+ // Para uma nota
276
+ function stopNote(note, octave) {
277
+ const fullNote = `${note}${octave}`;
278
+ if (!activeOscillators[fullNote]) return;
279
+
280
+ // Adiciona um pequeno release para evitar clicks
281
+ activeOscillators[fullNote].gainNode.gain.setValueAtTime(
282
+ activeOscillators[fullNote].gainNode.gain.value,
283
+ audioContext.currentTime
284
+ );
285
+ activeOscillators[fullNote].gainNode.gain.exponentialRampToValueAtTime(
286
+ 0.0001,
287
+ audioContext.currentTime + 0.03
288
+ );
289
+
290
+ setTimeout(() => {
291
+ activeOscillators[fullNote].oscillator.stop();
292
+ delete activeOscillators[fullNote];
293
+ }, 30);
294
+
295
+ highlightKey(note, octave, false);
296
+ }
297
+
298
+ // Configura o oscilador baseado no instrumento selecionado
299
+ function configureOscillator(osc, gainNode) {
300
+ switch(currentInstrument) {
301
+ case 'piano':
302
+ osc.type = 'sine';
303
+ // Simula o ataque rápido e decay do piano
304
+ gainNode.gain.setValueAtTime(0, audioContext.currentTime);
305
+ gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + 0.01);
306
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1);
307
+ break;
308
+ case 'synth':
309
+ osc.type = 'sawtooth';
310
+ // Filtro para suavizar o som
311
+ const filter = audioContext.createBiquadFilter();
312
+ filter.type = 'lowpass';
313
+ filter.frequency.value = 2000;
314
+ osc.connect(filter);
315
+ filter.connect(gainNode);
316
+ break;
317
+ case 'organ':
318
+ osc.type = 'sine';
319
+ // Adiciona harmônicos para simular órgão
320
+ const osc2 = audioContext.createOscillator();
321
+ osc2.type = 'square';
322
+ osc2.frequency.value = osc.frequency.value * 2;
323
+ osc2.connect(gainNode);
324
+ osc2.start();
325
+ activeOscillators[`${note}${octave}-organ`] = { oscillator: osc2, gainNode: gainNode };
326
+ break;
327
+ case 'guitar':
328
+ osc.type = 'sine';
329
+ // Simula o sustain da guitarra
330
+ gainNode.gain.setValueAtTime(0, audioContext.currentTime);
331
+ gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + 0.1);
332
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1.5);
333
+ break;
334
+ case 'strings':
335
+ osc.type = 'sine';
336
+ // Simula o ataque lento das cordas
337
+ gainNode.gain.setValueAtTime(0, audioContext.currentTime);
338
+ gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + 0.5);
339
+ gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 3);
340
+ break;
341
+ default:
342
+ osc.type = 'sine';
343
+ }
344
+ }
345
+
346
+ // Destaca a tecla quando pressionada
347
+ function highlightKey(note, octave, isActive) {
348
+ const keys = document.querySelectorAll('.key');
349
+ keys.forEach(key => {
350
+ if (key.dataset.note === note && parseInt(key.dataset.octave) === octave) {
351
+ if (isActive) {
352
+ key.classList.add('active');
353
+ } else {
354
+ key.classList.remove('active');
355
+ }
356
+ }
357
+ });
358
+ }
359
+
360
+ // Atualiza o visualizador MIDI
361
+ function updateMidiVisualizer(frequency, velocity) {
362
+ // Limpa o visualizador
363
+ midiVisualizer.innerHTML = '';
364
+
365
+ // Cria elementos de onda baseados na frequência e velocidade
366
+ const waveCount = Math.floor(frequency / 100);
367
+ const waveHeight = (velocity / 127) * 100;
368
+
369
+ for (let i = 0; i < waveCount; i++) {
370
+ const wave = document.createElement('div');
371
+ wave.className = 'absolute bg-blue-500 opacity-50 rounded-full';
372
+
373
+ // Posiciona e dimensiona a onda
374
+ const left = Math.random() * 100;
375
+ const width = 2 + Math.random() * 8;
376
+ const height = waveHeight * (0.5 + Math.random() * 0.5);
377
+ const top = 50 - (height / 2) + (Math.random() * 20 - 10);
378
+
379
+ wave.style.left = `${left}%`;
380
+ wave.style.top = `${top}%`;
381
+ wave.style.width = `${width}px`;
382
+ wave.style.height = `${height}px`;
383
+
384
+ midiVisualizer.appendChild(wave);
385
+
386
+ // Anima a onda
387
+ wave.animate([
388
+ { transform: 'scale(1)', opacity: 0.5 },
389
+ { transform: 'scale(1.5)', opacity: 0 }
390
+ ], {
391
+ duration: 1000,
392
+ easing: 'ease-out'
393
+ });
394
+ }
395
+ }
396
+
397
+ // Obtém a frequência de uma nota
398
+ function getNoteFrequency(note, octave) {
399
+ const A4 = 440;
400
+ let n;
401
+
402
+ if (note.length === 1) {
403
+ n = (octave - 4) * 12 + notes.indexOf(note);
404
+ } else {
405
+ n = (octave - 4) * 12 + notes.indexOf(note);
406
+ }
407
+
408
+ return A4 * Math.pow(2, n / 12);
409
+ }
410
+
411
+ // Configuração MIDI
412
+ async function setupMidi() {
413
+ try {
414
+ const midiAccess = await navigator.requestMIDIAccess();
415
+ updateMidiDevices(midiAccess);
416
+
417
+ midiAccess.onstatechange = () => updateMidiDevices(midiAccess);
418
+
419
+ midiStatus.classList.remove('bg-red-500');
420
+ midiStatus.classList.add('bg-green-500');
421
+ midiStatusText.textContent = 'MIDI disponível';
422
+ midiActivity.classList.remove('hidden');
423
+ } catch (error) {
424
+ console.error('Erro ao acessar MIDI:', error);
425
+ midiStatusText.textContent = 'MIDI não suportado';
426
+ }
427
+ }
428
+
429
+ // Atualiza a lista de dispositivos MIDI
430
+ function updateMidiDevices(midiAccess) {
431
+ midiInputSelect.innerHTML = '<option value="">Nenhum selecionado</option>';
432
+
433
+ const inputs = midiAccess.inputs.values();
434
+ for (let input = inputs.next(); !input.done; input = inputs.next()) {
435
+ const option = document.createElement('option');
436
+ option.value = input.value.id;
437
+ option.textContent = input.value.name;
438
+ midiInputSelect.appendChild(option);
439
+
440
+ // Remove listeners antigos
441
+ input.value.onmidimessage = null;
442
+
443
+ // Adiciona novo listener se selecionado
444
+ if (midiInputSelect.value === input.value.id) {
445
+ input.value.onmidimessage = handleMidiMessage;
446
+ }
447
+ }
448
+ }
449
+
450
+ // Manipula mensagens MIDI
451
+ function handleMidiMessage(message) {
452
+ const [command, note, velocity] = message.data;
453
+ const action = command >> 4;
454
+ const channel = command & 0xf;
455
+
456
+ // Note on (144) ou Note off (128)
457
+ if (action === 0x9 || action === 0x8) {
458
+ const noteName = notes[note % 12];
459
+ const noteOctave = Math.floor(note / 12) - 1;
460
+
461
+ if (action === 0x9 && velocity > 0) {
462
+ playNote(noteName, noteOctave, velocity);
463
+ } else {
464
+ stopNote(noteName, noteOctave);
465
+ }
466
+ }
467
+ }
468
+
469
+ // Event listeners
470
+ octaveRange.addEventListener('input', () => {
471
+ octave = parseInt(octaveRange.value);
472
+ octaveValue.textContent = octave;
473
+ initKeyboard();
474
+ });
475
+
476
+ instrumentSelect.addEventListener('change', () => {
477
+ currentInstrument = instrumentSelect.value;
478
+ });
479
+
480
+ volumeRange.addEventListener('input', () => {
481
+ volume = parseInt(volumeRange.value) / 100;
482
+ volumeValue.textContent = volumeRange.value;
483
+ });
484
+
485
+ showLabelsCheckbox.addEventListener('change', () => {
486
+ showLabels = showLabelsCheckbox.checked;
487
+ initKeyboard();
488
+ });
489
+
490
+ midiInputSelect.addEventListener('change', () => {
491
+ navigator.requestMIDIAccess().then(midiAccess => {
492
+ const inputs = midiAccess.inputs.values();
493
+ for (let input = inputs.next(); !input.done; input = inputs.next()) {
494
+ input.value.onmidimessage = null;
495
+ if (input.value.id === midiInputSelect.value) {
496
+ input.value.onmidimessage = handleMidiMessage;
497
+ }
498
+ }
499
+ });
500
+ });
501
+
502
+ // Eventos de teclado físico
503
+ document.addEventListener('keydown', (e) => {
504
+ const keyMap = {
505
+ 'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#', 'd': 'E',
506
+ 'f': 'F', 't': 'F#', 'g': 'G', 'y': 'G#', 'h': 'A',
507
+ 'u': 'A#', 'j': 'B', 'k': 'C', 'o': 'C#', 'l': 'D'
508
+ };
509
+
510
+ if (keyMap[e.key.toLowerCase()]) {
511
+ playNote(keyMap[e.key.toLowerCase()], octave, 100);
512
+ }
513
+ });
514
+
515
+ document.addEventListener('keyup', (e) => {
516
+ const keyMap = {
517
+ 'a': 'C', 'w': 'C#', 's': 'D', 'e': 'D#', 'd': 'E',
518
+ 'f': 'F', 't': 'F#', 'g': 'G', 'y': 'G#', 'h': 'A',
519
+ 'u': 'A#', 'j': 'B', 'k': 'C', 'o': 'C#', 'l': 'D'
520
+ };
521
+
522
+ if (keyMap[e.key.toLowerCase()]) {
523
+ stopNote(keyMap[e.key.toLowerCase()], octave);
524
+ }
525
+ });
526
+
527
+ // Inicializa o aplicativo
528
+ initKeyboard();
529
+ setupMidi();
530
+ });
531
+ </script>
532
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - ��� <a href="https://enzostvs-deepsite.hf.space?remix=mugnatto/midi-keyboard" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
533
+ </html>