awacke1 commited on
Commit
3672ad1
Β·
verified Β·
1 Parent(s): c43c03a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -330
app.py CHANGED
@@ -1,4 +1,3 @@
1
- import spaces
2
  import random
3
  import argparse
4
  import glob
@@ -22,279 +21,94 @@ from midi_synthesizer import MidiSynthesizer
22
  MAX_SEED = np.iinfo(np.int32).max
23
  in_space = os.getenv("SYSTEM") == "spaces"
24
 
25
- # Chord to emoji mapping
26
- CHORD_EMOJIS = {
27
- 'C': '🎡', 'Cm': '🎢', 'C7': '🎼', 'Cmaj7': '🎹', 'Cm7': '🎻',
28
- 'D': 'πŸ₯', 'Dm': 'πŸͺ˜', 'D7': '🎷', 'Dmaj7': '🎺', 'Dm7': 'πŸͺ•',
29
- 'E': '🎸', 'Em': '🎻', 'E7': '🎡', 'Emaj7': '🎢', 'Em7': '🎼',
30
- 'F': '🎹', 'Fm': '🎸', 'F7': '🎻', 'Fmaj7': '🎷', 'Fm7': '🎺',
31
- 'G': 'πŸͺ—', 'Gm': '🎡', 'G7': '🎢', 'Gmaj7': '🎼', 'Gm7': '🎹',
32
- 'A': '🎸', 'Am': '🎻', 'A7': '🎷', 'Amaj7': '🎺', 'Am7': 'πŸͺ•',
33
- 'B': '🎡', 'Bm': '🎢', 'B7': '🎼', 'Bmaj7': '🎹', 'Bm7': '🎻'
 
 
 
 
 
34
  }
35
 
36
- # Chord note definitions (MIDI note numbers)
37
- CHORD_NOTES = {
38
- 'C': [60, 64, 67], # C major (C, E, G)
39
- 'Cm': [60, 63, 67], # C minor (C, Eb, G)
40
- 'C7': [60, 64, 67, 70], # C7 (C, E, G, Bb)
41
- 'Cmaj7': [60, 64, 67, 71], # Cmaj7 (C, E, G, B)
42
- 'Cm7': [60, 63, 67, 70], # Cm7 (C, Eb, G, Bb)
43
-
44
- 'D': [62, 66, 69], # D major (D, F#, A)
45
- 'Dm': [62, 65, 69], # D minor (D, F, A)
46
- 'D7': [62, 66, 69, 72], # D7 (D, F#, A, C)
47
- 'Dmaj7': [62, 66, 69, 73], # Dmaj7 (D, F#, A, C#)
48
- 'Dm7': [62, 65, 69, 72], # Dm7 (D, F, A, C)
49
-
50
- 'E': [64, 68, 71], # E major (E, G#, B)
51
- 'Em': [64, 67, 71], # E minor (E, G, B)
52
- 'E7': [64, 68, 71, 74], # E7 (E, G#, B, D)
53
- 'Emaj7': [64, 68, 71, 75], # Emaj7 (E, G#, B, D#)
54
- 'Em7': [64, 67, 71, 74], # Em7 (E, G, B, D)
55
-
56
- 'F': [65, 69, 72], # F major (F, A, C)
57
- 'Fm': [65, 68, 72], # F minor (F, Ab, C)
58
- 'F7': [65, 69, 72, 75], # F7 (F, A, C, Eb)
59
- 'Fmaj7': [65, 69, 72, 76], # Fmaj7 (F, A, C, E)
60
- 'Fm7': [65, 68, 72, 75], # Fm7 (F, Ab, C, Eb)
61
-
62
- 'G': [67, 71, 74], # G major (G, B, D)
63
- 'Gm': [67, 70, 74], # G minor (G, Bb, D)
64
- 'G7': [67, 71, 74, 77], # G7 (G, B, D, F)
65
- 'Gmaj7': [67, 71, 74, 78], # Gmaj7 (G, B, D, F#)
66
- 'Gm7': [67, 70, 74, 77], # Gm7 (G, Bb, D, F)
67
-
68
- 'A': [69, 73, 76], # A major (A, C#, E)
69
- 'Am': [69, 72, 76], # A minor (A, C, E)
70
- 'A7': [69, 73, 76, 79], # A7 (A, C#, E, G)
71
- 'Amaj7': [69, 73, 76, 80], # Amaj7 (A, C#, E, G#)
72
- 'Am7': [69, 72, 76, 79], # Am7 (A, C, E, G)
73
-
74
- 'B': [71, 75, 78], # B major (B, D#, F#)
75
- 'Bm': [71, 74, 78], # B minor (B, D, F#)
76
- 'B7': [71, 75, 78, 81], # B7 (B, D#, F#, A)
77
- 'Bmaj7': [71, 75, 78, 82], # Bmaj7 (B, D#, F#, A#)
78
- 'Bm7': [71, 74, 78, 81] # Bm7 (B, D, F#, A)
79
- }
80
-
81
- # MIDI device manager
82
  class MIDIDeviceManager:
