Source code for jdxi_editor.midi.message.midi

"""
MIDI Message Module
===================

This module provides a base class for handling MIDI messages in a structured manner.
It defines the `Message` class, which serves as the foundation for various types of
MIDI messages, ensuring proper formatting and conversion to MIDI-compliant byte sequences.

Classes:
    - Message: Base class for MIDI messages, enforcing structure and conversion methods.

Features:
    - Provides constants for MIDI message handling (status mask, channel mask, max value).
    - Ensures subclass implementation of the `to_list` method for MIDI byte conversion.
    - Offers utility methods for converting messages to bytes and hexadecimal string format.

Usage Example:
    >>> class NoteOnMessage(MidiMessage):
    ...     def to_list(self):
    ...         return [0x90, 60, 100]  # Note On for Middle C with velocity 100
    ...
    >>> msg = NoteOnMessage()
    >>> msg.to_bytes().hex()
    '903c64'
    >>> msg.to_hex_string()
    '90 3C 64'
"""

from dataclasses import dataclass
from typing import List


@dataclass
[docs] class MidiMessage: """MIDI message base class"""
[docs] MIDI_MAX_VALUE = 0x7F # 127
[docs] MIDI_STATUS_MASK = 0xF0 # Extracts message type
[docs] MIDI_CHANNEL_MASK = 0x0F # Extracts channel number
[docs] def to_message_list(self) -> List[int]: """Convert to list of bytes for sending, must be implemented in subclass""" raise NotImplementedError("Subclasses should implement this method.")
[docs] def to_bytes(self) -> bytes: """Convert to bytes for sending""" return bytes(self.to_message_list())
[docs] def to_hex_string(self) -> str: """Convert message to a formatted hexadecimal string.""" # Safely convert to int for formatting (handles strings, enums, floats, etc.) def safe_int(val): if isinstance(val, int): return val if hasattr(val, "value"): # Handle enums enum_val = val.value return int(enum_val) if not isinstance(enum_val, int) else enum_val try: return int(float(val)) # Handle floats and strings except (ValueError, TypeError): return 0 return " ".join(f"{safe_int(x):02X}" for x in self.to_message_list())