Update docs and screenshots
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

This commit is contained in:
2026-07-25 10:26:29 +02:00
parent d6cda77aaf
commit 1f110866f5
170 changed files with 24657 additions and 3 deletions
@@ -0,0 +1,131 @@
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())