1- import mido
2-
31import os
2+ import mido
43import time
54import queue
65import logging
7- from typing import Any
6+ from typing import Any , Callable
87from ...exceptions import DeviceNotFoundException
98from ...constants import MIDI_CLOCK_TICKS_PER_BEAT
9+ from ..midinote import MidiNote
1010
1111logger = logging .getLogger (__name__ )
1212
@@ -31,22 +31,35 @@ def __init__(self,
3131 if device_name is None :
3232 device_name = os .getenv ("ISOBAR_DEFAULT_MIDI_IN" )
3333 try :
34- self .midi = mido .open_input (device_name , callback = self ._callback , virtual = virtual )
34+ self .midi = mido .open_input (name = device_name ,
35+ callback = self ._mido_callback ,
36+ virtual = virtual )
3537 except (RuntimeError , SystemError , OSError ):
3638 raise DeviceNotFoundException ("Could not find MIDI device" )
3739
3840 self .clock_target = clock_target
3941 self .queue = queue .Queue ()
40- self .callback = None
4142 self .estimated_tempo = None
4243 self .last_clock_time = None
44+
4345 logger .info ("Opened MIDI input: %s" % self .midi .name )
4446
47+ self .notes_down : list [MidiNote ] = []
48+ self .notes_down_dict : dict [int , MidiNote ] = dict ((n , None ) for n in range (128 ))
49+
50+ self .callback : Callable = None
51+ self .on_note_on_handler : Callable = None
52+ self .on_note_off_handler : Callable = None
53+ self .on_control_change_handler : Callable = None
54+ self .on_aftertouch_handler : Callable = None
55+ self .on_polytouch_handler : Callable = None
56+ self .on_pitchbend_handler : Callable = None
57+
4558 @property
4659 def device_name (self ):
4760 return self .midi .name
4861
49- def _callback (self , message ):
62+ def _mido_callback (self , message ):
5063 """
5164 Callback used by mido.
5265 Args:
@@ -88,7 +101,66 @@ def _callback(self, message):
88101 else :
89102 logger .warning ("MIDI song position message received, but MIDI input cannot seek to arbitrary position" )
90103
91- elif message .type in ['note_on' , 'note_off' , 'control_change' , 'pitchwheel' ]:
104+ elif message .type in ['note_on' , 'note_off' , 'control_change' , 'pitchwheel' , 'aftertouch' , 'polytouch' ]:
105+ if message .type == 'note_off' or (message .type == 'note_on' and message .velocity == 0 ):
106+ #------------------------------------------------------------------------
107+ # Note off event
108+ #------------------------------------------------------------------------
109+ if self .notes_down_dict [message .note ] is not None :
110+ note = self .notes_down_dict [message .note ]
111+ self .notes_down_dict [message .note ] = None
112+ self .notes_down .remove (note )
113+ if self .on_note_off_handler :
114+ self .on_note_off_handler (note )
115+
116+ elif message .type == 'note_on' :
117+ #------------------------------------------------------------------------
118+ # Note on event
119+ #------------------------------------------------------------------------
120+ if self .notes_down_dict [message .note ] is None :
121+ note = MidiNote (pitch = message .note ,
122+ velocity = message .velocity ,
123+ channel = message .channel )
124+
125+ self .notes_down .append (note )
126+ self .notes_down_dict [message .note ] = note
127+
128+ if self .on_note_on_handler :
129+ self .on_note_on_handler (note )
130+
131+ elif message .type == 'control_change' :
132+ #------------------------------------------------------------------------
133+ # Control change event
134+ #------------------------------------------------------------------------
135+ # TODO: This passes a mido message. Should be an isobar structure.
136+ if self .on_control_change_handler :
137+ self .on_control_change_handler (message )
138+
139+ elif message .type == 'pitchwheel' :
140+ #------------------------------------------------------------------------
141+ # Pitch bend event
142+ #------------------------------------------------------------------------
143+ if self .on_pitchbend_handler :
144+ self .on_pitchbend_handler (message )
145+
146+ elif message .type == 'aftertouch' :
147+ #------------------------------------------------------------------------
148+ # Aftertouch event
149+ #------------------------------------------------------------------------
150+ for note in self .notes_down :
151+ note .aftertouch = message .value
152+ if self .on_aftertouch_handler :
153+ self .on_aftertouch_handler (message )
154+
155+ elif message .type == 'polytouch' :
156+ #------------------------------------------------------------------------
157+ # Polyphonic aftertouch event
158+ #------------------------------------------------------------------------
159+ note = self .notes_down_dict [message .note ]
160+ note .aftertouch = message .value
161+ if self .on_polytouch_handler :
162+ self .on_polytouch_handler (message )
163+
92164 if self .callback :
93165 self .callback (message )
94166 else :
@@ -147,3 +219,48 @@ def poll(self):
147219
148220 def close (self ):
149221 del self .midi
222+
223+ def set_note_on_handler (self , handler : Callable ):
224+ """
225+ Set the function to call when a note on event is received.
226+ The function should take a single argument, which is the MidiNote object.
227+
228+ Args:
229+ handler (Callable): The function to call.
230+ """
231+ self .on_note_on_handler = handler
232+
233+ def set_note_off_handler (self , handler : Callable ):
234+ """
235+ Set the function to call when a note off event is received.
236+
237+ Args:
238+ handler (Callable): The function to call.
239+ """
240+ self .on_note_off_handler = handler
241+
242+ def set_control_change_handler (self , handler : Callable ):
243+ """
244+ Set the function to call when a control change event is received.
245+
246+ Args:
247+ handler (Callable): The function to call.
248+ """
249+ self .on_control_change_handler = handler
250+
251+ def set_polytouch_handler (self , handler : Callable ):
252+ """
253+ Set the function to call when a polyphonic aftertouch event is received.
254+
255+ Args:
256+ handler (Callable): The function to call.
257+ """
258+ self .on_polytouch_handler = handler
259+
260+ def set_aftertouch_handler (self , handler : Callable ):
261+ """
262+ Set the function to call when an aftertouch event is received.
263+ Args:
264+ handler (Callable): The function to call.
265+ """
266+ self .on_aftertouch_handler = handler
0 commit comments