awacke1 commited on
Commit
31dec23
·
verified ·
1 Parent(s): 730e20f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -1
app.py CHANGED
@@ -306,4 +306,156 @@ def main():
306
  # MIDI I/O
307
  st.subheader("MIDI Ports")
308
  in_ports = mido.get_input_names()
309
- out_
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  # MIDI I/O
307
  st.subheader("MIDI Ports")
308
  in_ports = mido.get_input_names()
309
+ out_ports = mido.get_output_names()
310
+ input_choice = st.selectbox("MIDI Input", ["None"] + in_ports)
311
+ output_choice = st.selectbox("MIDI Output", ["None"] + out_ports)
312
+
313
+ # Manage opening/closing
314
+ if 'midi_in' not in st.session_state:
315
+ st.session_state.midi_in = None
316
+ if 'midi_out' not in st.session_state:
317
+ st.session_state.midi_out = None
318
+
319
+ # Callback for incoming hardware MIDI
320
+ def midi_in_callback(msg):
321
+ if msg.type in ['note_on', 'note_off']:
322
+ # Convert to dictionary
323
+ event = {
324
+ 'type': 'noteOn' if msg.type == 'note_on' else 'noteOff',
325
+ 'note': msg.note,
326
+ 'velocity': msg.velocity
327
+ }
328
+ st.session_state.incoming_events.put(event)
329
+
330
+ def open_input(port):
331
+ if port == "None":
332
+ return None
333
+ return mido.open_input(port, callback=midi_in_callback)
334
+
335
+ def open_output(port):
336
+ if port == "None":
337
+ return None
338
+ return mido.open_output(port)
339
+
340
+ # Re-open if changed
341
+ if st.session_state.midi_in and st.session_state.midi_in.name != input_choice:
342
+ st.session_state.midi_in.close()
343
+ st.session_state.midi_in = open_input(input_choice)
344
+ elif not st.session_state.midi_in and input_choice != "None":
345
+ st.session_state.midi_in = open_input(input_choice)
346
+
347
+ if st.session_state.midi_out and st.session_state.midi_out.name != output_choice:
348
+ st.session_state.midi_out.close()
349
+ st.session_state.midi_out = open_output(output_choice)
350
+ elif not st.session_state.midi_out and output_choice != "None":
351
+ st.session_state.midi_out = open_output(output_choice)
352
+
353
+ st.write("Press keys on hardware (if connected) or use the on-screen UI below:")
354
+
355
+ # On-screen 5-octave keyboard
356
+ st.subheader("5-Octave Keyboard")
357
+ components.html(get_keyboard_html(), height=220)
358
+
359
+ # Drum pads
360
+ st.subheader("Drum Pads (16)")
361
+ components.html(get_drum_pads_html(), height=220)
362
+
363
+ # Hidden script to route postMessage -> Streamlit
364
+ components.html("""
365
+ <script>
366
+ window.addEventListener('message', function(e) {
367
+ if (e.data.type === 'midiEvent') {
368
+ // forward to parent's postMessage
369
+ window.parent.postMessage({
370
+ type: 'streamlit:message',
371
+ data: {
372
+ type: 'midi_event',
373
+ event: e.data.data
374
+ }
375
+ }, '*');
376
+ } else if (e.data.type === 'drumTrigger') {
377
+ window.parent.postMessage({
378
+ type: 'streamlit:message',
379
+ data: {
380
+ type: 'drum_event',
381
+ event: e.data.data
382
+ }
383
+ }, '*');
384
+ }
385
+ });
386
+ </script>
387
+ """, height=0)
388
+
389
+ # We'll store inbound events in a queue
390
+ if 'incoming_events' not in st.session_state:
391
+ st.session_state.incoming_events = Queue()
392
+
393
+ # A small debug output
394
+ debug_area = st.empty()
395
+
396
+ # We define a function to dispatch events to pyo and optionally to MIDI out
397
+ def dispatch_event(event):
398
+ etype = event['type']
399
+ if etype in ('noteOn', 'noteOff'):
400
+ note = event['note']
401
+ vel = event.get('velocity', 100)
402
+ # Arp logic or direct
403
+ if use_arp:
404
+ # Send to arpeggiator
405
+ if etype == 'noteOn':
406
+ st.session_state.arpeggiator.note_on(note)
407
+ else:
408
+ st.session_state.arpeggiator.note_off(note)
409
+ else:
410
+ # Trigger directly
411
+ if etype == 'noteOn':
412
+ note_on(note, vel)
413
+ else:
414
+ note_off(note)
415
+
416
+ # Also echo to output port
417
+ if st.session_state.midi_out:
418
+ if etype == 'noteOn':
419
+ out_msg = Message('note_on', note=note, velocity=vel)
420
+ st.session_state.midi_out.send(out_msg)
421
+ else:
422
+ out_msg = Message('note_off', note=note, velocity=0)
423
+ st.session_state.midi_out.send(out_msg)
424
+
425
+ debug_area.write(f"MIDI Note Event -> {event}")
426
+
427
+ elif etype == 'drum':
428
+ # for drum, we have event['padIndex']
429
+ idx = event['padIndex']
430
+ vel = event.get('velocity', 100)
431
+ drum_trigger(idx, vel)
432
+ debug_area.write(f"Drum Trigger -> Pad {idx}")
433
+ else:
434
+ pass
435
+
436
+ # We'll do a short poll in the Streamlit loop
437
+ # (In actual usage, you'd use a Streamlit custom component to pass these more elegantly.)
438
+ def poll_events():
439
+ while not st.session_state.incoming_events.empty():
440
+ e = st.session_state.incoming_events.get_nowait()
441
+ dispatch_event(e)
442
+
443
+ poll_events()
444
+
445
+ st.write("Try pressing the on-screen keys/pads or your hardware keyboard/pads. Enjoy!")
446
+
447
+ # Cleanup
448
+ def cleanup():
449
+ st.session_state.arpeggiator.stop()
450
+ for note in list(active_oscillators.keys()):
451
+ note_off(note)
452
+ if st.session_state.midi_in:
453
+ st.session_state.midi_in.close()
454
+ if st.session_state.midi_out:
455
+ st.session_state.midi_out.close()
456
+ s.stop()
457
+
458
+ st.on_session_end(cleanup)
459
+
460
+ if __name__ == "__main__":
461
+ main()