367 lines
12 KiB
Python
367 lines
12 KiB
Python
|
|
"""
|
||
|
|
Meta messages for MIDI files.
|
||
|
|
|
||
|
|
TODO:
|
||
|
|
- what if an unknown meta message is implemented and someone depends on
|
||
|
|
the 'data' attribute?
|
||
|
|
- is 'type_byte' a good name?
|
||
|
|
- 'values' is not a good name for a dictionary.
|
||
|
|
- type and value safety?
|
||
|
|
- copy().
|
||
|
|
- expose _key_signature_encode/decode?
|
||
|
|
"""
|
||
|
|
|
||
|
|
from abc import ABCMeta, abstractmethod
|
||
|
|
from collections.abc import Generator, Iterable, Sequence
|
||
|
|
from contextlib import contextmanager
|
||
|
|
from numbers import Integral
|
||
|
|
from typing import ClassVar, Literal, Never, Self, SupportsIndex, override, type_check_only
|
||
|
|
|
||
|
|
from _typeshed import ReadableBuffer
|
||
|
|
|
||
|
|
from ..messages import BaseMessage
|
||
|
|
|
||
|
|
_charset: str = "latin1"
|
||
|
|
|
||
|
|
class KeySignatureError(Exception):
|
||
|
|
"""Raised when key cannot be converted from key/mode to key letter"""
|
||
|
|
|
||
|
|
_key_signature_decode: dict[tuple[int, int], str]
|
||
|
|
_key_signature_encode: dict[str, tuple[int, int]]
|
||
|
|
_smpte_framerate_decode: dict[int, int | float]
|
||
|
|
_smpte_framerate_encode: dict[int | float, int]
|
||
|
|
|
||
|
|
type _EncodeTypeNameSigned = Literal["byte", "short", "long"]
|
||
|
|
type _EncodeTypeNameUnsigned = Literal["ubyte", "ushort", "ulong"]
|
||
|
|
|
||
|
|
def signed(to_type: _EncodeTypeNameSigned | _EncodeTypeNameUnsigned, n: int) -> int: ...
|
||
|
|
def unsigned(to_type: _EncodeTypeNameSigned, n: int) -> int: ...
|
||
|
|
def encode_variable_int(value: int) -> list[int]:
|
||
|
|
"""Encode variable length integer.
|
||
|
|
|
||
|
|
Returns the integer as a list of bytes,
|
||
|
|
where the last byte is < 128.
|
||
|
|
|
||
|
|
This is used for delta times and meta message payload
|
||
|
|
length.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def decode_variable_int(value: Iterable[int]) -> int:
|
||
|
|
"""Decode a list to a variable length integer.
|
||
|
|
|
||
|
|
Does the opposite of `encode_variable_int(value)`
|
||
|
|
"""
|
||
|
|
|
||
|
|
def encode_string(string: str) -> list[int]: ...
|
||
|
|
def decode_string(data: Iterable[SupportsIndex] | ReadableBuffer) -> str: ...
|
||
|
|
@contextmanager
|
||
|
|
def meta_charset(tmp_charset: str) -> Generator[None, Never]: ...
|
||
|
|
def check_int(value: int | Integral, low: int | Integral, high: int | Integral) -> None: ...
|
||
|
|
def check_str(value: str) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec(metaclass=ABCMeta): # not an ABC in runtime
|
||
|
|
type: str
|
||
|
|
@type_check_only
|
||
|
|
@abstractmethod
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ... # not exist in runtime
|
||
|
|
@type_check_only
|
||
|
|
@abstractmethod
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ... # not exist in runtime
|
||
|
|
def check(self, name: Never, value: Never) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_sequence_number(MetaSpec):
|
||
|
|
type: str = "sequence_number"
|
||
|
|
type_byte: int = 0x00
|
||
|
|
attributes: ClassVar[list[str]] = ["number"]
|
||
|
|
defaults: ClassVar[list[int]] = [0]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: Never, value: int | Integral) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_text(MetaSpec):
|
||
|
|
type: str = "text"
|
||
|
|
type_byte: int = 0x01
|
||
|
|
attributes: ClassVar[list[str]] = ["text"]
|
||
|
|
defaults: ClassVar[list[str]] = [""]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Iterable[SupportsIndex] | ReadableBuffer) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: Never, value: str) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_copyright(MetaSpec_text):
|
||
|
|
type: str = "copyright"
|
||
|
|
type_byte: int = 0x02
|
||
|
|
|
||
|
|
class MetaSpec_track_name(MetaSpec_text):
|
||
|
|
type: str = "track_name"
|
||
|
|
type_byte: int = 0x03
|
||
|
|
attributes: ClassVar[list[str]] = ["name"]
|
||
|
|
defaults: ClassVar[list[str]] = [""]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Iterable[SupportsIndex] | ReadableBuffer) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
|
||
|
|
class MetaSpec_instrument_name(MetaSpec_track_name):
|
||
|
|
type: str = "instrument_name"
|
||
|
|
type_byte: int = 0x04
|
||
|
|
|
||
|
|
class MetaSpec_lyrics(MetaSpec_text):
|
||
|
|
type: str = "lyrics"
|
||
|
|
type_byte: int = 0x05
|
||
|
|
|
||
|
|
class MetaSpec_marker(MetaSpec_text):
|
||
|
|
type: str = "marker"
|
||
|
|
type_byte: int = 0x06
|
||
|
|
|
||
|
|
class MetaSpec_cue_marker(MetaSpec_text):
|
||
|
|
type: str = "cue_marker"
|
||
|
|
type_byte: int = 0x07
|
||
|
|
|
||
|
|
class MetaSpec_device_name(MetaSpec_track_name):
|
||
|
|
type: str = "device_name"
|
||
|
|
type_byte: int = 0x09
|
||
|
|
|
||
|
|
class MetaSpec_channel_prefix(MetaSpec):
|
||
|
|
type: str = "channel_prefix"
|
||
|
|
type_byte: int = 0x20
|
||
|
|
attributes: ClassVar[list[str]] = ["channel"]
|
||
|
|
defaults: ClassVar[list[int]] = [0]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: Never, value: int) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_midi_port(MetaSpec):
|
||
|
|
type: str = "midi_port"
|
||
|
|
type_byte: int = 0x21
|
||
|
|
attributes: ClassVar[list[str]] = ["port"]
|
||
|
|
defaults: ClassVar[list[int]] = [0]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: Never, value: int) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_end_of_track(MetaSpec):
|
||
|
|
type: str = "end_of_track"
|
||
|
|
type_byte: int = 0x2F
|
||
|
|
attributes: ClassVar[list[Never]] = []
|
||
|
|
defaults: ClassVar[list[Never]] = []
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
|
||
|
|
class MetaSpec_set_tempo(MetaSpec):
|
||
|
|
type: str = "set_tempo"
|
||
|
|
type_byte: int = 0x51
|
||
|
|
attributes: ClassVar[list[str]] = ["tempo"]
|
||
|
|
defaults: ClassVar[list[int]] = [500000]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: Never, value: int) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_smpte_offset(MetaSpec):
|
||
|
|
type: str = "smpte_offset"
|
||
|
|
type_byte: int = 0x54
|
||
|
|
attributes: ClassVar[list[str]] = ["frame_rate", "hours", "minutes", "seconds", "frames", "sub_frames"]
|
||
|
|
# TODO: What are some good defaults?
|
||
|
|
defaults: ClassVar[list[int]] = [24, 0, 0, 0, 0, 0]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: str, value: int) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_time_signature(MetaSpec):
|
||
|
|
type: str = "time_signature"
|
||
|
|
type_byte: int = 0x58
|
||
|
|
# TODO: these need more sensible names.
|
||
|
|
attributes: ClassVar[list[str]] = ["numerator", "denominator", "clocks_per_click", "notated_32nd_notes_per_beat"]
|
||
|
|
defaults: ClassVar[list[int]] = [4, 4, 24, 8]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: str, value: int) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_key_signature(MetaSpec):
|
||
|
|
type: str = "key_signature"
|
||
|
|
type_byte: int = 0x59
|
||
|
|
attributes: ClassVar[list[str]] = ["key"]
|
||
|
|
defaults: ClassVar[list[str]] = ["C"]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Sequence[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
@override
|
||
|
|
def check(self, name: Never, value: str) -> None: ...
|
||
|
|
|
||
|
|
class MetaSpec_sequencer_specific(MetaSpec):
|
||
|
|
type: str = "sequencer_specific"
|
||
|
|
type_byte: int = 0x7F
|
||
|
|
attributes: ClassVar[list[str]] = ["data"]
|
||
|
|
defaults: ClassVar[list[Sequence[int]]]
|
||
|
|
@override
|
||
|
|
def decode(self, message: MetaMessage, data: Iterable[int]) -> None: ...
|
||
|
|
@override
|
||
|
|
def encode(self, message: MetaMessage) -> list[int]: ...
|
||
|
|
|
||
|
|
def add_meta_spec(klass: type[MetaSpec]) -> None: ...
|
||
|
|
|
||
|
|
_META_SPECS: dict[int | str, type[MetaSpec]]
|
||
|
|
_META_SPEC_BY_TYPE: dict[str, type[MetaSpec]]
|
||
|
|
|
||
|
|
def build_meta_message(
|
||
|
|
meta_type: int | str, data: Sequence[int], delta: int = 0
|
||
|
|
) -> MetaMessage | UnknownMetaMessage: ...
|
||
|
|
|
||
|
|
# NOTE: Generics not supported in runtime
|
||
|
|
class _MetaMessage[T: str = str](BaseMessage):
|
||
|
|
type: T
|
||
|
|
time: int
|
||
|
|
@property
|
||
|
|
@override
|
||
|
|
def is_meta(self) -> Literal[True]: ... # a normal attribute in runtime
|
||
|
|
def __init__(self, type: str, skip_checks: bool = False, **kwargs: object) -> None: ...
|
||
|
|
@override
|
||
|
|
def copy(self, **overrides: object) -> Self:
|
||
|
|
"""Return a copy of the message
|
||
|
|
|
||
|
|
Attributes will be overridden by the passed keyword arguments.
|
||
|
|
Only message specific attributes can be overridden. The message
|
||
|
|
type can not be changed.
|
||
|
|
"""
|
||
|
|
@override
|
||
|
|
def __setattr__(self, name: str, value: object) -> None: ...
|
||
|
|
@override
|
||
|
|
def bytes(self) -> list[int]: ...
|
||
|
|
@classmethod
|
||
|
|
def from_bytes(cls, msg_bytes: Sequence[int]) -> MetaMessage | UnknownMetaMessage: ...
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class SequenceNumberMetaMessage(_MetaMessage[Literal["sequence_number"]]):
|
||
|
|
number: int
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class TextMetaMessage(_MetaMessage[Literal["text"]]):
|
||
|
|
text: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class CopyrightMetaMessage(_MetaMessage[Literal["copyright"]]):
|
||
|
|
text: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class TrackNameMetaMessage(_MetaMessage[Literal["track_name"]]):
|
||
|
|
name: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class InstrumentNameMetaMessage(_MetaMessage[Literal["instrument_name"]]):
|
||
|
|
name: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class LyricsMetaMessage(_MetaMessage[Literal["lyrics"]]):
|
||
|
|
text: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class MarkerMetaMessage(_MetaMessage[Literal["marker"]]):
|
||
|
|
text: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class CueMarkerMetaMessage(_MetaMessage[Literal["cue_marker"]]):
|
||
|
|
text: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class DeviceNameMetaMessage(_MetaMessage[Literal["device_name"]]):
|
||
|
|
name: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class ChannelPrefixMetaMessage(_MetaMessage[Literal["channel_prefix"]]):
|
||
|
|
channel: int
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class MidiPortMetaMessage(_MetaMessage[Literal["midi_port"]]):
|
||
|
|
port: int
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class EndOfTrackMetaMessage(_MetaMessage[Literal["end_of_track"]]): ...
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class SetTempoMetaMessage(_MetaMessage[Literal["set_tempo"]]):
|
||
|
|
tempo: int
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class SmpteOffsetMetaMessage(_MetaMessage[Literal["smpte_offset"]]):
|
||
|
|
frame_rate: int
|
||
|
|
hours: int
|
||
|
|
minutes: int
|
||
|
|
seconds: int
|
||
|
|
frames: int
|
||
|
|
sub_frames: int
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class TimeSignatureMetaMessage(_MetaMessage[Literal["time_signature"]]):
|
||
|
|
numerator: int
|
||
|
|
denominator: int
|
||
|
|
clocks_per_click: int
|
||
|
|
notated_32nd_notes_per_beat: int
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class KeySignatureMetaMessage(_MetaMessage[Literal["key_signature"]]):
|
||
|
|
key: str
|
||
|
|
|
||
|
|
@type_check_only
|
||
|
|
class SequencerSpecificMetaMessage(_MetaMessage[Literal["sequencer_specific"]]):
|
||
|
|
data: Sequence[int]
|
||
|
|
|
||
|
|
class UnknownMetaMessage(_MetaMessage):
|
||
|
|
type_byte: Sequence[int]
|
||
|
|
data: tuple[int, ...]
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
type_byte: Sequence[int],
|
||
|
|
data: Sequence[int] | None = ...,
|
||
|
|
time: int = 0,
|
||
|
|
type: str = "unknown_meta",
|
||
|
|
**kwargs: Never,
|
||
|
|
) -> None: ...
|
||
|
|
@override
|
||
|
|
def __setattr__(self, name: str, value: object) -> None: ...
|
||
|
|
@override
|
||
|
|
def bytes(self) -> list[int]: ...
|
||
|
|
|
||
|
|
# NOTE: `MetaMessage` is a concrete type (actually the `_MetaMessage` in this stub) in runtime
|
||
|
|
type MetaMessage = (
|
||
|
|
SequenceNumberMetaMessage
|
||
|
|
| TextMetaMessage
|
||
|
|
| CopyrightMetaMessage
|
||
|
|
| TrackNameMetaMessage
|
||
|
|
| InstrumentNameMetaMessage
|
||
|
|
| LyricsMetaMessage
|
||
|
|
| MarkerMetaMessage
|
||
|
|
| CueMarkerMetaMessage
|
||
|
|
| DeviceNameMetaMessage
|
||
|
|
| ChannelPrefixMetaMessage
|
||
|
|
| MidiPortMetaMessage
|
||
|
|
| EndOfTrackMetaMessage
|
||
|
|
| SetTempoMetaMessage
|
||
|
|
| SmpteOffsetMetaMessage
|
||
|
|
| TimeSignatureMetaMessage
|
||
|
|
| KeySignatureMetaMessage
|
||
|
|
| SequencerSpecificMetaMessage
|
||
|
|
)
|