33
44from __future__ import annotations
55
6- from typing import Dict , List , Sequence , Union
6+ from typing import Dict , List , Optional , Sequence , Tuple , Union
77import hashlib
88
99# -----------------------------
1515
1616# SE-02 edit buffer dump blocks (DT1 address starts with 0x05)
1717EDITBUF_ADDR_PREFIX = (0x05 ,)
18+ EDITBUF_BLOCK_OFFSETS = ((0x00 , 0x00 ), (0x00 , 0x40 ), (0x01 , 0x00 ), (0x01 , 0x40 ))
1819
1920# Optional tracing (disabled by default in public release)
2021TRACE_ENABLED = False
@@ -160,14 +161,7 @@ def _retarget_dt1(blob: bytes, channel, target_bb: int):
160161 dev = _device_id_from_channel (channel )
161162
162163 for m in msgs :
163- if not (
164- len (m ) >= 14
165- and m [0 ] == 0xF0
166- and m [1 ] == ROLAND_ID
167- and m [6 ] == MODEL_ID
168- and m [7 ] == CMD_DT1
169- and m [- 1 ] == 0xF7
170- ):
164+ if not _is_se02_dt1 (m ):
171165 continue
172166
173167 changed = False
@@ -197,6 +191,7 @@ def _is_se02_dt1(msg: bytes) -> bool:
197191 and msg [6 ] == MODEL_ID
198192 and msg [7 ] == CMD_DT1
199193 and msg [- 1 ] == 0xF7
194+ and _roland_checksum_7bit (bytes (msg [8 :- 2 ])) == msg [- 2 ]
200195 )
201196
202197
@@ -215,6 +210,41 @@ def _is_editbuf_dt1(msg: bytes) -> bool:
215210 return bool (a ) and a [0 ] in EDITBUF_ADDR_PREFIX
216211
217212
213+ def _editbuf_block_info (msg : bytes ) -> Optional [Tuple [int , Tuple [int , int ]]]:
214+ if not _is_editbuf_dt1 (msg ):
215+ return None
216+ a = _addr4 (msg )
217+ if not a :
218+ return None
219+ return a [1 ], (a [2 ], a [3 ])
220+
221+
222+ def _ordered_valid_editbuf_blocks (blocks : Sequence [bytes ]) -> List [bytes ]:
223+ if len (blocks ) != len (EDITBUF_BLOCK_OFFSETS ):
224+ return []
225+
226+ bb = None
227+ by_offset : Dict [Tuple [int , int ], bytes ] = {}
228+ for block in blocks :
229+ info = _editbuf_block_info (block )
230+ if info is None :
231+ return []
232+
233+ block_bb , offset = info
234+ if bb is None :
235+ bb = block_bb
236+ elif block_bb != bb :
237+ return []
238+
239+ if offset not in EDITBUF_BLOCK_OFFSETS or offset in by_offset :
240+ return []
241+ by_offset [offset ] = block
242+
243+ if len (by_offset ) != len (EDITBUF_BLOCK_OFFSETS ):
244+ return []
245+ return [by_offset [offset ] for offset in EDITBUF_BLOCK_OFFSETS ]
246+
247+
218248# -----------------------------
219249# Device detect (KEEP STABLE)
220250# -----------------------------
@@ -296,15 +326,16 @@ def isPartOfEditBufferDump(message) -> bool:
296326
297327def isEditBufferDump (message ) -> bool :
298328 """
299- True if the blob contains a full edit buffer dump (4 DT1 messages).
300- We detect this by splitting the blob into SysEx messages and counting DT1 blocks.
329+ True if the blob contains a full edit buffer dump.
330+ A complete SE-02 edit buffer dump has the four expected DT1 block offsets
331+ for the same bank/slot identifier.
301332 """
302333 blob = _to_blob (message )
303334 if len (blob ) < 12 :
304335 return False
305336 msgs = _split_sysex (blob ) if blob .count (b"\xF0 " ) >= 2 else ([blob ] if blob else [])
306337 dt1 = [m for m in msgs if _is_editbuf_dt1 (m )]
307- ok = ( len (dt1 ) == 4 )
338+ ok = bool ( _ordered_valid_editbuf_blocks (dt1 ))
308339 if ok :
309340 _trace (f"isEditBufferDump(len={ len (blob )} msgs={ len (msgs )} ok={ ok } )" )
310341 return ok
@@ -426,7 +457,7 @@ def extractPatches(messages):
426457 File import hook:
427458 Orm gives a list of SysEx messages; we return a list of "patch blobs".
428459 SE-02 edit buffer dump = 4 DT1 messages.
429- We concatenate 4 DT1 blocks into one blob.
460+ We concatenate each valid group of the four expected DT1 blocks into one blob.
430461 """
431462 _trace (f"extractPatches(count={ len (messages ) if messages is not None else 'None' } )" )
432463 if not messages :
@@ -441,12 +472,14 @@ def extractPatches(messages):
441472 _trace (f"extractPatches: editbuf_dt1_blocks={ len (dt1_blocks )} " )
442473
443474 patches : List [bytes ] = []
444- for i in range (0 , len (dt1_blocks ) // 4 ):
445- blocks = dt1_blocks [i * 4 : (i + 1 ) * 4 ]
446- patches .append (b"" .join (blocks ))
447- remainder = len (dt1_blocks ) % 4
448- if remainder :
449- _trace (f"extractPatches: ignored incomplete trailing block group of { remainder } DT1 messages" )
475+ group_size = len (EDITBUF_BLOCK_OFFSETS )
476+ for start in range (0 , len (dt1_blocks ), group_size ):
477+ blocks = dt1_blocks [start : start + group_size ]
478+ ordered_blocks = _ordered_valid_editbuf_blocks (blocks )
479+ if ordered_blocks :
480+ patches .append (b"" .join (ordered_blocks ))
481+ else :
482+ _trace (f"extractPatches: ignored invalid DT1 block group at index { start // group_size } " )
450483
451484 _trace (f"extractPatches: returning patches={ len (patches )} " )
452485 return patches
@@ -667,11 +700,10 @@ def convertToProgramDump(channel, message, program_number):
667700 blob = _to_blob (message )
668701 try :
669702 target_bb = int (program_number )
670- except Exception :
671- target_bb = 0
703+ except ( TypeError , ValueError ) as exc :
704+ raise ValueError ( f"program_number must be an int in the range 0..127, got { program_number !r } " ) from exc
672705 if not 0 <= target_bb <= 127 :
673- _trace (f"convertToProgramDump: invalid program_number={ program_number } , defaulting to USER slot 0" )
674- target_bb = 0
706+ raise ValueError (f"program_number must be an int in the range 0..127, got { program_number !r} " )
675707 out_blob , msg_count , addr_fixed , dev_fixed , touched_dt1 = _retarget_dt1 (blob , channel , target_bb )
676708 _trace (
677709 f"convertToProgramDump: channel={ channel } program_number={ program_number } target_bb={ target_bb } "
0 commit comments