68 lines
2.2 KiB
Python
68 lines
2.2 KiB
Python
"""FL-MCP MIDI Transport using mido."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import mido
|
|
from mido import Message
|
|
|
|
from .sysex import encode_command
|
|
|
|
|
|
class MidiTransport:
|
|
"""MIDI transport for sending SysEx commands to FL Studio via loopback."""
|
|
|
|
def __init__(self, port_name: str = "FL_MCP"):
|
|
self.port_name = port_name
|
|
self._output: mido.ports.Port | None = None
|
|
self._input: mido.ports.Port | None = None
|
|
|
|
def connect(self) -> bool:
|
|
"""Find and open the FL_MCP output port."""
|
|
ports = mido.get_output_names()
|
|
# Try exact match first, then partial
|
|
match: str | None = None
|
|
for p in ports:
|
|
if self.port_name in p:
|
|
match = p
|
|
break
|
|
if not match:
|
|
raise ConnectionError(
|
|
f"Port '{self.port_name}' not found. Available: {ports}"
|
|
)
|
|
self._output = mido.open_output(match)
|
|
return True
|
|
|
|
def send_command(self, cmd: str, params: dict | None = None) -> None:
|
|
"""Send a SysEx command to FL Studio."""
|
|
if not self._output:
|
|
self.connect()
|
|
data = encode_command(cmd, params)
|
|
# mido.Message('sysex', data=...) automatically wraps with F0/F7
|
|
# data[1:-1] skips the F0/SYSEX_ID/F7 wrapper since mido adds them
|
|
msg = Message("sysex", data=bytes(data[1:-1]))
|
|
self._output.send(msg)
|
|
|
|
def receive(self, timeout: float = 1.0) -> mido.Message | None:
|
|
"""Receive a MIDI message (blocking with timeout)."""
|
|
if not self._input:
|
|
self._input = mido.open_input(
|
|
next((p for p in mido.get_input_names() if self.port_name in p), None)
|
|
)
|
|
if self._input:
|
|
return self._input.receive(timeout=timeout)
|
|
return None
|
|
|
|
def close(self) -> None:
|
|
"""Close all MIDI ports."""
|
|
if self._output:
|
|
self._output.close()
|
|
self._output = None
|
|
if self._input:
|
|
self._input.close()
|
|
self._input = None
|
|
|
|
@staticmethod
|
|
def list_ports() -> dict[str, list[str]]:
|
|
"""List all available MIDI input and output ports."""
|
|
return {"inputs": mido.get_input_names(), "outputs": mido.get_output_names()}
|