83
- def __init__(self):
84
- self.midi_out = rtmidi.MidiOut()
85
- self.available_ports = self.midi_out.get_ports()
86
- self.current_port = None
87
-
88
- def get_available_devices(self):
89
- """Return list of available MIDI output devices"""
90
- self.available_ports = self.midi_out.get_ports()
91
- return self.available_ports
92
-
93
- def open_port(self, port_index):
94
- """Open a MIDI port by index"""
95
- if 0 <= port_index < len(self.available_ports):
96
- if self.current_port is not None:
97
- self.midi_out.close_port()
98
- self.midi_out.open_port(port_index)
99
- self.current_port = port_index
100
- return True
101
- return False
102
-
103
- def send_note_on(self, note, velocity=64, channel=0):
104
- """Send MIDI note on message"""
105
- if self.current_port is not None:
106
- message = [0x90 + channel, note, velocity]
107
- self.midi_out.send_message(message)
108
-
109
- def send_note_off(self, note, velocity=0, channel=0):
110
- """Send MIDI note off message"""
111
- if self.current_port is not None:
112
- message = [0x80 + channel, note, velocity]
113
- self.midi_out.send_message(message)
114
-
115
- def send_program_change(self, program, channel=0):
116
- """Send MIDI program change message"""
117
- if self.current_port is not None:
118
- message = [0xC0 + channel, program]
119
- self.midi_out.send_message(message)
120
-
121
- def play_chord(self, chord_name, velocity=80, channel=0, duration=None):
122
- """Play a chord by name with optional automatic release"""
123
- if chord_name in CHORD_NOTES:
124
- notes = CHORD_NOTES[chord_name]
125
- for note in notes:
126
- self.send_note_on(note, velocity, channel)
127
-
128
- if duration is not None:
129
- # Automatic note off after duration
130
- time.sleep(duration)
131
- for note in notes:
132
- self.send_note_off(note, 0, channel)
133
-
134
- def release_chord(self, chord_name, channel=0):
135
- """Release all notes in a chord"""
136
- if chord_name in CHORD_NOTES:
137
- notes = CHORD_NOTES[chord_name]
138
- for note in notes:
139
- self.send_note_off(note, 0, channel)
140
-
141
- def close(self):
142
- """Close current MIDI port"""
143
- if self.current_port is not None:
144
- self.midi_out.close_port()
145
- self.current_port = None
146
 
147
  # Global MIDI manager
148
  midi_manager = MIDIDeviceManager()
149
 
