Add type stubs for mido MIDI library
- Created type stubs for various modules in the mido library including messages, midifiles, parser, ports, sockets, syx, tokenizer, and version. - Implemented type hints for functions and classes to improve type checking and code clarity. - Added support for MIDI over TCP/IP in sockets module. - Included methods for reading and writing SYX files in syx module. - Enhanced the parser functionality with a dedicated Parser class for MIDI byte streams. - Established a structure for MIDI file handling with MidiFile and MidiTrack classes.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
from .checks import check_time
|
||||
from .messages import BaseMessage, Message, format_as_string, parse_string, parse_string_stream
|
||||
from .specs import MAX_PITCHWHEEL, MAX_SONGPOS, MIN_PITCHWHEEL, MIN_SONGPOS, SPEC_BY_STATUS, SPEC_BY_TYPE, SPEC_LOOKUP
|
||||
|
||||
__all__ = [
|
||||
"MAX_PITCHWHEEL",
|
||||
"MAX_SONGPOS",
|
||||
"MIN_PITCHWHEEL",
|
||||
"MIN_SONGPOS",
|
||||
"SPEC_BY_STATUS",
|
||||
"SPEC_BY_TYPE",
|
||||
"SPEC_LOOKUP",
|
||||
"BaseMessage",
|
||||
"Message",
|
||||
"check_time",
|
||||
"format_as_string",
|
||||
"parse_string",
|
||||
"parse_string_stream",
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
from collections.abc import Callable, Iterable
|
||||
from numbers import Integral, Real
|
||||
|
||||
from .specs import MsgDict
|
||||
|
||||
def check_type(type_: str) -> None: ...
|
||||
def check_channel(channel: int | Integral) -> None: ...
|
||||
def check_pos(pos: int | Integral) -> None: ...
|
||||
def check_pitch(pitch: int | Integral) -> None: ...
|
||||
def check_data(data_bytes: Iterable[int | Integral]) -> None: ...
|
||||
def check_frame_type(value: int | Integral) -> None: ...
|
||||
def check_frame_value(value: int | Integral) -> None: ...
|
||||
def check_data_byte(value: int | Integral) -> None: ...
|
||||
def check_time(time: float | Real) -> None: ...
|
||||
|
||||
_CHECKS: dict[str, Callable[[object], None]]
|
||||
|
||||
def check_value(name: str, value: object) -> None: ...
|
||||
def check_msgdict(msgdict: MsgDict) -> None: ...
|
||||
@@ -0,0 +1,15 @@
|
||||
from collections.abc import Callable, Iterable
|
||||
from numbers import Integral
|
||||
|
||||
from .specs import MsgDict
|
||||
|
||||
_SPECIAL_CASES: dict[int, Callable[[Iterable[int | Integral]], dict[str, object]]]
|
||||
|
||||
def decode_message(msg_bytes: Iterable[int | Integral], time: int = 0, check: bool = True) -> MsgDict:
|
||||
"""Decode message bytes and return messages as a dictionary.
|
||||
|
||||
Raises ValueError if the bytes are out of range or the message is
|
||||
invalid.
|
||||
|
||||
This is not a part of the public API.
|
||||
"""
|
||||
@@ -0,0 +1,14 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from .specs import MsgDict
|
||||
|
||||
_SPECIAL_CASES: dict[str, Callable[[MsgDict], list[object]]]
|
||||
|
||||
def encode_message(msg: MsgDict) -> list[int]:
|
||||
"""Encode msg dict as a list of bytes.
|
||||
|
||||
TODO: Add type and value checking.
|
||||
(Can be turned off with keyword argument.)
|
||||
|
||||
This is not a part of the public API.
|
||||
"""
|
||||
@@ -0,0 +1,146 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import Generator, Iterable
|
||||
from typing import Literal, Never, Self, Unpack, override
|
||||
|
||||
from mido.messages.specs import MsgDict, MsgDictOverride
|
||||
|
||||
class BaseMessage(metaclass=ABCMeta):
|
||||
"""Abstract base class for messages."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_meta(self) -> bool: ... # a normal attribute in runtime
|
||||
@abstractmethod
|
||||
def copy(self) -> Self: ...
|
||||
@abstractmethod
|
||||
def bytes(self) -> list[int]: ...
|
||||
def bin(self) -> bytearray:
|
||||
"""Encode message and return as a bytearray.
|
||||
|
||||
This can be used to write the message to a file.
|
||||
"""
|
||||
|
||||
def hex(self, sep: str = " ") -> str:
|
||||
"""Encode message and return as a string of hex numbers,
|
||||
|
||||
Each number is separated by the string sep.
|
||||
"""
|
||||
|
||||
def dict(self) -> MsgDict:
|
||||
"""Returns a dictionary containing the attributes of the message.
|
||||
|
||||
Example: `{'type': 'sysex', 'data': [1, 2], 'time': 0}`
|
||||
|
||||
Sysex data will be returned as a list.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: MsgDict) -> Self:
|
||||
"""Create a message from a dictionary.
|
||||
|
||||
Only "type" is required. The other will be set to default
|
||||
values.
|
||||
"""
|
||||
|
||||
@property
|
||||
def is_realtime(self) -> bool:
|
||||
"""True if the message is a system realtime message."""
|
||||
|
||||
def is_cc(self, control: int | None = ...) -> bool:
|
||||
"""Return True if the message is of type 'control_change'.
|
||||
|
||||
The optional control argument can be used to test for a specific
|
||||
control number, for example:
|
||||
|
||||
>>> if msg.is_cc(7):
|
||||
... # Message is control change 7 (channel volume).
|
||||
"""
|
||||
|
||||
@override
|
||||
def __delattr__(self, name: str) -> Never: ...
|
||||
@override
|
||||
def __setattr__(self, name: str, value: object) -> None: ...
|
||||
@override
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
|
||||
class SysexData(tuple[int, ...]):
|
||||
"""Special kind of tuple accepts and converts any sequence in `+=`."""
|
||||
def __iadd__(self, other: SysexData) -> Self: ...
|
||||
|
||||
class Message(BaseMessage):
|
||||
type: str
|
||||
time: int
|
||||
|
||||
@property
|
||||
@override
|
||||
def is_meta(self) -> Literal[False]: ... # a normal attribute in runtime
|
||||
def __init__(self, type: str, skip_checks: bool = False, **args: Unpack[MsgDictOverride]) -> None: ...
|
||||
@override
|
||||
def copy(self, skip_checks: bool = False, **overrides: Unpack[MsgDictOverride]) -> 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.
|
||||
|
||||
The `skip_checks` arg can be used to bypass validation of message
|
||||
attributes and should be used cautiously.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: Iterable[int], time: int = 0) -> Self:
|
||||
"""Parse a byte encoded message.
|
||||
|
||||
Accepts a byte string or any iterable of integers.
|
||||
|
||||
This is the reverse of `msg.bytes()` or `msg.bin()`.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_hex(cls, text: str, time: int = 0, sep: str | None = ...) -> Self:
|
||||
"""Parse a hex encoded message.
|
||||
|
||||
This is the reverse of `msg.hex()`.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, text: str) -> Self:
|
||||
"""Parse a string encoded message.
|
||||
|
||||
This is the reverse of `str(msg)`.
|
||||
"""
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
@override
|
||||
def __setattr__(self, name: str, value: object) -> None: ...
|
||||
@override
|
||||
def bytes(self) -> list[int]:
|
||||
"""Encode message and return as a list of integers."""
|
||||
|
||||
def parse_string(text: str) -> Message:
|
||||
"""Parse a string of text and return a message.
|
||||
|
||||
The string can span multiple lines, but must contain
|
||||
one full message.
|
||||
|
||||
Raises ValueError if the string could not be parsed.
|
||||
"""
|
||||
|
||||
def parse_string_stream(stream: Iterable[str]) -> Generator[tuple[Message, None] | tuple[None, str], Never]:
|
||||
"""Parse a stream of messages and yield `(message, error_message)`
|
||||
|
||||
stream can be any iterable that generates text strings, where each
|
||||
string is a string encoded message.
|
||||
|
||||
If a string can be parsed, `(message, None)` is returned. If it
|
||||
can't be parsed, `(None, error_message)` is returned. The error
|
||||
message contains the line number where the error occurred.
|
||||
"""
|
||||
|
||||
def format_as_string(msg: Message, include_time: bool = True) -> str:
|
||||
"""Format a message and return as a string.
|
||||
|
||||
This is equivalent to `str(message)`.
|
||||
|
||||
To leave out the time attribute, pass `include_time=False`.
|
||||
"""
|
||||
@@ -0,0 +1,179 @@
|
||||
"""Definitions and lookup tables for MIDI messages.
|
||||
|
||||
TODO:
|
||||
|
||||
* add lookup functions for messages definitions by type and status
|
||||
byte.
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Literal, NotRequired, TypedDict, final, overload, type_check_only
|
||||
|
||||
CHANNEL_MESSAGES: set[int]
|
||||
COMMON_MESSAGES: set[int]
|
||||
REALTIME_MESSAGES: set[int]
|
||||
|
||||
SYSEX_START: int = 0xF0
|
||||
SYSEX_END: int = 0xF7
|
||||
|
||||
# Pitchwheel is a 14 bit signed integer
|
||||
MIN_PITCHWHEEL: int = -8192
|
||||
MAX_PITCHWHEEL: int = 8191
|
||||
|
||||
# Song pos is a 14 bit unsigned integer
|
||||
MIN_SONGPOS: int = 0
|
||||
MAX_SONGPOS: int = 16383
|
||||
|
||||
@type_check_only
|
||||
class SpecsDict(TypedDict):
|
||||
status_byte: int
|
||||
type: str
|
||||
value_names: tuple[str, ...]
|
||||
attribute_names: set[str]
|
||||
length: int | float
|
||||
|
||||
@type_check_only
|
||||
class MsgDictOverride(TypedDict):
|
||||
channel: NotRequired[int]
|
||||
control: NotRequired[int]
|
||||
data: NotRequired[Sequence[int]]
|
||||
frame_type: NotRequired[int]
|
||||
frame_value: NotRequired[int]
|
||||
note: NotRequired[int]
|
||||
pitch: NotRequired[int]
|
||||
pos: NotRequired[int]
|
||||
program: NotRequired[int]
|
||||
song: NotRequired[int]
|
||||
value: NotRequired[int]
|
||||
velocity: NotRequired[int]
|
||||
time: NotRequired[int | float]
|
||||
|
||||
@type_check_only
|
||||
class MsgDictBase[T: str](TypedDict):
|
||||
type: T
|
||||
time: int | float
|
||||
|
||||
@type_check_only
|
||||
class ChannelMsgDictBase[
|
||||
T: Literal["note_off", "note_on", "polytouch", "control_change", "program_change", "aftertouch", "pitchwheel"]
|
||||
](MsgDictBase[T]):
|
||||
channel: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class NoteMsgDict(ChannelMsgDictBase[Literal["note_off", "note_on"]]):
|
||||
note: int
|
||||
velocity: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class PolytouchMsgDict(ChannelMsgDictBase[Literal["polytouch"]]):
|
||||
note: int
|
||||
value: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class ControlChangeMsgDict(ChannelMsgDictBase[Literal["control_change"]]):
|
||||
control: int
|
||||
value: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class ProgramChangeMsgDict(ChannelMsgDictBase[Literal["program_change"]]):
|
||||
program: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class AftertouchMsgDict(ChannelMsgDictBase[Literal["aftertouch"]]):
|
||||
value: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class PitchwheelMsgDict(ChannelMsgDictBase[Literal["pitchwheel"]]):
|
||||
pitch: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class SysExMsgDict(MsgDictBase[Literal["sysex"]]):
|
||||
data: Sequence[int]
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class QuarterFrameMsgDict(MsgDictBase[Literal["quarter_frame"]]):
|
||||
frame_type: int
|
||||
frame_value: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class SongposMsgDict(MsgDictBase[Literal["songpos"]]):
|
||||
pos: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class SongSelectMsgDict(MsgDictBase[Literal["song_select"]]):
|
||||
song: int
|
||||
|
||||
@type_check_only
|
||||
@final
|
||||
class RealTimeMsgDict(
|
||||
MsgDictBase[Literal["tune_request", "clock", "start", "continue", "stop", "active_sensing", "reset"]]
|
||||
): ...
|
||||
|
||||
type MsgDict = (
|
||||
NoteMsgDict
|
||||
| PolytouchMsgDict
|
||||
| ControlChangeMsgDict
|
||||
| ProgramChangeMsgDict
|
||||
| AftertouchMsgDict
|
||||
| PitchwheelMsgDict
|
||||
| SysExMsgDict
|
||||
| QuarterFrameMsgDict
|
||||
| SongposMsgDict
|
||||
| SongSelectMsgDict
|
||||
| RealTimeMsgDict
|
||||
)
|
||||
|
||||
SPECS: list[SpecsDict]
|
||||
SPEC_LOOKUP: dict[str | int, SpecsDict]
|
||||
SPEC_BY_STATUS: dict[int, SpecsDict]
|
||||
SPEC_BY_TYPE: dict[str, SpecsDict]
|
||||
REALTIME_TYPES: set[str]
|
||||
DEFAULT_VALUES: MsgDict
|
||||
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["note_off", "note_on"], overrides: MsgDictOverride) -> NoteMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["polytouch"], overrides: MsgDictOverride) -> PolytouchMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["control_change"], overrides: MsgDictOverride) -> ControlChangeMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["program_change"], overrides: MsgDictOverride) -> ProgramChangeMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["aftertouch"], overrides: MsgDictOverride) -> AftertouchMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["pitchwheel"], overrides: MsgDictOverride) -> PitchwheelMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["sysex"], overrides: MsgDictOverride) -> SysExMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["quarter_frame"], overrides: MsgDictOverride) -> QuarterFrameMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["songpos"], overrides: MsgDictOverride) -> SongposMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(type_: Literal["song_select"], overrides: MsgDictOverride) -> SongSelectMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict(
|
||||
type_: Literal["tune_request", "clock", "start", "continue", "stop", "active_sensing", "reset"],
|
||||
overrides: MsgDictOverride,
|
||||
) -> RealTimeMsgDict: ...
|
||||
@overload
|
||||
def make_msgdict[T: str](type_: T, overrides: MsgDictOverride) -> MsgDictBase[T]: ...
|
||||
def make_msgdict[T: str](type_: T, overrides: MsgDictOverride) -> MsgDictBase[T]:
|
||||
"""Return a new message.
|
||||
|
||||
Returns a dictionary representing a message.
|
||||
|
||||
Message values can be overriden.
|
||||
|
||||
No type or value checking is done. The caller is responsible for
|
||||
calling `check_msgdict()`.
|
||||
"""
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
This type stub file was generated by pyright.
|
||||
"""
|
||||
|
||||
from .specs import MsgDict
|
||||
|
||||
def msg2str(msg: MsgDict, include_time: bool = True) -> str:
|
||||
...
|
||||
|
||||
def str2msg(text: str) -> MsgDict:
|
||||
"""Parse str format and return message dict.
|
||||
|
||||
No type or value checking is done. The caller is responsible for
|
||||
calling check_msgdict().
|
||||
"""
|
||||
Reference in New Issue
Block a user