Source code for libretro.drivers.midi.generator
"""
:class:`.MidiDriver` implementation that streams events produced by a generator function.
.. seealso::
:class:`.MidiDriver`
The protocol this driver implements.
"""
from collections import deque
from collections.abc import Callable, Iterator, Sequence
from typing import NamedTuple, override
from .driver import MidiDriver
MidiIterator = Iterator[int | None]
MidiGenerator = Callable[[], MidiIterator]
[docs]
class MidiWrite(NamedTuple):
"""A record of a MIDI write event from the core."""
byte: int
delta_time: int
[docs]
class GeneratorMidiDriver(MidiDriver):
"""
A :class:`.MidiDriver` that streams input bytes from a generator function.
Output bytes are appended to an in-memory buffer
accessible through :attr:`output` so tests can assert on what the core sent.
.. seealso::
:class:`.MidiDriver`
The protocol this class implements.
"""
[docs]
def __init__(self, generator: MidiGenerator | None = None):
"""
Initialize the driver with an optional MIDI input source.
:param generator: A callable returning an iterator of MIDI input bytes
(or :obj:`None` for "no byte this poll"),
or :obj:`None` to disable MIDI input entirely.
"""
super().__init__()
self._generator = generator
self._generator_state: MidiIterator | None = None
self._last_poll_result: int | StopIteration | None = None
self._output: deque[MidiWrite] = deque()
self._input_enabled = True
self._output_enabled = True
@property
@override
def input_enabled(self) -> bool:
return bool(
self._generator
and self._input_enabled
and not isinstance(self._last_poll_result, StopIteration)
)
@input_enabled.setter
def input_enabled(self, value: bool):
self._input_enabled = bool(value)
@property
@override
def output_enabled(self) -> bool:
return self._output_enabled
@output_enabled.setter
def output_enabled(self, value: bool):
self._output_enabled = bool(value)
@property
def output(self) -> Sequence[MidiWrite]:
"""The buffered :class:`.MidiWrite` events produced by the core, in send order."""
return self._output
[docs]
@override
def read(self) -> int | None:
if not self._generator:
return None
try:
if self._generator_state is None:
self._generator_state = self._generator()
self._last_poll_result = next(self._generator_state, StopIteration())
match self._last_poll_result:
case None | int() as i:
return i
case StopIteration() as e:
return None
case _:
raise TypeError("MidiGenerator must yield integers or None")
except StopIteration as e:
self._last_poll_result = e
return None
[docs]
@override
def write(self, byte: int, delta_time: int) -> bool:
self._output.append(MidiWrite(byte, delta_time))
return True
[docs]
@override
def flush(self) -> bool:
self._output.clear()
return True
__all__ = ["GeneratorMidiDriver", "MidiWrite", "MidiIterator", "MidiGenerator"]