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:
2026-04-26 00:51:40 +08:00
parent e5c692e5d7
commit 9009a7c5bc
23 changed files with 1875 additions and 0 deletions
+234
View File
@@ -0,0 +1,234 @@
"""
Useful tools for working with ports
"""
import threading
from collections import deque
from collections.abc import Collection, Generator, Iterable
from typing import Any, Literal, Never, Self, overload
from .messages import Message
from .parser import Parser
# How many seconds to sleep before polling again.
_DEFAULT_SLEEP_TIME: float
_sleep_time: int | float
def sleep() -> None:
"""Sleep for N seconds.
This is used in ports when polling and waiting for messages. N can
be set with `set_sleep_time()`."""
def set_sleep_time(seconds: int | float = ...) -> None:
"""Set the number of seconds `sleep()` will sleep."""
def get_sleep_time() -> int | float:
"""Get number of seconds `sleep()` will sleep."""
def reset_messages() -> Generator[Message, Never]:
"""Yield "All Notes Off" and "Reset All Controllers" for all channels"""
def panic_messages() -> Generator[Message, Never]:
"""Yield "All Sounds Off" for all channels.
This will mute all sounding notes regardless of
envelopes. Useful when notes are hanging and nothing else
helps.
"""
class DummyLock:
def __enter__(self) -> Self: ...
def __exit__(self, *_: object) -> Literal[False]: ...
class BasePort:
"""
Abstract base class for Input and Output ports.
"""
is_input: bool = False
is_output: bool = False
_locking: bool = True
name: bool
closed: bool
_lock: DummyLock | threading.RLock
def __init__(self, name: str | None = None, **kwargs: object) -> None: ...
def _open(self, **kwargs: object) -> None: ...
def _close(self) -> None: ...
def close(self) -> None:
"""Close the port.
If the port is already closed, nothing will happen. The port
is automatically closed when the object goes out of scope or
is garbage collected.
"""
def __del__(self) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, *_) -> Literal[False]: ...
class BaseInput(BasePort):
"""Base class for input port.
Subclass and override `_receive()` to create a new input port type.
(See `portmidi.py` for an example of how to do this.)
"""
is_input: bool = True
_parser: Parser
_messages: deque[Message]
def __init__(self, name: str = "", **kwargs: Any) -> None:
"""Create an input port.
name is the port name, as returned by `input_names()`. If
name is not passed, the default input is used instead.
"""
def _receive(self, block: bool = True) -> Message: ...
def iter_pending(self) -> Generator[Message, Never]:
"""Iterate through pending messages."""
@overload
def receive(self, block: Literal[True] = True) -> Message:
"""Return the next message.
This will block until a message arrives.
If you pass `block=False` it will not block and instead return
None if there is no available message.
If the port is closed and there are no pending messages IOError
will be raised. If the port closes while waiting inside `receive()`,
IOError will be raised. TODO: this seems a bit inconsistent. Should
different errors be raised? What's most useful here?
"""
@overload
def receive(self, block: bool) -> Message | None: ...
def poll(self) -> Message | None:
"""Receive the next pending message or None
This is the same as calling `receive(block=False)`."""
def __iter__(self) -> Generator[Message, Never]:
"""Iterate through messages until the port closes."""
class BaseOutput(BasePort):
"""
Base class for output port.
Subclass and override `_send()` to create a new port type. (See
`portmidi.py` for how to do this.)
"""
is_output: bool = True
autoreset: bool
def __init__(self, name: str = "", autoreset: bool = False, **kwargs: object) -> None:
"""Create an output port
name is the port name, as returned by `output_names()`. If
name is not passed, the default output is used instead.
"""
def _send(self, msg: Message) -> None: ...
def send(self, msg: Message) -> None:
"""Send a message on the port.
A copy of the message will be sent, so you can safely modify
the original message without any unexpected consequences.
"""
def reset(self) -> None:
"""Send "All Notes Off" and "Reset All Controllers" on all channels"""
def panic(self) -> None:
"""Send "All Sounds Off" on all channels.
This will mute all sounding notes regardless of
envelopes. Useful when notes are hanging and nothing else
helps.
"""
class BaseIOPort(BaseInput, BaseOutput): # pyright: ignore[reportUnsafeMultipleInheritance]
def __init__(self, name: str = "", **kwargs: object) -> None:
"""Create an IO port.
name is the port name, as returned by ioport_names().
"""
# NOTE: Generics not supported in runtime
class IOPort[InT: BaseInput = BaseInput, OutT: BaseOutput = BaseOutput](BaseIOPort):
"""Input / output port.
This is a convenient wrapper around an input port and an output
port which provides the functionality of both. Every method call
is forwarded to the appropriate port.
"""
_locking: bool = False
input: InT
output: OutT
def __init__(self, input: InT, output: OutT) -> None: ...
class EchoPort(BaseIOPort): ...
class MultiPort(BaseIOPort):
def __init__(self, ports: Iterable[BaseIOPort], yield_ports: bool = False) -> None: ...
@overload
def multi_receive[T: BaseInput](
ports: Collection[T], yield_ports: Literal[True], block: bool = True
) -> Generator[tuple[T, Message], Never]: ...
@overload
def multi_receive(
ports: Collection[BaseInput], yield_ports: Literal[False] = False, block: bool = True
) -> Generator[Message, Never]: ...
@overload
def multi_receive[T: BaseInput](
ports: Collection[T], yield_ports: bool, block: bool = True
) -> Generator[tuple[T, Message] | Message, Never]: ...
def multi_receive[T: BaseInput](
ports: Collection[T], yield_ports: bool, block: bool = True
) -> Generator[tuple[T, Message] | Message, Never]:
"""Receive messages from multiple ports.
Generates messages from ever input port. The ports are polled in
random order for fairness, and all messages from each port are
yielded before moving on to the next port.
If `yield_ports=True`, `(port, message)` is yielded instead of just
the message.
If `block=False` only pending messages will be yielded.
"""
@overload
def multi_iter_pending[T: BaseInput](
ports: Collection[T], yield_ports: Literal[True]
) -> Generator[tuple[T, Message], Never]: ...
@overload
def multi_iter_pending(
ports: Collection[BaseInput], yield_ports: Literal[False] = False
) -> Generator[Message, Never]: ...
@overload
def multi_iter_pending[T: BaseInput](
ports: Collection[T], yield_ports: bool
) -> Generator[tuple[T, Message] | Message, Never]: ...
def multi_iter_pending[T: BaseInput](
ports: Collection[T], yield_ports: bool
) -> Generator[tuple[T, Message] | Message, Never]:
"""Iterate through all pending messages in ports.
This is the same as calling `multi_receive(ports, block=False)`.
The function is kept around for backwards compatability.
"""
def multi_send(ports: Iterable[BaseOutput], msg: Message) -> None:
"""Send message on all ports."""