@@ -111,7 +111,7 @@ class MIDIMessage:
111
111
_STATUS = None
112
112
_STATUSMASK = None
113
113
LENGTH = None
114
- CHANNELMASK = None
114
+ CHANNELMASK = 0x0f
115
115
ENDSTATUS = None
116
116
117
117
# Commonly used exceptions to save memory
@@ -121,6 +121,24 @@ class MIDIMessage:
121
121
# order is more specific masks first
122
122
_statusandmask_to_class = []
123
123
124
+ def __init__ (self , * , channel = None ):
125
+ ### TODO - can i kwargs this?????
126
+ self ._channel = channel # dealing with pylint inadequacy
127
+ self .channel = channel
128
+
129
+ @property
130
+ def channel (self ):
131
+ """The channel number of the MIDI message where appropriate.
132
+ This is *updated* by MIDI.send() method.
133
+ """
134
+ return self ._channel
135
+
136
+ @channel .setter
137
+ def channel (self , channel ):
138
+ if channel is not None and not 0 <= channel <= 15 :
139
+ raise "channel must be 0-15 or None"
140
+ self ._channel = channel
141
+
124
142
@classmethod
125
143
def register_message_type (cls ):
126
144
"""Register a new message by its status value and mask.
@@ -161,16 +179,13 @@ def _search_eom_status(cls, buf, eom_status, msgstartidx, msgendidxplusone, endi
161
179
162
180
return (msgendidxplusone , good_termination , bad_termination )
163
181
164
- # pylint: disable=too-many-arguments,too-many-locals
165
182
@classmethod
166
- def _match_message_status (cls , buf , channel_in , msgstartidx , msgendidxplusone , endidx ):
183
+ def _match_message_status (cls , buf , msgstartidx , msgendidxplusone , endidx ):
167
184
msgclass = None
168
185
status = buf [msgstartidx ]
169
186
known_msg = False
170
187
complete_msg = False
171
188
bad_termination = False
172
- channel_match_orna = True
173
- channel = None
174
189
175
190
# Rummage through our list looking for a status match
176
191
for status_mask , msgclass in MIDIMessage ._statusandmask_to_class :
@@ -183,10 +198,6 @@ def _match_message_status(cls, buf, channel_in, msgstartidx, msgendidxplusone, e
183
198
if not complete_msg :
184
199
break
185
200
186
- if msgclass .CHANNELMASK is not None :
187
- channel = status & msgclass .CHANNELMASK
188
- channel_match_orna = channel_filter (channel , channel_in )
189
-
190
201
if msgclass .LENGTH < 0 : # indicator of variable length message
191
202
(msgendidxplusone ,
192
203
terminated_msg ,
@@ -203,26 +214,26 @@ def _match_message_status(cls, buf, channel_in, msgstartidx, msgendidxplusone, e
203
214
204
215
return (msgclass , status ,
205
216
known_msg , complete_msg , bad_termination ,
206
- channel_match_orna , channel , msgendidxplusone )
217
+ msgendidxplusone )
207
218
219
+ # pylint: disable=too-many-locals,too-many-branches
208
220
@classmethod
209
221
def from_message_bytes (cls , midibytes , channel_in ):
210
222
"""Create an appropriate object of the correct class for the
211
- first message found in some MIDI bytes.
223
+ first message found in some MIDI bytes filtered by channel_in .
212
224
213
- Returns (messageobject, endplusone, skipped, channel )
225
+ Returns (messageobject, endplusone, skipped)
214
226
or for no messages, partial messages or messages for other channels
215
- (None, endplusone, skipped, None ).
227
+ (None, endplusone, skipped).
216
228
"""
217
- msg = None
218
229
endidx = len (midibytes ) - 1
219
230
skipped = 0
220
231
preamble = True
221
- channel = None
222
232
223
233
msgstartidx = 0
224
234
msgendidxplusone = 0
225
235
while True :
236
+ msg = None
226
237
# Look for a status byte
227
238
# Second rule of the MIDI club is status bytes have MSB set
228
239
while msgstartidx <= endidx and not midibytes [msgstartidx ] & 0x80 :
@@ -233,27 +244,27 @@ def from_message_bytes(cls, midibytes, channel_in):
233
244
234
245
# Either no message or a partial one
235
246
if msgstartidx > endidx :
236
- return (None , endidx + 1 , skipped , None )
247
+ return (None , endidx + 1 , skipped )
237
248
238
249
# Try and match the status byte found in midibytes
239
250
(msgclass ,
240
251
status ,
241
252
known_message ,
242
253
complete_message ,
243
254
bad_termination ,
244
- channel_match_orna ,
245
- channel ,
246
255
msgendidxplusone ) = cls ._match_message_status (midibytes ,
247
- channel_in ,
248
256
msgstartidx ,
249
257
msgendidxplusone ,
250
258
endidx )
251
-
252
- if complete_message and not bad_termination and channel_match_orna :
259
+ channel_match_orna = True
260
+ if complete_message and not bad_termination :
253
261
try :
254
- msg = msgclass .from_bytes (midibytes [msgstartidx + 1 :msgendidxplusone ])
262
+ msg = msgclass .from_bytes (midibytes [msgstartidx :msgendidxplusone ])
263
+ if msg .channel is not None :
264
+ channel_match_orna = channel_filter (msg .channel , channel_in )
265
+
255
266
except (ValueError , TypeError ) as ex :
256
- msg = MIDIBadEvent (midibytes [msgstartidx + 1 :msgendidxplusone ], ex )
267
+ msg = MIDIBadEvent (midibytes [msgstartidx :msgendidxplusone ], ex )
257
268
258
269
# break out of while loop for a complete message on good channel
259
270
# or we have one we do not know about
@@ -274,24 +285,23 @@ def from_message_bytes(cls, midibytes, channel_in):
274
285
msgendidxplusone = msgstartidx + 1
275
286
break
276
287
277
- return (msg , msgendidxplusone , skipped , channel )
288
+ return (msg , msgendidxplusone , skipped )
278
289
279
- # channel value present to keep interface uniform but unused
280
290
# A default method for constructing wire messages with no data.
281
- # Returns a (mutable) bytearray with just the status code in.
282
- # pylint: disable=unused-argument
283
- def as_bytes ( self , * , channel = None ):
284
- """Return the ``bytearray`` wire protocol representation of the object ."""
285
- return bytearray ([self ._STATUS ])
291
+ # Returns an (immutable) bytes with just the status code in.
292
+ def __bytes__ ( self ):
293
+ """Return the ``bytes`` wire protocol representation of the object
294
+ with channel number applied where appropriate ."""
295
+ return bytes ([self ._STATUS ])
286
296
287
297
# databytes value present to keep interface uniform but unused
288
298
# A default method for constructing message objects with no data.
289
299
# Returns the new object.
290
300
# pylint: disable=unused-argument
291
301
@classmethod
292
- def from_bytes (cls , databytes ):
302
+ def from_bytes (cls , msg_bytes ):
293
303
"""Creates an object from the byte stream of the wire protocol
294
- (not including the first status byte) ."""
304
+ representation of the MIDI message ."""
295
305
return cls ()
296
306
297
307
@@ -308,18 +318,22 @@ class MIDIUnknownEvent(MIDIMessage):
308
318
309
319
def __init__ (self , status ):
310
320
self .status = status
321
+ super ().__init__ ()
311
322
312
323
313
324
class MIDIBadEvent (MIDIMessage ):
314
325
"""A bad MIDI message, one that could not be parsed/constructed.
315
326
316
- :param list data: The MIDI status number.
327
+ :param list data: The MIDI status including any embedded channel number
328
+ and associated subsequent data bytes.
317
329
:param Exception exception: The exception used to store the repr() text representation.
318
330
319
331
This could be due to status bytes appearing where data bytes are expected.
332
+ The channel property will not be set.
320
333
"""
321
334
LENGTH = - 1
322
335
323
- def __init__ (self , data , exception ):
324
- self .data = bytearray ( data )
336
+ def __init__ (self , msg_bytes , exception ):
337
+ self .data = bytes ( msg_bytes )
325
338
self .exception_text = repr (exception )
339
+ super ().__init__ ()
0 commit comments