CI / backend (pull_request) Canceled after 0s
CI / shell (pull_request) Canceled after 0s
CI / frontend (pull_request) Canceled after 0s
CI / arm64-smoke (pull_request) Canceled after 0s
CI / backend (push) Canceled after 0s
CI / shell (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / arm64-smoke (push) Canceled after 0s
132 lines
5.0 KiB
Python
132 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import logging
|
|
from contextlib import suppress
|
|
|
|
from .client import TuxDmxMidiClient, reconnect_delay
|
|
from .config import BridgeConfig, load_config
|
|
from .logging_setup import configure_logging
|
|
from .midi import MidiEventFilter, list_input_devices, match_input_device, normalize_message
|
|
|
|
logger = logging.getLogger("tuxdmx_midi_bridge")
|
|
|
|
|
|
async def run_bridge(config: BridgeConfig, debug: bool = False) -> None:
|
|
if debug:
|
|
config.logging.level = "DEBUG"
|
|
config.logging.show_midi_events = True
|
|
configure_logging(config.logging.level)
|
|
client = TuxDmxMidiClient(config.server)
|
|
event_filter = MidiEventFilter(
|
|
channel_filter=config.midi.channel_filter,
|
|
control_change_interval_ms=config.performance.control_change_interval_ms,
|
|
suppress_duplicate_values=config.performance.suppress_duplicate_values,
|
|
)
|
|
attempt = 0
|
|
|
|
while True:
|
|
try:
|
|
device_name = match_input_device(config.midi.device_name)
|
|
if device_name is None:
|
|
raise RuntimeError(f"MIDI-input matcher ikke '{config.midi.device_name}'.")
|
|
logger.info("Valgt MIDI-input: %s", device_name)
|
|
import mido
|
|
|
|
with mido.open_input(device_name) as port:
|
|
await client.send_heartbeat(
|
|
{
|
|
"type": "heartbeat",
|
|
"protocol_version": 1,
|
|
"bridge_id": config.bridge_id,
|
|
"device": device_name,
|
|
}
|
|
)
|
|
logger.info("Forbundet til TuxDMX som bridge '%s'", config.bridge_id)
|
|
attempt = 0
|
|
heartbeat_task = asyncio.create_task(_heartbeat_loop(client, config.bridge_id, device_name))
|
|
try:
|
|
while True:
|
|
for message in port.iter_pending():
|
|
if config.midi.ignore_active_sensing and getattr(message, "type", "") == "active_sensing":
|
|
continue
|
|
payload = normalize_message(message, config.bridge_id, device_name)
|
|
if payload is None or not event_filter.should_forward(payload):
|
|
continue
|
|
if config.logging.show_midi_events:
|
|
logger.info(
|
|
"MIDI %s ch=%s no=%s val=%s",
|
|
payload["message"]["type"],
|
|
payload["message"]["channel"],
|
|
payload["message"]["number"],
|
|
payload["message"]["value"],
|
|
)
|
|
await client.send_event(payload)
|
|
await asyncio.sleep(0.01)
|
|
finally:
|
|
heartbeat_task.cancel()
|
|
with suppress(asyncio.CancelledError):
|
|
await heartbeat_task
|
|
except KeyboardInterrupt:
|
|
logger.info("Afslutter MIDI-bridge kontrolleret.")
|
|
return
|
|
except Exception as exc:
|
|
attempt += 1
|
|
delay = reconnect_delay(attempt, config.server.reconnect_seconds)
|
|
logger.warning("Bridge-fejl: %s. Nyt forsøg om %.1f sek.", exc, delay)
|
|
await asyncio.sleep(delay)
|
|
|
|
|
|
async def _heartbeat_loop(client: TuxDmxMidiClient, bridge_id: str, device_name: str) -> None:
|
|
while True:
|
|
await asyncio.sleep(5)
|
|
await client.send_heartbeat(
|
|
{
|
|
"type": "heartbeat",
|
|
"protocol_version": 1,
|
|
"bridge_id": bridge_id,
|
|
"device": device_name,
|
|
}
|
|
)
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(description="TuxDMX MIDI bridge")
|
|
parser.add_argument("--config", default="config.yaml", help="Sti til YAML-konfiguration")
|
|
parser.add_argument("--list-devices", action="store_true", help="List tilgængelige MIDI-inputs")
|
|
parser.add_argument("--test-connection", action="store_true", help="Test forbindelse til TuxDMX")
|
|
parser.add_argument("--debug", action="store_true", help="Aktivér debuglog")
|
|
return parser
|
|
|
|
|
|
async def _async_main() -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
if args.list_devices:
|
|
for name in list_input_devices():
|
|
print(name)
|
|
return 0
|
|
|
|
config = load_config(args.config)
|
|
configure_logging("DEBUG" if args.debug else config.logging.level)
|
|
|
|
if args.test_connection:
|
|
client = TuxDmxMidiClient(config.server)
|
|
device_name = match_input_device(config.midi.device_name) or config.midi.device_name
|
|
result = await client.test_connection(config.bridge_id, device_name)
|
|
print(result)
|
|
return 0
|
|
|
|
await run_bridge(config, debug=args.debug)
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
return asyncio.run(_async_main())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|