Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|