150
- def create_msg(name, data):
151
- return {"name": name, "data": data}
152
-
153
- def send_msgs(msgs):
154
- return json.dumps(msgs)
155
-
156
- def create_chord_events(chord, duration=480, velocity=80):
157
- """Create MIDI events for a chord"""
158
- events = []
159
-
160
- if chord in CHORD_NOTES:
161
- notes = CHORD_NOTES[chord]
162
- # Note on events
163
- for note in notes:
164
- events.append(['note_on', 0, 0, 0, 0, note, velocity])
165
-
166
- # Note off events after specified duration
167
- for note in notes:
168
- events.append(['note_off', duration, 0, 0, 0, note, 0])
169
-
170
- return events
171
-
172
- def add_chord_to_queue(chord_name, chord_queue, max_queue_size=8):
173
- """Add a chord to the playback queue"""
174
- if len(chord_queue) >= max_queue_size:
175
- chord_queue.pop(0) # Remove oldest chord
176
- chord_queue.append(chord_name)
177
- return chord_queue
178
-
179
- def play_chord_on_device(chord_name, midi_device_index):
180
- """Play a chord on the selected MIDI device"""
181
- if midi_device_index is not None and midi_device_index >= 0:
182
- midi_manager.open_port(midi_device_index)
183
- midi_manager.play_chord(chord_name, duration=0.5)
184
- return chord_name
185
-
186
- def play_chord_sequence(chord_queue, midi_device_index, tempo=120):
187
- """Play a sequence of chords at the specified tempo"""
188
- if midi_device_index is not None and midi_device_index >= 0:
189
- # Calculate timing based on tempo (beats per minute)
190
- beat_duration = 60.0 / tempo # seconds per beat
191
-
192
- midi_manager.open_port(midi_device_index)
193
-
194
- for chord in chord_queue:
195
- midi_manager.play_chord(chord, duration=beat_duration)
196
- # Add a small gap between chords
197
- time.sleep(0.05)
198
-
199
- return chord_queue
200
-
201
- def refresh_midi_devices():
202
- """Refresh the list of available MIDI devices"""
203
- return gr.Dropdown.update(choices=midi_manager.get_available_devices())
204
-
205
- def hf_hub_download_retry(repo_id, filename):
206
- print(f"downloading {repo_id} {filename}")
207
- retry = 0
208
- err = None
209
- while retry < 30:
210
- try:
211
- return hf_hub_download(repo_id=repo_id, filename=filename)
212
- except Exception as e:
213
- err = e
214
- retry += 1
215
- if err:
216
- raise err
217
-
218
- def load_javascript(dir="javascript"):
219
- scripts_list = glob.glob(f"{dir}/*.js")
220
- javascript = ""
221
- for path in scripts_list:
222
- with open(path, "r", encoding="utf8") as jsfile:
223
- js_content = jsfile.read()
224
- js_content = js_content.replace("const MIDI_OUTPUT_BATCH_SIZE=4;",
225
- f"const MIDI_OUTPUT_BATCH_SIZE={OUTPUT_BATCH_SIZE};")
226
- javascript += f"\n<!-- {path} --><script>{js_content}</script>"
227
- template_response_ori = gr.routes.templates.TemplateResponse
228
-
229
- def template_response(*args, **kwargs):
230
- res = template_response_ori(*args, **kwargs)
231
- res.body = res.body.replace(
232
- b'</head>', f'{javascript}</head>'.encode("utf8"))
233
- res.init_headers()
234
- return res
235
-
236
- gr.routes.templates.TemplateResponse = template_response
237
-
238
- def create_virtual_keyboard(chord_types):
239
- """Create virtual keyboard buttons organized by root note and chord type"""
240
- root_notes = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
241
- buttons = {}
242
-
243
- for root in root_notes:
244
- buttons[root] = {}
245
- for chord_type in chord_types:
246
- chord_name = f"{root}{chord_type}"
247
- emoji = CHORD_EMOJIS.get(chord_name, "🎡")
248
- buttons[root][chord_type] = (chord_name, emoji)
249
-
250
- return buttons
251
 
252
  if __name__ == "__main__":
253
  parser = argparse.ArgumentParser()
254
- parser.add_argument("--share", action="store_true", default=False, help="share gradio app")
255
- parser.add_argument("--port", type=int, default=7860, help="gradio server port")
256
- parser.add_argument("--device", type=str, default="cuda", help="device to run model")
257
- parser.add_argument("--batch", type=int, default=4, help="batch size")
258
- parser.add_argument("--max-gen", type=int, default=1024, help="max")
259
  opt = parser.parse_args()
