2323# TODO: decode MMC (page 58 + dedicated spec)
2424
2525import functools
26+ from collections import deque
2627
2728import midi_const
2829import mido
2930
30- """
31- System exclusive ID decoder.
31+ from midiexplorer . gui import Logger
32+
3233
33- Denoted "ID number" in the specification.
34- Frequently referred to as "Manufacturer ID".
35- """
3634class DecodedSysExId :
35+ """
36+ System exclusive ID decoder.
37+
38+ Denoted "ID number" in the specification.
39+ Frequently referred to as "Manufacturer ID".
40+ """
41+
3742 def __init__ (self , value : int | tuple [int ]):
3843 length : int
3944 try :
@@ -77,7 +82,7 @@ def region(self) -> str:
7782 index = self ._raw
7883 else :
7984 index = self ._raw [1 ]
80- region = midi_const .SYSTEM_EXCLUSIVE_ID_REGIONS .get (index , "N.A. " )
85+ region = midi_const .SYSTEM_EXCLUSIVE_ID_REGIONS .get (index , "N/A " )
8186 return region
8287
8388 @functools .cached_property
@@ -98,21 +103,24 @@ def name(self) -> str:
98103
99104class DecodedSysExPayload :
100105 _id = int
101- _raw : int | tuple [ int ]
106+ _raw : deque
102107
103108 def __init__ (self , identifier : DecodedSysExId , contents : int | tuple [int ]):
104109 self ._id = identifier
105- self ._raw = contents
110+ if isinstance (contents , int ):
111+ self ._raw = deque ([contents ])
112+ else :
113+ self ._raw = deque (contents )
106114
107115 @property
108116 def value (self ):
109- return self ._raw
117+ return tuple ( self ._raw )
110118
111119 @staticmethod
112120 def get_decoder (identifier ):
113- if identifier .value == 0x7E :
121+ if midi_const . SYSTEM_EXCLUSIVE_ID . get ( identifier .value ) == "Non-Real Time" :
114122 return DecodedUniversalNonRealTimeSysExPayload
115- if identifier .value == 0x7F :
123+ if midi_const . SYSTEM_EXCLUSIVE_ID . get ( identifier .value ) == "Real Time" :
116124 return DecodedUniversalRealTimeSysExPayload
117125 return DecodedSysExPayload
118126
@@ -124,59 +132,66 @@ def __init__(self, identifier: DecodedSysExId, contents: int | tuple[int]):
124132
125133class DecodedUniversalNonRealTimeSysExPayload (DecodedUniversalSysExPayload ):
126134 def __init__ (self , identifier : DecodedSysExId , contents : int | tuple [int ]):
127- if identifier .value != 0x7E :
135+ if midi_const . SYSTEM_EXCLUSIVE_ID . get ( identifier .value ) != "Non-Real Time" :
128136 raise ValueError
129137 super ().__init__ (identifier , contents )
130- next_byte : int = 0
131- self .sub_id1_value = self ._raw [next_byte ]
132- self .sub_id1_name = midi_const . \
133- DEFINED_UNIVERSAL_SYSTEM_EXCLUSIVE_MESSAGES_NON_REAL_TIME_SUB_ID_1 .get (
134- self .sub_id1_value , "Undefined"
138+ self .sub_id1_value = self ._raw .popleft ()
139+ self .sub_id1_name = \
140+ midi_const .DEFINED_UNIVERSAL_SYSTEM_EXCLUSIVE_MESSAGES_NON_REAL_TIME_SUB_ID_1 .get (
141+ self .sub_id1_value , "Undefined"
135142 )
136143 if self .sub_id1_value in midi_const .NON_REAL_TIME_SUB_ID_2_FROM_1 :
137- next_byte += 1
138- self .sub_id2_value = self ._raw [next_byte ]
144+ self .sub_id2_value = self ._raw .popleft ()
139145 self .sub_id2_name = midi_const .NON_REAL_TIME_SUB_ID_2_FROM_1 .get (
140146 self .sub_id1_value
141- ).get (
147+ ).get (
142148 self .sub_id2_value , "Undefined"
143149 )
144150
145151
146152class DecodedUniversalRealTimeSysExPayload (DecodedUniversalSysExPayload ):
147153 def __init__ (self , identifier : DecodedSysExId , contents : int | tuple [int ]):
148- if identifier .value != 0x7F :
154+ if midi_const . SYSTEM_EXCLUSIVE_ID . get ( identifier .value ) != "Real Time" :
149155 raise ValueError
150156 super ().__init__ (identifier , contents )
151- next_byte : int = 0
152- self .sub_id1_value = self ._raw [next_byte ]
153- self .sub_id1_name = midi_const . \
154- DEFINED_UNIVERSAL_SYSTEM_EXCLUSIVE_MESSAGES_REAL_TIME_SUB_ID_1 .get (
155- self .sub_id1_value , "Undefined"
157+ self .sub_id1_value = self ._raw .popleft ()
158+ self .sub_id1_name = \
159+ midi_const .DEFINED_UNIVERSAL_SYSTEM_EXCLUSIVE_MESSAGES_REAL_TIME_SUB_ID_1 .get (
160+ self .sub_id1_value , "Undefined"
156161 )
157162 if self .sub_id1_value in midi_const .REAL_TIME_SUB_ID_2_FROM_1 :
158- next_byte += 1
159- self .sub_id2_value = self ._raw [next_byte ]
163+ self .sub_id2_value = self ._raw .popleft ()
160164 self .sub_id2_name = midi_const .REAL_TIME_SUB_ID_2_FROM_1 .get (
161165 self .sub_id1_value
162- ).get (
166+ ).get (
163167 self .sub_id2_value , "Undefined"
164168 )
165169
166170
167171class DecodedSysEx :
168172 def __init__ (self , message : tuple ):
169173 if len (message ) < 3 :
174+ # We need at least 3 bytes:
175+ # - 1 ID number byte
176+ # - 1 device ID byte
177+ # - 1 payload byte
170178 raise ValueError ("Message too short (less than 3 bytes) to be a proper system exclusive message." )
179+
171180 # Scrub EOX if present
172181 if message [- 1 ] == mido .messages .specs .SYSEX_END :
182+ Logger ().log_warning ("Scrubbing EOX from SysEx payload before decoding!" )
173183 self ._raw = message [:- 1 ]
174184 else :
175185 self ._raw = message
186+
176187 # Determine ID length
177188 if self ._raw [0 ] == 0x00 :
178189 # 3-byte ID
179- if len (message ) < 5 :
190+ if len (self ._raw ) < 5 :
191+ # We need at least 5 bytes:
192+ # - 3 ID number bytes
193+ # - 1 device ID byte
194+ # - 1 payload byte
180195 raise ValueError (
181196 "Message too short (less than 5 bytes) to be a proper system exclusive message with a 3-byte ID."
182197 )
@@ -196,6 +211,8 @@ def _payload(self) -> int | tuple[int]:
196211 return self ._raw [self ._device_id_byte + 1 :]
197212
198213 @functools .cached_property
199- def payload (self ) -> DecodedSysExPayload | DecodedUniversalRealTimeSysExPayload | DecodedUniversalNonRealTimeSysExPayload :
214+ def payload (
215+ self
216+ ) -> DecodedSysExPayload | DecodedUniversalRealTimeSysExPayload | DecodedUniversalNonRealTimeSysExPayload :
200217 decoder = DecodedSysExPayload .get_decoder (self .identifier )
201218 return decoder (self .identifier , self ._payload )
0 commit comments