-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinputs.py
More file actions
306 lines (254 loc) · 9.12 KB
/
Copy pathinputs.py
File metadata and controls
306 lines (254 loc) · 9.12 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
import threading
import mido
class KeyboardInput:
"""
Handles computer keyboard input and converts to MIDI note events.
"""
def __init__(self, synth, on_note_callback=None):
"""
Args:
synth: RealtimeSynth instance
on_note_callback: Optional callback for note events (for display updates)
"""
self.synth = synth
self.on_note_callback = on_note_callback
self.current_octave = 4
# Maps keyboard keys to semitone offsets from C
self.note_map = {
'a': 0, # C
'w': 1, # C#
's': 2, # D
'e': 3, # D#
'd': 4, # E
'f': 5, # F
't': 6, # F#
'g': 7, # G
'y': 8, # G#
'h': 9, # A
'u': 10, # A#
'j': 11, # B
'k': 12 # C (next octave)
}
def handle_key_press(self, char):
"""
Handle a keyboard character press.
Args:
char: Character from key press event
Returns:
dict with action info, or None if no action taken
"""
ch = char.lower()
if ch in self.note_map:
# Calculate MIDI note (C4 = 60)
semitone_offset = self.note_map[ch]
midi_note = 12 + 12 * self.current_octave + semitone_offset
# Send note to synth (fixed velocity for keyboard)
self.synth.note_on(midi_note, velocity=100)
if self.on_note_callback:
self.on_note_callback(midi_note, 100)
return {
'action': 'note',
'midi_note': midi_note,
'key': ch.upper()
}
elif ch == 'z':
# Octave down
if self.current_octave > 1:
self.current_octave -= 1
return {
'action': 'octave_change',
'octave': self.current_octave
}
elif ch == 'x':
# Octave up
if self.current_octave < 6:
self.current_octave += 1
return {
'action': 'octave_change',
'octave': self.current_octave
}
return None
def get_octave(self):
return self.current_octave
def get_help_text(self):
"""Get help text for keyboard controls."""
return (
"Keyboard Controls:\n"
" A W S E D F T G Y H U J K - Play notes (chromatic C to C)\n"
" Z - Octave down\n"
" X - Octave up"
)
class MIDIInput:
"""
Handles MIDI hardware input and converts to synth events.
"""
def __init__(self, synth, on_note_callback=None):
"""
Args:
synth: RealtimeSynth instance
on_note_callback: Optional callback for note events (for display updates)
"""
self.synth = synth
self.on_note_callback = on_note_callback
# MIDI state
self.midi_input = None
self.midi_thread = None
self.midi_running = False
self.current_port = None
def get_available_ports(self):
"""Get list of available MIDI input ports."""
ports = mido.get_input_names()
return ports if ports else []
def is_connected(self):
"""Check if MIDI input is currently connected."""
return self.midi_input is not None and self.midi_running
def get_current_port(self):
"""Get name of currently connected port."""
return self.current_port
def connect(self, port_name):
"""
Connect to a MIDI input port.
Args:
port_name: Name of MIDI port to connect to
Returns:
True if successful, False otherwise
"""
# Disconnect existing connection
self.disconnect()
try:
self.midi_input = mido.open_input(port_name)
self.midi_running = True
self.current_port = port_name
# Start listener thread
self.midi_thread = threading.Thread(
target=self._midi_listener,
daemon=True
)
self.midi_thread.start()
print(f"✓ MIDI connected: {port_name}")
return True
except Exception as e:
print(f"✗ MIDI connection failed: {e}")
self.current_port = None
return False
def disconnect(self):
"""Disconnect from current MIDI port."""
self.midi_running = False
if self.midi_input:
self.midi_input.close()
self.midi_input = None
self.current_port = None
def _handle_midi_message(self, msg):
"""Process incoming MIDI message."""
if msg.type == 'note_on' and msg.velocity > 0:
self.synth.note_on(msg.note, msg.velocity)
if self.on_note_callback:
self.on_note_callback(msg.note, msg.velocity)
elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0):
self.synth.note_off(msg.note)
def _midi_listener(self):
"""Background thread to listen for MIDI messages."""
print("MIDI listener started...")
try:
while self.midi_running:
msg = self.midi_input.receive()
if msg:
self._handle_midi_message(msg)
except Exception as e:
print(f"MIDI listener error: {e}")
finally:
print("MIDI listener stopped.")
def get_help_text(self):
"""Get help text for MIDI controls."""
available = self.get_available_ports()
if available:
return (
f"MIDI Hardware Controls:\n"
f" Connect to your MIDI keyboard and play\n"
f" Available ports: {', '.join(available)}"
)
else:
return "MIDI Hardware Controls:\n No MIDI devices detected"
class VirtualMIDIInput:
"""
Creates a virtual MIDI port for inter-application communication.
Other scripts can send MIDI to this port.
"""
def __init__(self, synth, port_name="KarplusStrong Virtual In", on_note_callback=None):
"""
Args:
synth: RealtimeSynth instance
port_name: Name for the virtual MIDI port
on_note_callback: Optional callback for note events (for display updates)
"""
self.synth = synth
self.on_note_callback = on_note_callback
self.port_name = port_name
# Virtual MIDI state
self.midi_input = None
self.midi_thread = None
self.midi_running = False
def is_connected(self):
"""Check if virtual port is open."""
return self.midi_input is not None and self.midi_running
def get_port_name(self):
"""Get the name of the virtual port."""
return self.port_name
def open_port(self):
"""
Open a virtual MIDI input port.
Returns:
True if successful, False otherwise
"""
# Close existing port
self.close_port()
try:
# Create virtual input port
self.midi_input = mido.open_input(self.port_name, virtual=True)
self.midi_running = True
# Start listener thread
self.midi_thread = threading.Thread(
target=self._midi_listener,
daemon=True
)
self.midi_thread.start()
print(f"✓ Virtual MIDI port opened: '{self.port_name}'")
print(f" Other scripts can connect to this port to send MIDI")
return True
except Exception as e:
print(f"✗ Failed to open virtual MIDI port: {e}")
return False
def close_port(self):
"""Close the virtual MIDI port."""
self.midi_running = False
if self.midi_input:
self.midi_input.close()
self.midi_input = None
def _handle_midi_message(self, msg):
"""Process incoming MIDI message."""
if msg.type == 'note_on' and msg.velocity > 0:
self.synth.note_on(msg.note, msg.velocity)
if self.on_note_callback:
self.on_note_callback(msg.note, msg.velocity)
elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0):
self.synth.note_off(msg.note)
def _midi_listener(self):
"""Background thread to listen for MIDI messages."""
print(f"Virtual MIDI listener started on '{self.port_name}'...")
try:
while self.midi_running:
msg = self.midi_input.receive()
if msg:
self._handle_midi_message(msg)
except Exception as e:
print(f"Virtual MIDI listener error: {e}")
finally:
print("Virtual MIDI listener stopped.")
def get_help_text(self):
"""Get help text for virtual MIDI."""
return (
f"Virtual MIDI Port:\n"
f" Port name: '{self.port_name}'\n"
f" Other Python scripts can send to this port\n"
f" Example: mido.open_output('{self.port_name}').send(msg)"
)