File size: 7,954 Bytes
75a5b95
 
 
 
 
827e184
 
75a5b95
827e184
 
 
 
75a5b95
827e184
 
 
 
 
 
 
 
 
 
 
 
75a5b95
827e184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75a5b95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
827e184
 
 
 
75a5b95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
827e184
 
 
 
 
 
 
 
75a5b95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import streamlit as st
import streamlit.components.v1 as components
import json
from pathlib import Path
import time
import subprocess
import sys

def check_midi_prerequisites():
    """Check and report MIDI system status"""
    issues = []
    solutions = []
    
    try:
        import rtmidi
    except ImportError:
        issues.append("python-rtmidi is not installed")
        solutions.append("pip install python-rtmidi")
    except OSError as e:
        issues.append(f"MIDI system error: {str(e)}")
        solutions.extend([
            "sudo apt-get install libasound2-dev",
            "sudo apt-get install alsa-utils",
            "sudo modprobe snd-seq-midi"
        ])
    
    if issues:
        st.error("MIDI System Issues Detected")
        st.write("Problems found:")
        for issue in issues:
            st.write(f"- {issue}")
            
        st.write("Try these solutions:")
        for solution in solutions:
            st.code(solution)
            
        st.write("After installing prerequisites, restart the application.")
        return False
    return True

def create_midi_output():
    """Initialize MIDI output with fallback options"""
    try:
        import rtmidi
        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 hardware MIDI ports found. Creating virtual port.")
            try:
                midi_out.open_virtual_port("Virtual MIDI Output")
            except rtmidi.SystemError:
                st.sidebar.error("Could not create virtual MIDI port. Running in simulation mode.")
                return None
        return midi_out
    except Exception as e:
        st.sidebar.error(f"MIDI system unavailable: {str(e)}")
        st.sidebar.info("Running in simulation mode (no MIDI output)")
        return None

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)")
    
    # Check MIDI prerequisites
    if not check_midi_prerequisites():
        return
    
    # 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 midi_out is None:
            # Simulation mode - just display the events
            if event['type'] == 'noteOn':
                midi_message_placeholder.write(f"Simulated Note On: {event['note']} (No MIDI Output)")
            else:
                midi_message_placeholder.write(f"Simulated Note Off: {event['note']} (No MIDI Output)")
            return
            
        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
    )

if __name__ == "__main__":
    main()