"""
JD-Xi SysEx Offsets
==================
This module defines the offsets for various JD-Xi SysEx messages, including control change,
program change, pitch bend, and identity messages. Each offset is represented as an `IntEnum`
to provide a clear and structured way to access the byte positions within the SysEx messages.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import IntEnum
from typing import Type, Union
from picomidi import SysExByte
from picomidi.core.parameter.address import ParameterAddress
from jdxi_editor.midi.data.address.address import (
CommandID,
ModelID,
RolandID,
RolandSysExAddress,
)
@dataclass(frozen=True)
[docs]
class FieldSpec:
[docs]
offset: Union[int, type] # int or OffsetEnum
[docs]
class JDXIControlChangeOffset(IntEnum):
"""
JDXIControlChangeOffset
Represents the offsets for JD-Xi Control Change messages.
Byte | Description
--------------------------------------------------------
Status | 0xB0 to 0xBF — Control Change on MIDI channels 1–16
Data 1 | Control Number (0–127)
Data 2 | Control Value (0–127)
"""
[docs]
STATUS_BYTE = 0 # Midi channel 1-16 is the low 4 bits
[docs]
CONTROL = 1 # Control Number (0-127)
[docs]
VALUE = 2 # Control Value (0-127)
[docs]
END = -1 # End of message, no more data bytes expected
[docs]
class JDXIProgramChangeOffset(IntEnum):
"""
JDXIProgramChangeOffset
Represents the offsets for JD-Xi Program Change messages.
Byte | Description
--------------------------------------------------------
Status | 0xC0 to 0xCF — Program Change on MIDI channels 1–16
Data 1 | Program Number (0–127)
"""
[docs]
STATUS_BYTE = 0 # Midi channel is the low 4 bits
[docs]
PROGRAM_NUMBER = 1 # Program Number (0-127)
[docs]
END = -1 # End of message, no more data bytes expected
[docs]
class JDXIPitchBendOffset(IntEnum):
"""
JDXIPitchBendOffset
Represents the offsets for JD-Xi Pitch Bend messages.
Byte | Description
--------------------------------------------------------
Status | 0xE0 to 0xEF — Pitch Bend on MIDI channels 1–16
Data 1 | Pitch Bend Value (14-bit, split into two bytes)
"""
[docs]
STATUS_BYTE = 0 # Midi channel is the low 4 bits
[docs]
PITCH_BEND_VALUE = 1 # Pitch Bend Value (14-bit, split into two bytes)
[docs]
END = -1 # End of message, no more data bytes expected
[docs]
class JDXiSysExModelIDOffset(IntEnum):
"""Model ID Offsets"""
[docs]
class JDXiSysExToneNameOffset(IntEnum):
"""Tone Name offsets"""
[docs]
class JDXiSysExAddressOffset(IntEnum):
"""Sysex Offsets"""
[docs]
class JDXiSysExMessageLayout:
"""
JDXiSysExMessageLayout
Represents the offsets for JD-Xi SysEx messages.
Byte | Description
--------------------------------------------------------
SYSEX_START | Start of SysEx message (0xF0)
ROLAND_ID | Roland ID (0x41)
DEVICE_ID | Device ID (0x10)
MODEL_ID_1 | First byte of Model ID (0x00)
MODEL_ID_2 | Second byte of Model ID (0x0E)
MODEL_ID_3 | Third byte of Model ID (0x00)
MODEL_ID_4 | Fourth byte of Model ID (0x00)
COMMAND_ID | Command ID (0x00 for Identity Request, 0x01 for Identity Reply)
ADDRESS_MSB | Most Significant Byte of Address
ADDRESS_UMB | Upper Middle Byte of Address
ADDRESS_LMB | Lower Middle Byte of Address
ADDRESS_LSB | Least Significant Byte of Address
TONE_NAME_START | Start of Tone Name (12 bytes)
TONE_NAME_END | End of Tone Name (24 bytes)
VALUE | Value (3 bytes, varies by command)
CHECKSUM | Checksum byte (calculated from the message)
SYSEX_END | End of SysEx message (0xF7)
for field in self.FIELDS:
raw = slice_bytes(data, field)
parsed = field.parser.from_bytes(raw)
"""
[docs]
FIELDS = (
FieldSpec(0, 1, SysExByte.START),
FieldSpec(1, 1, RolandID),
FieldSpec(2, 1, RolandID),
FieldSpec(JDXiSysExModelIDOffset, 4, ModelID),
FieldSpec(7, 1, CommandID),
FieldSpec(JDXiSysExAddressOffset, 4, ParameterAddress),
FieldSpec(JDXiSysExToneNameOffset, 12, bytes),
FieldSpec(-3, 3, bytes),
FieldSpec(-2, 1, Checksum),
FieldSpec(-1, 1, SysExByte.END),
)
[docs]
MODEL_ID = JDXiSysExModelIDOffset # 3-6
[docs]
ADDRESS = JDXiSysExAddressOffset # 8-11
[docs]
TONE_NAME = JDXiSysExToneNameOffset # 12-24
[docs]
class JDXiIdentityDeviceOffset:
"""Device Offsets"""
[docs]
FAMILY_CODE_1 = 6 # Device family code 1 0x0E
[docs]
FAMILY_CODE_2 = 7 # Device family code 2 0x03
[docs]
FAMILY_NUMBER_CODE_1 = 8 # Device family number code (0x00 for JD-Xi)
[docs]
FAMILY_NUMBER_CODE_2 = 9 # Device family number code (0x00 for JD-Xi)
[docs]
class JDXiIdentitySoftwareOffset:
"""Software revision offsets"""
[docs]
REVISION_1 = 10 # Software revision level 0x00
[docs]
class JDXiSysExIdentityLayout:
"""
JDXiIdentitySysExLayout
Represents the offsets for JD-Xi Identity SysEx messages.
Pos | Byte | Description
--------------------------------------------------------
0 | SYSEX_START | Start of SysEx message (0xF0)
1 | ID_NUMBER | ID Number (0x7E for non-realtime, 0x7F for realtime)
2 | DEVICE_ID | Device ID (0x7F for all devices)
3 | SUB_ID_1 | Sub ID 1 (0x06 for General Information)
4 | SUB_ID_2 | Sub ID 2 (0x01 for Identity Request, 0x02 for Identity Reply)
5 | ROLAND_ID | Roland Manufacturer ID (0x41, 0x10, 0x00)
6-9 | DEVICE_ID_1-4 | Device ID (0x0E for JD-Xi)
10-13 | REVISION_1-4 | Revision bytes x 4
14 | SYSEX_END | End of SysEx message (0xF7)
"""
[docs]
START = 0 # Start of SysEx message (0xF0)
[docs]
ID = JDXiIdentityHeaderOffset
[docs]
DEVICE = JDXiIdentityDeviceOffset # 6-9
[docs]
SOFTWARE = JDXiIdentitySoftwareOffset
[docs]
END = -1 # End of SysEx message (0xF7)
[docs]
__len__ = 14 # Total length of the Identity message (including start and end bytes)
@classmethod
[docs]
def expected_length(cls) -> int:
return 14 # 0 through 14, inclusive