260
  OUTPUT_BATCH_SIZE = opt.batch
261
 
262
- # Initialize MIDI device manager
263
  midi_manager = MIDIDeviceManager()
264
-
265
- # Initialize models (simplified version)
266
  soundfont_path = hf_hub_download_retry(repo_id="skytnt/midi-model", filename="soundfont.sf2")
267
  thread_pool = ThreadPoolExecutor(max_workers=OUTPUT_BATCH_SIZE)
268
  synthesizer = MidiSynthesizer(soundfont_path)
269
 
270
- # Define chord types to use in the virtual keyboard
271
  chord_types = ['', 'm', '7', 'maj7', 'm7']
272
-
273
- # Create virtual keyboard structure
274
  keyboard = create_virtual_keyboard(chord_types)
275
 
276
- # Define CSS for the virtual keyboard
277
  keyboard_css = """
278
- .chord-button {
279
- margin: 4px;
280
- min-width: 80px;
281
- height: 60px;
282
- font-size: 18px;
283
- font-weight: bold;
284
- border-radius: 8px;
285
- transition: all 0.2s;
286
- }
287
- .chord-button:active {
288
- transform: scale(0.95);
289
- }
290
- .chord-queue {
291
- padding: 10px;
292
- background: #f5f5f5;
293
- border-radius: 8px;
294
- min-height: 50px;
295
- font-size: 16px;
296
- margin-bottom: 15px;
297
- }
298
  .root-c { background-color: #FFCDD2; }
299
  .root-d { background-color: #F8BBD0; }
300
  .root-e { background-color: #E1BEE7; }
@@ -302,62 +116,67 @@ if __name__ == "__main__":
302
  .root-g { background-color: #C5CAE9; }
303
  .root-a { background-color: #BBDEFB; }
304
  .root-b { background-color: #B3E5FC; }
 
 
 
 
 
 
305
  """
306
 
307
  load_javascript()
308
  app = gr.Blocks(theme=gr.themes.Soft(), css=keyboard_css)
309
 
310
  with app:
311
- gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>🎡 Real-Time MIDI Chord Keyboard 🎡</h1>")
312
 
313
- js_msg = gr.Textbox(elem_id="msg_receiver", visible=False)
314
- js_msg.change(None, [js_msg], [], js="""
315
- (msg_json) =>{
316
- let msgs = JSON.parse(msg_json);
317
- executeCallbacks(msgReceiveCallbacks, msgs);
318
- return [];
319
- }
320
- """)
321
-
322
- # MIDI Device Settings
323
  with gr.Row():
324
  with gr.Column(scale=3):
325
- midi_device = gr.Dropdown(label="MIDI Output Device",
326
- choices=midi_manager.get_available_devices(),
327
- type="index")
 
 
 
328
  refresh_button = gr.Button("πŸ”„ Refresh MIDI Devices")
329
 
330
  with gr.Column(scale=1):
331
- tempo = gr.Slider(label="Tempo (BPM)",
332
- minimum=40,
333
- maximum=200,
334
- value=120,
335
- step=1)
336
-
337
- # Chord Queue Display
338
- chord_queue = gr.State([])
339
- queue_display = gr.Markdown("### Current Chord Queue\n*No chords in queue*",
340
- elem_classes=["chord-queue"])
 
 
341
 
342
- # Play queue button
343
- play_queue_button = gr.Button("▢️ Play Chord Sequence", variant="primary", size="lg")
 
 
344
 
345
- # Clear queue button
 
 
 
 
346
  clear_queue_button = gr.Button("πŸ—‘οΈ Clear Queue", variant="secondary")
347
 
348
- # Virtual Keyboard - Create sections for each root note
349
  gr.Markdown("## Virtual Chord Keyboard")
350
-
351
  for root in ['C', 'D', 'E', 'F', 'G', 'A', 'B']:
352
  with gr.Row():
353
  gr.Markdown(f"### {root}")
354
  for chord_type in chord_types:
355
  chord_name, emoji = keyboard[root][chord_type]
356
  display_name = chord_name if chord_type == '' else chord_name
