@@ -111,7 +111,7 @@ class MIDIMessage:
111111 _STATUS = None
112112 _STATUSMASK = None
113113 LENGTH = None
114- CHANNELMASK = None
114+ CHANNELMASK = 0x0f
115115 ENDSTATUS = None
116116
117117 # Commonly used exceptions to save memory
@@ -121,6 +121,24 @@ class MIDIMessage:
121121 # order is more specific masks first
122122 _statusandmask_to_class = []
123123
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+
124142 @classmethod
125143 def register_message_type (cls ):
126144 """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
161179
162180 return (msgendidxplusone , good_termination , bad_termination )
163181
164- # pylint: disable=too-many-arguments,too-many-locals
165182 @classmethod
166- def _match_message_status (cls , buf , channel_in , msgstartidx , msgendidxplusone , endidx ):
183+ def _match_message_status (cls , buf , msgstartidx , msgendidxplusone , endidx ):
167184 msgclass = None
168185 status = buf [msgstartidx ]
169186 known_msg = False
170187 complete_msg = False
171188 bad_termination = False
172- channel_match_orna = True
173- channel = None
174189
175190 # Rummage through our list looking for a status match
176191 for status_mask , msgclass in MIDIMessage ._statusandmask_to_class :
@@ -183,10 +198,6 @@ def _match_message_status(cls, buf, channel_in, msgstartidx, msgendidxplusone, e
183198 if not complete_msg :
184199 break
185200
186- if msgclass .CHANNELMASK is not None :
187- channel = status & msgclass .CHANNELMASK
188- channel_match_orna = channel_filter (channel , channel_in )
189-
190201 if msgclass .LENGTH < 0 : # indicator of variable length message
191202 (msgendidxplusone ,
192203 terminated_msg ,
@@ -203,26 +214,26 @@ def _match_message_status(cls, buf, channel_in, msgstartidx, msgendidxplusone, e
203214
204215 return (msgclass , status ,
205216 known_msg , complete_msg , bad_termination ,
206- channel_match_orna , channel , msgendidxplusone )
217+ msgendidxplusone )
207218
219+ # pylint: disable=too-many-locals,too-many-branches
208220 @classmethod
209221 def from_message_bytes (cls , midibytes , channel_in ):
210222 """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 .
212224
213- Returns (messageobject, endplusone, skipped, channel )
225+ Returns (messageobject, endplusone, skipped)
214226 or for no messages, partial messages or messages for other channels
215- (None, endplusone, skipped, None ).
227+ (None, endplusone, skipped).
216228 """
217- msg = None
218229 endidx = len (midibytes ) - 1
219230 skipped = 0
220231 preamble = True
221- channel = None
222232
223233 msgstartidx = 0
224234 msgendidxplusone = 0
225235 while True :
236+ msg = None
226237 # Look for a status byte
227238 # Second rule of the MIDI club is status bytes have MSB set
228239 while msgstartidx <= endidx and not midibytes [msgstartidx ] & 0x80 :
@@ -233,27 +244,27 @@ def from_message_bytes(cls, midibytes, channel_in):
233244
234245 # Either no message or a partial one
235246 if msgstartidx > endidx :
236- return (None , endidx + 1 , skipped , None )
247+ return (None , endidx + 1 , skipped )
237248
238249 # Try and match the status byte found in midibytes
239250 (msgclass ,
240251 status ,
241252 known_message ,
242253 complete_message ,
243254 bad_termination ,
244- channel_match_orna ,
245- channel ,
246255 msgendidxplusone ) = cls ._match_message_status (midibytes ,
247- channel_in ,
248256 msgstartidx ,
249257 msgendidxplusone ,
250258 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 :
253261 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+
255266 except (ValueError , TypeError ) as ex :
256- msg = MIDIBadEvent (midibytes [msgstartidx + 1 :msgendidxplusone ], ex )
267+ msg = MIDIBadEvent (midibytes [msgstartidx :msgendidxplusone ], ex )
257268
258269 # break out of while loop for a complete message on good channel
259270 # or we have one we do not know about
@@ -274,24 +285,23 @@ def from_message_bytes(cls, midibytes, channel_in):
274285 msgendidxplusone = msgstartidx + 1
275286 break
276287
277- return (msg , msgendidxplusone , skipped , channel )
288+ return (msg , msgendidxplusone , skipped )
278289
279- # channel value present to keep interface uniform but unused
280290 # 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 ])
286296
287297 # databytes value present to keep interface uniform but unused
288298 # A default method for constructing message objects with no data.
289299 # Returns the new object.
290300 # pylint: disable=unused-argument
291301 @classmethod
292- def from_bytes (cls , databytes ):
302+ def from_bytes (cls , msg_bytes ):
293303 """Creates an object from the byte stream of the wire protocol
294- (not including the first status byte) ."""
304+ representation of the MIDI message ."""
295305 return cls ()
296306
297307
@@ -308,18 +318,22 @@ class MIDIUnknownEvent(MIDIMessage):
308318
309319 def __init__ (self , status ):
310320 self .status = status
321+ super ().__init__ ()
311322
312323
313324class MIDIBadEvent (MIDIMessage ):
314325 """A bad MIDI message, one that could not be parsed/constructed.
315326
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.
317329 :param Exception exception: The exception used to store the repr() text representation.
318330
319331 This could be due to status bytes appearing where data bytes are expected.
332+ The channel property will not be set.
320333 """
321334 LENGTH = - 1
322335
323- def __init__ (self , data , exception ):
324- self .data = bytearray ( data )
336+ def __init__ (self , msg_bytes , exception ):
337+ self .data = bytes ( msg_bytes )
325338 self .exception_text = repr (exception )
339+ super ().__init__ ()
0 commit comments