awacke1's picture
Create app.py
75a5b95 verified
raw
history blame
6.34 kB
import streamlit as st
import streamlit.components.v1 as components
import rtmidi
import json
from pathlib import Path
import time
def create_midi_output():
"""Initialize MIDI output"""
midi_out = rtmidi.MidiOut()
available_ports = midi_out.get_ports()
if available_ports:
st.sidebar.write("Available MIDI ports:", available_ports)
selected_port = st.sidebar.selectbox("Select MIDI Output", range(len(available_ports)),
format_func=lambda x: available_ports[x])
midi_out.open_port(selected_port)
else:
st.sidebar.warning("No MIDI output ports available")
midi_out.open_virtual_port("Virtual MIDI Output")
return midi_out
def get_piano_html():
"""Return the HTML content for the piano keyboard"""
return """
<!DOCTYPE html>
<html>
<head>
<style>
#keyboard-container {
position: relative;
width: 100%;
max-width: 800px;
margin: 20px auto;
}
.note-label {
position: absolute;
bottom: 5px;
width: 100%;
text-align: center;
font-size: 12px;
pointer-events: none;
}
.white-note { color: #333; }
.black-note { color: #fff; }
</style>
</head>
<body>
<div id="keyboard-container">
<div id="keyboard"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qwerty-hancock/0.10.0/qwerty-hancock.min.js"></script>
<script>
// Initialize keyboard
const keyboard = new QwertyHancock({
id: 'keyboard',
width: 800,
height: 150,
octaves: 2,
startNote: 'C4',
whiteKeyColour: 'white',
blackKeyColour: '#333',
activeColour: '#88c6ff'
});
// Note to MIDI number mapping
const noteToMidi = {
'C4': 60, 'C#4': 61, 'D4': 62, 'D#4': 63, 'E4': 64, 'F4': 65,
'F#4': 66, 'G4': 67, 'G#4': 68, 'A4': 69, 'A#4': 70, 'B4': 71,
'C5': 72, 'C#5': 73, 'D5': 74, 'D#5': 75, 'E5': 76, 'F5': 77,
'F#5': 78, 'G5': 79, 'G#5': 80, 'A5': 81, 'A#5': 82, 'B5': 83
};
// Add note labels
function addNoteLabels() {
const container = document.getElementById('keyboard');
const whiteKeys = container.querySelectorAll('[data-note-type="white"]');
const blackKeys = container.querySelectorAll('[data-note-type="black"]');
whiteKeys.forEach(key => {
const note = key.getAttribute('data-note');
const label = document.createElement('div');
label.className = 'note-label white-note';
label.textContent = noteToMidi[note];
key.appendChild(label);
});
blackKeys.forEach(key => {
const note = key.getAttribute('data-note');
const label = document.createElement('div');
label.className = 'note-label black-note';
label.textContent = noteToMidi[note];
key.appendChild(label);
});
}
// Send MIDI events to Streamlit
keyboard.keyDown = function(note, frequency) {
const midiNote = noteToMidi[note];
const event = {
type: 'noteOn',
note: midiNote,
velocity: 100
};
window.parent.postMessage({type: 'midiEvent', data: event}, '*');
};
keyboard.keyUp = function(note, frequency) {
const midiNote = noteToMidi[note];
const event = {
type: 'noteOff',
note: midiNote,
velocity: 0
};
window.parent.postMessage({type: 'midiEvent', data: event}, '*');
};
// Wait for the keyboard to be rendered
setTimeout(addNoteLabels, 100);
</script>
</body>
</html>
"""
def main():
st.title("MIDI Piano Keyboard")
st.write("Click keys or use your computer keyboard (A-K and W-U for white and black keys)")
# Initialize MIDI output
midi_out = create_midi_output()
# Create a placeholder for MIDI messages
midi_message_placeholder = st.empty()
# Display the piano keyboard
components.html(
get_piano_html(),
height=200,
scrolling=False
)
# Handle MIDI events from JavaScript
if 'midi_events' not in st.session_state:
st.session_state.midi_events = []
def handle_midi_event(event):
if event['type'] == 'noteOn':
midi_out.send_message([0x90, event['note'], event['velocity']])
midi_message_placeholder.write(f"Note On: {event['note']}")
elif event['type'] == 'noteOff':
midi_out.send_message([0x80, event['note'], event['velocity']])
midi_message_placeholder.write(f"Note Off: {event['note']}")
# JavaScript callback handler
components.html(
"""
<script>
window.addEventListener('message', function(e) {
if (e.data.type === 'midiEvent') {
window.parent.postMessage({
type: 'streamlit:message',
data: {
type: 'midi_event',
event: e.data.data
}
}, '*');
}
});
</script>
""",
height=0
)
# Add a loop to continuously check for new MIDI events
while True:
time.sleep(0.1) # Small delay to prevent excessive CPU usage
# Process any pending MIDI events
for event in st.session_state.midi_events:
handle_midi_event(event)
st.session_state.midi_events = []
if __name__ == "__main__":
main()