""" 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."""