357
- button = gr.Button(f"{emoji} {display_name}",
358
- elem_classes=[f"chord-button root-{root.lower()}"])
359
-
360
- # Connect the button to add chord to queue and play it immediately
361
  button.click(
362
  fn=play_chord_on_device,
363
  inputs=[gr.State(chord_name), midi_device],
@@ -372,64 +191,32 @@ if __name__ == "__main__":
372
  outputs=[queue_display]
373
  )
374
 
375
- # Connect refresh button
376
  refresh_button.click(
377
- fn=refresh_midi_devices,
378
  inputs=None,
379
- outputs=[midi_device]
 
 
 
 
 
 
 
 
380
  )
381
 
382
- # Connect play queue button
383
  play_queue_button.click(
384
  fn=play_chord_sequence,
385
  inputs=[chord_queue, midi_device, tempo],
386
  outputs=[chord_queue]
387
  )
388
 
389
- # Connect clear queue button
390
  clear_queue_button.click(
391
- fn=lambda: [],
392
- inputs=None,
393
- outputs=[chord_queue]
394
- ).then(
395
- fn=lambda: "### Current Chord Queue\n*No chords in queue*",
396
  inputs=None,
397
- outputs=[queue_display]
398
  )
399
-
400
- # MIDI Generation Settings (for advanced users)
401
- with gr.Accordion("Advanced MIDI Settings", open=False):
402
- with gr.Row():
403
- midi_channel = gr.Slider(label="MIDI Channel",
404
- minimum=0,
405
- maximum=15,
406
- value=0,
407
- step=1)
408
-
409
- instrument = gr.Dropdown(label="Instrument",
410
- choices=[(f"{i}: {name}", i) for i, name in enumerate([
411
- "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano",
412
- "Honky-tonk Piano", "Electric Piano 1", "Electric Piano 2", "Harpsichord",
413
- "Clavinet", "Celesta", "Glockenspiel", "Music Box", "Vibraphone",
414
- "Marimba", "Xylophone", "Tubular Bells", "Dulcimer"
415
- ])],
416
- value=0)
417
-
418
- velocity = gr.Slider(label="Velocity",
419
- minimum=1,
420
- maximum=127,
421
- value=80,
422
- step=1)
423
-
424
- # Program change button
425
- program_change_button = gr.Button("Send Program Change")
426
- program_change_button.click(
427
- fn=lambda inst, chan: midi_manager.send_program_change(inst, chan),
428
- inputs=[instrument, midi_channel],
429
- outputs=None
430
- )
431
 
432
  app.queue().launch(server_port=opt.port, share=opt.share, inbrowser=True, ssr_mode=False)
433
-
434
- # Clean up MIDI connections when the app closes
435
  midi_manager.close()
 
 
1
  import random
2
  import argparse
3
  import glob
 
21
  MAX_SEED = np.iinfo(np.int32).max
22
  in_space = os.getenv("SYSTEM") == "spaces"
23
 
