awacke1 commited on
Commit
75a5b95
·
verified ·
1 Parent(s): f3b2412

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -0
app.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit.components.v1 as components
3
+ import rtmidi
4
+ import json
5
+ from pathlib import Path
6
+ import time
7
+
8
+ def create_midi_output():
9
+ """Initialize MIDI output"""
10
+ midi_out = rtmidi.MidiOut()
11
+ available_ports = midi_out.get_ports()
12
+
13
+ if available_ports:
14
+ st.sidebar.write("Available MIDI ports:", available_ports)
15
+ selected_port = st.sidebar.selectbox("Select MIDI Output", range(len(available_ports)),
16
+ format_func=lambda x: available_ports[x])
17
+ midi_out.open_port(selected_port)
18
+ else:
19
+ st.sidebar.warning("No MIDI output ports available")
20
+ midi_out.open_virtual_port("Virtual MIDI Output")
21
+
22
+ return midi_out
23
+
24
+ def get_piano_html():
25
+ """Return the HTML content for the piano keyboard"""
26
+ return """
27
+ <!DOCTYPE html>
28
+ <html>
29
+ <head>
30
+ <style>
31
+ #keyboard-container {
32
+ position: relative;
33
+ width: 100%;
34
+ max-width: 800px;
35
+ margin: 20px auto;
36
+ }
37
+ .note-label {
38
+ position: absolute;
39
+ bottom: 5px;
40
+ width: 100%;
41
+ text-align: center;
42
+ font-size: 12px;
43
+ pointer-events: none;
44
+ }
45
+ .white-note { color: #333; }
46
+ .black-note { color: #fff; }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <div id="keyboard-container">
51
+ <div id="keyboard"></div>
52
+ </div>
53
+
54
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/qwerty-hancock/0.10.0/qwerty-hancock.min.js"></script>
55
+ <script>
56
+ // Initialize keyboard
57
+ const keyboard = new QwertyHancock({
58
+ id: 'keyboard',
59
+ width: 800,
60
+ height: 150,
61
+ octaves: 2,
62
+ startNote: 'C4',
63
+ whiteKeyColour: 'white',
64
+ blackKeyColour: '#333',
65
+ activeColour: '#88c6ff'
66
+ });
67
+
68
+ // Note to MIDI number mapping
69
+ const noteToMidi = {
70
+ 'C4': 60, 'C#4': 61, 'D4': 62, 'D#4': 63, 'E4': 64, 'F4': 65,
71
+ 'F#4': 66, 'G4': 67, 'G#4': 68, 'A4': 69, 'A#4': 70, 'B4': 71,
72
+ 'C5': 72, 'C#5': 73, 'D5': 74, 'D#5': 75, 'E5': 76, 'F5': 77,
73
+ 'F#5': 78, 'G5': 79, 'G#5': 80, 'A5': 81, 'A#5': 82, 'B5': 83
74
+ };
75
+
76
+ // Add note labels
77
+ function addNoteLabels() {
78
+ const container = document.getElementById('keyboard');
79
+ const whiteKeys = container.querySelectorAll('[data-note-type="white"]');
80
+ const blackKeys = container.querySelectorAll('[data-note-type="black"]');
81
+
82
+ whiteKeys.forEach(key => {
83
+ const note = key.getAttribute('data-note');
84
+ const label = document.createElement('div');
85
+ label.className = 'note-label white-note';
86
+ label.textContent = noteToMidi[note];
87
+ key.appendChild(label);
88
+ });
89
+
90
+ blackKeys.forEach(key => {
91
+ const note = key.getAttribute('data-note');
92
+ const label = document.createElement('div');
93
+ label.className = 'note-label black-note';
94
+ label.textContent = noteToMidi[note];
95
+ key.appendChild(label);
96
+ });
97
+ }
98
+
99
+ // Send MIDI events to Streamlit
100
+ keyboard.keyDown = function(note, frequency) {
101
+ const midiNote = noteToMidi[note];
102
+ const event = {
103
+ type: 'noteOn',
104
+ note: midiNote,
105
+ velocity: 100
106
+ };
107
+ window.parent.postMessage({type: 'midiEvent', data: event}, '*');
108
+ };
109
+
110
+ keyboard.keyUp = function(note, frequency) {
111
+ const midiNote = noteToMidi[note];
112
+ const event = {
113
+ type: 'noteOff',
114
+ note: midiNote,
115
+ velocity: 0
116
+ };
117
+ window.parent.postMessage({type: 'midiEvent', data: event}, '*');
118
+ };
119
+
120
+ // Wait for the keyboard to be rendered
121
+ setTimeout(addNoteLabels, 100);
122
+ </script>
123
+ </body>
124
+ </html>
125
+ """
126
+
127
+ def main():
128
+ st.title("MIDI Piano Keyboard")
129
+ st.write("Click keys or use your computer keyboard (A-K and W-U for white and black keys)")
130
+
131
+ # Initialize MIDI output
132
+ midi_out = create_midi_output()
133
+
134
+ # Create a placeholder for MIDI messages
135
+ midi_message_placeholder = st.empty()
136
+
137
+ # Display the piano keyboard
138
+ components.html(
139
+ get_piano_html(),
140
+ height=200,
141
+ scrolling=False
142
+ )
143
+
144
+ # Handle MIDI events from JavaScript
145
+ if 'midi_events' not in st.session_state:
146
+ st.session_state.midi_events = []
147
+
148
+ def handle_midi_event(event):
149
+ if event['type'] == 'noteOn':
150
+ midi_out.send_message([0x90, event['note'], event['velocity']])
151
+ midi_message_placeholder.write(f"Note On: {event['note']}")
152
+ elif event['type'] == 'noteOff':
153
+ midi_out.send_message([0x80, event['note'], event['velocity']])
154
+ midi_message_placeholder.write(f"Note Off: {event['note']}")
155
+
156
+ # JavaScript callback handler
157
+ components.html(
158
+ """
159
+ <script>
160
+ window.addEventListener('message', function(e) {
161
+ if (e.data.type === 'midiEvent') {
162
+ window.parent.postMessage({
163
+ type: 'streamlit:message',
164
+ data: {
165
+ type: 'midi_event',
166
+ event: e.data.data
167
+ }
168
+ }, '*');
169
+ }
170
+ });
171
+ </script>
172
+ """,
173
+ height=0
174
+ )
175
+
176
+ # Add a loop to continuously check for new MIDI events
177
+ while True:
178
+ time.sleep(0.1) # Small delay to prevent excessive CPU usage
179
+
180
+ # Process any pending MIDI events
181
+ for event in st.session_state.midi_events:
182
+ handle_midi_event(event)
183
+ st.session_state.midi_events = []
184
+
185
+ if __name__ == "__main__":
186
+ main()