25
25
__version__ = "0.0.0+auto.0"
26
26
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MIDI.git"
27
27
28
+ try :
29
+ from typing import Union , Tuple , Any , List , Optional
30
+ except ImportError :
31
+ pass
32
+
28
33
# From C3 - A and B are above G
29
34
# Semitones A B C D E F G
30
35
NOTE_OFFSET = [21 , 23 , 12 , 14 , 16 , 17 , 19 ]
31
36
32
37
33
- def channel_filter (channel , channel_spec ):
38
+ def channel_filter (
39
+ channel : int , channel_spec : Optional [Union [int , Tuple [int , ...]]]
40
+ ) -> bool :
34
41
"""
35
42
Utility function to return True iff the given channel matches channel_spec.
36
43
"""
@@ -41,13 +48,12 @@ def channel_filter(channel, channel_spec):
41
48
raise ValueError ("Incorrect type for channel_spec" + str (type (channel_spec )))
42
49
43
50
44
- def note_parser (note ) :
51
+ def note_parser (note : Union [ int , str ]) -> int :
45
52
"""If note is a string then it will be parsed and converted to a MIDI note (key) number, e.g.
46
53
"C4" will return 60, "C#4" will return 61. If note is not a string it will simply be returned.
47
54
48
55
:param note: Either 0-127 int or a str representing the note, e.g. "C#4"
49
56
"""
50
- midi_note = note
51
57
if isinstance (note , str ):
52
58
if len (note ) < 2 :
53
59
raise ValueError ("Bad note format" )
@@ -61,7 +67,8 @@ def note_parser(note):
61
67
sharpen = - 1
62
68
# int may throw exception here
63
69
midi_note = int (note [1 + abs (sharpen ) :]) * 12 + NOTE_OFFSET [noteidx ] + sharpen
64
-
70
+ elif isinstance (note , int ):
71
+ midi_note = note
65
72
return midi_note
66
73
67
74
@@ -82,40 +89,43 @@ class MIDIMessage:
82
89
This is an *abstract* class.
83
90
"""
84
91
85
- _STATUS = None
92
+ _STATUS : Optional [ int ] = None
86
93
_STATUSMASK = None
87
- LENGTH = None
94
+ LENGTH : Optional [ int ] = None
88
95
CHANNELMASK = 0x0F
89
96
ENDSTATUS = None
90
97
91
98
# Commonly used exceptions to save memory
92
99
@staticmethod
93
- def _raise_valueerror_oor ():
100
+ def _raise_valueerror_oor () -> None :
94
101
raise ValueError ("Out of range" )
95
102
96
103
# Each element is ((status, mask), class)
97
104
# order is more specific masks first
98
- _statusandmask_to_class = []
105
+ # Add better type hints for status, mask, class referenced above
106
+ _statusandmask_to_class : List [
107
+ Tuple [Tuple [Optional [bytes ], Optional [int ]], "MIDIMessage" ]
108
+ ] = []
99
109
100
- def __init__ (self , * , channel = None ):
110
+ def __init__ (self , * , channel : Optional [ int ] = None ) -> None :
101
111
self ._channel = channel # dealing with pylint inadequacy
102
112
self .channel = channel
103
113
104
114
@property
105
- def channel (self ):
115
+ def channel (self ) -> Optional [ int ] :
106
116
"""The channel number of the MIDI message where appropriate.
107
117
This is *updated* by MIDI.send() method.
108
118
"""
109
119
return self ._channel
110
120
111
121
@channel .setter
112
- def channel (self , channel ) :
122
+ def channel (self , channel : int ) -> None :
113
123
if channel is not None and not 0 <= channel <= 15 :
114
124
raise ValueError ("Channel must be 0-15 or None" )
115
125
self ._channel = channel
116
126
117
127
@classmethod
118
- def register_message_type (cls ):
128
+ def register_message_type (cls ) -> None :
119
129
"""Register a new message by its status value and mask.
120
130
This is called automagically at ``import`` time for each message.
121
131
"""
@@ -132,7 +142,14 @@ def register_message_type(cls):
132
142
133
143
# pylint: disable=too-many-arguments
134
144
@classmethod
135
- def _search_eom_status (cls , buf , eom_status , msgstartidx , msgendidxplusone , endidx ):
145
+ def _search_eom_status (
146
+ cls ,
147
+ buf : bytearray ,
148
+ eom_status : Optional [int ],
149
+ msgstartidx : int ,
150
+ msgendidxplusone : int ,
151
+ endidx : int ,
152
+ ) -> Tuple [int , bool , bool ]:
136
153
good_termination = False
137
154
bad_termination = False
138
155
@@ -155,7 +172,9 @@ def _search_eom_status(cls, buf, eom_status, msgstartidx, msgendidxplusone, endi
155
172
return (msgendidxplusone , good_termination , bad_termination )
156
173
157
174
@classmethod
158
- def _match_message_status (cls , buf , msgstartidx , msgendidxplusone , endidx ):
175
+ def _match_message_status (
176
+ cls , buf : bytearray , msgstartidx : int , msgendidxplusone : int , endidx : int
177
+ ) -> Tuple [Optional [Any ], int , bool , bool , bool , int ]:
159
178
msgclass = None
160
179
status = buf [msgstartidx ]
161
180
known_msg = False
@@ -198,7 +217,9 @@ def _match_message_status(cls, buf, msgstartidx, msgendidxplusone, endidx):
198
217
199
218
# pylint: disable=too-many-locals,too-many-branches
200
219
@classmethod
201
- def from_message_bytes (cls , midibytes , channel_in ):
220
+ def from_message_bytes (
221
+ cls , midibytes : bytearray , channel_in : Optional [Union [int , Tuple [int , ...]]]
222
+ ) -> Tuple [Optional ["MIDIMessage" ], int , int ]:
202
223
"""Create an appropriate object of the correct class for the
203
224
first message found in some MIDI bytes filtered by channel_in.
204
225
@@ -270,7 +291,7 @@ def from_message_bytes(cls, midibytes, channel_in):
270
291
271
292
# A default method for constructing wire messages with no data.
272
293
# Returns an (immutable) bytes with just the status code in.
273
- def __bytes__ (self ):
294
+ def __bytes__ (self ) -> bytes :
274
295
"""Return the ``bytes`` wire protocol representation of the object
275
296
with channel number applied where appropriate."""
276
297
return bytes ([self ._STATUS ])
@@ -280,12 +301,12 @@ def __bytes__(self):
280
301
# Returns the new object.
281
302
# pylint: disable=unused-argument
282
303
@classmethod
283
- def from_bytes (cls , msg_bytes ) :
304
+ def from_bytes (cls , msg_bytes : bytes ) -> "MIDIMessage" :
284
305
"""Creates an object from the byte stream of the wire protocol
285
306
representation of the MIDI message."""
286
307
return cls ()
287
308
288
- def __str__ (self ):
309
+ def __str__ (self ) -> str :
289
310
"""Print an instance"""
290
311
cls = self .__class__
291
312
if slots := getattr (cls , "_message_slots" , None ):
@@ -313,7 +334,7 @@ class MIDIUnknownEvent(MIDIMessage):
313
334
_message_slots = ["status" ]
314
335
LENGTH = - 1
315
336
316
- def __init__ (self , status ):
337
+ def __init__ (self , status : int ):
317
338
self .status = status
318
339
super ().__init__ ()
319
340
@@ -333,7 +354,7 @@ class MIDIBadEvent(MIDIMessage):
333
354
334
355
_message_slots = ["msg_bytes" , "exception" ]
335
356
336
- def __init__ (self , msg_bytes , exception ):
357
+ def __init__ (self , msg_bytes : bytearray , exception : Exception ):
337
358
self .data = bytes (msg_bytes )
338
359
self .exception_text = repr (exception )
339
360
super ().__init__ ()
0 commit comments