24
+ # Chord definitions and emoji mappings remain the same
25
+ # ... (keeping your CHORD_EMOJIS and CHORD_NOTES dictionaries)
26
+
27
+ # Example song data for "Do You Believe in Love"
28
+ SONG_DATA = {
29
+ "title": "Do You Believe in Love",
30
+ "artist": "Huey Lewis and the News",
31
+ "progression": ["G", "D", "Em", "C"], # Simplified progression
32
+ "lyrics": [
33
+ "I was walking down a one-way street",
34
+ "Just a-looking for someone to meet",
35
+ "One woman who was looking for a man",
36
+ "Now I'm hoping that the feeling is right"
37
+ ]
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  class MIDIDeviceManager:
41
+ # ... (keeping your existing MIDIDeviceManager class)
42
+ def get_device_info(self):
43
+ """Return detailed info about connected MIDI devices"""
44
+ devices = self.get_available_devices()
45
+ if not devices:
46
+ return "No MIDI devices detected"
47
+ return "\n".join([f"Port {i}: {name}" for i, name in enumerate(devices)])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
  # Global MIDI manager
50
  midi_manager = MIDIDeviceManager()
51
 
52
+ def analyze_midi_file(midi_file_path):
53
+ """Analyze uploaded MIDI file to extract chord progression"""
54
+ try:
55
+ midi = MIDI.load(midi_file_path)
56
+ # Simple chord detection (this could be expanded with proper analysis)
57
+ detected_chords = []
58
+ for track in midi.tracks:
59
+ current_chord = []
60
+ for event in track.events:
61
+ if event.type == 'note_on' and event.velocity > 0:
62
+ current_chord.append(event.note)
63
+ if len(current_chord) >= 3: # Basic triad detection
64
+ for chord_name, notes in CHORD_NOTES.items():
65
+ if set(current_chord[:3]) == set(notes[:3]):
66
+ detected_chords.append(chord_name)
67
+ current_chord = []
68
+ break
69
+ return detected_chords if detected_chords else SONG_DATA["progression"]
70
+ except Exception as e:
71
+ return SONG_DATA["progression"] # Fallback to example progression
72
+
73
+ def generate_chord_sheet(chords, lyrics):
74
+ """Generate a formatted chord sheet with chords and lyrics"""
75
+ sheet = ""
76
+ for i, (chord, lyric) in enumerate(zip(chords * (len(lyrics) // len(chords) + 1), lyrics)):
77
+ sheet += f"{CHORD_EMOJIS.get(chord, '🎡')} {chord}\n"
78
+ sheet += f"{lyric}\n\n"
79
+ return sheet
80
+
81
+ def create_chord_visualizer(chords, lyrics):
82
+ """Create HTML visualization for chord sheet"""
83
+ html = "<div style='font-family: monospace; line-height: 1.5;'>"
84
+ for i, (chord, lyric) in enumerate(zip(chords * (len(lyrics) // len(chords) + 1), lyrics)):
85
+ html += f"<div style='margin-bottom: 10px;'>"
86
+ html += f"<span style='color: #2196F3; font-weight: bold;'>{CHORD_EMOJIS.get(chord, '🎡')} {chord}</span>"
87
+ html += f"<br>{lyric}</div>"
88
+ html += "</div>"
89
+ return html
90
+
91
+ # ... (keeping your existing helper functions like create_msg, etc.)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  if __name__ == "__main__":
94
  parser = argparse.ArgumentParser()
95
+ # ... (keeping your existing parser arguments)
 
 
 
 
96
  opt = parser.parse_args()
97
  OUTPUT_BATCH_SIZE = opt.batch
98
 
99
+ # Initialize MIDI components
100
  midi_manager = MIDIDeviceManager()
 
 
101
  soundfont_path = hf_hub_download_retry(repo_id="skytnt/midi-model", filename="soundfont.sf2")
102
  thread_pool = ThreadPoolExecutor(max_workers=OUTPUT_BATCH_SIZE)
103
  synthesizer = MidiSynthesizer(soundfont_path)
104
 
 
105
  chord_types = ['', 'm', '7', 'maj7', 'm7']
 
 
106
  keyboard = create_virtual_keyboard(chord_types)
107
 
108
+ # Enhanced CSS with visualizer styling
109
  keyboard_css = """
110
+ .chord-button { /* ... existing styles ... */ }
111
+ .chord-queue { /* ... existing styles ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  .root-c { background-color: #FFCDD2; }
113
  .root-d { background-color: #F8BBD0; }
114
  .root-e { background-color: #E1BEE7; }
 
116
  .root-g { background-color: #C5CAE9; }
117
  .root-a { background-color: #BBDEFB; }
118
  .root-b { background-color: #B3E5FC; }
119
+ .visualizer-container {
120
+ background: #f9f9f9;
121
+ padding: 15px;
122
+ border-radius: 8px;
123
+ margin-top: 15px;
124
+ }
125
  """
126
 
127
  load_javascript()
128
  app = gr.Blocks(theme=gr.themes.Soft(), css=keyboard_css)
129
 
130
  with app:
131
+ gr.Markdown("<h1 style='text-align: center;'>🎡 Chord Sheet Generator & Visualizer 🎡</h1>")
132
 
133
+ # MIDI Device Info
 
 
 
 
 
 
 
 
 
134
  with gr.Row():
135
  with gr.Column(scale=3):
136
+ midi_device = gr.Dropdown(label="MIDI Output Device",
137
+ choices=midi_manager.get_available_devices(),
138
+ type="index")
139
+ device_info = gr.Textbox(label="Connected MIDI Devices",
140
+ value=midi_manager.get_device_info(),
141
+ readonly=True)
142
  refresh_button = gr.Button("πŸ”„ Refresh MIDI Devices")
143
 
144
  with gr.Column(scale=1):
145
+ tempo = gr.Slider(label="Tempo (BPM)",
146
+ minimum=40,
147
+ maximum=200,
148
+ value=120,
149
+ step=1)
150
+
151
+ # MIDI File Upload and Chord Sheet Generation
152
+ with gr.Row():
153
+ midi_upload = gr.File(label="Upload MIDI File for Analysis")
154
+ chord_output = gr.Textbox(label="Generated Chord Sheet",
155
+ lines=10,
156
+ value=generate_chord_sheet(SONG_DATA["progression"], SONG_DATA["lyrics"]))
157
 
158
+ # Chord Visualizer
159
+ visualizer = gr.HTML(label="Chord Sheet Visualizer",
160
+ value=create_chord_visualizer(SONG_DATA["progression"], SONG_DATA["lyrics"]),
161
+ elem_classes=["visualizer-container"])
162
 
163
+ # Chord Queue and Playback
164
+ chord_queue = gr.State([])
165
+ queue_display = gr.Markdown("### Current Chord Queue\n*No chords in queue*",
166
+ elem_classes=["chord-queue"])
167
+ play_queue_button = gr.Button("▢️ Play Chord Sequence", variant="primary")
168
  clear_queue_button = gr.Button("πŸ—‘οΈ Clear Queue", variant="secondary")
169
 
170
+ # Virtual Keyboard
171
  gr.Markdown("## Virtual Chord Keyboard")
 
172
  for root in ['C', 'D', 'E', 'F', 'G', 'A', 'B']:
173
  with gr.Row():
174
  gr.Markdown(f"### {root}")
175
  for chord_type in chord_types:
176
  chord_name, emoji = keyboard[root][chord_type]
177
  display_name = chord_name if chord_type == '' else chord_name
178
+ button = gr.Button(f"{emoji} {display_name}",
179
+ elem_classes=[f"chord-button root-{root.lower()}"])
 
 
180
  button.click(
181
  fn=play_chord_on_device,
182
  inputs=[gr.State(chord_name), midi_device],
 
191
  outputs=[queue_display]
192
  )
193
 
194
+ # Event Handlers
195
  refresh_button.click(
196
+ fn=lambda: (midi_manager.get_available_devices(), midi_manager.get_device_info()),
197
  inputs=None,
198
+ outputs=[midi_device, device_info]
199
+ )
200
+
201
+ midi_upload.change(
202
+ fn=lambda file: (analyze_midi_file(file.name),
203
+ generate_chord_sheet(analyze_midi_file(file.name), SONG_DATA["lyrics"]),
204
+ create_chord_visualizer(analyze_midi_file(file.name), SONG_DATA["lyrics"])),
205
+ inputs=[midi_upload],
206
+ outputs=[chord_queue, chord_output, visualizer]
207
  )
208
 
 
209
  play_queue_button.click(
210
  fn=play_chord_sequence,
211
  inputs=[chord_queue, midi_device, tempo],
212
  outputs=[chord_queue]
213
  )
214
 
 
215
  clear_queue_button.click(
216
+ fn=lambda: ([], "### Current Chord Queue\n*No chords in queue*"),
 
 
 
 
217
  inputs=None,
218
+ outputs=[chord_queue, queue_display]
219
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
  app.queue().launch(server_port=opt.port, share=opt.share, inbrowser=True, ssr_mode=False)
 
 
222
  midi_manager.close()