@ -14,7 +14,7 @@ Erster Einsatz: OP-Liveübertragung (ATEM im OP, Regie im Saal, eine Netzwerklei
## Status
Planung abgeschlossen, Entwicklung startet mit Meilenstein 0. Siehe [docs/04-fahrplan.md](docs/04-fahrplan.md).
M0 abgenommen. M1 (Bild), M2 (Ton-Matrix) und M3 (Szenen und Fernsteuerung) umgesetzt, Langzeittests und Teile der Abnahmen offen. Siehe [docs/04-fahrplan.md](docs/04-fahrplan.md).
## Dokumente
@ -38,14 +38,32 @@ open Musubi.xcodeproj
```
Das `.xcodeproj` ist generiert und nicht im Repo. Nach Änderungen an `project.yml` erneut `xcodegen` ausführen.
Ohne NDI SDK in `Vendor/NDI/` bricht der Build mit einem Hinweis ab. Tests: im Schema `Musubi` (⌘U) oder `cd Packages/MusubiKit && swift test`– beides braucht das SDK, weil alle Module mitgebaut werden.
Signiert wird mit **Developer ID Application** (Team `XY4C2RG3L5`), Hardened Runtime an. Ohne dieses Zertifikat im Schlüsselbund `CODE_SIGN_IDENTITY`/`DEVELOPMENT_TEAM` in `project.yml` anpassen.
**Installieren immer nach `/Applications`** – in privaten Ordnern blockiert macOS Bonjour (Fernsteuerung).
### Startargumente (für Tests und Knoten ohne Bildschirm)
| Argument | Wirkung |
|---|---|
| `--route "<NDI-Name>=<Bildschirm-Index>"` | Quelle im Vollbild auf Bildschirm (0 = Hauptbildschirm) |
| `--audio-route "ndi:<NDI-Name>=<Gerät>:<Kanal>"` | NDI-Ton auf Ausgangskanal (Gerätename als Teilstring, Kanal ab 1) |
| `--audio-route "input:<Eingang>=<Gerät>:<Kanal>"` | Mikrofon (über die Sprechtaste) auf Ausgangskanal |
Ohne NDI SDK in `Vendor/NDI/` bricht der Build mit einem Hinweis ab. Tests (Swift Testing, 44 Stück): im Schema `Musubi` (⌘U) oder `cd Packages/MusubiKit && swift test`– beides braucht das SDK, weil alle Module mitgebaut werden.
- Drift-Regler langsam auslegen (Sekunden): der Füllstand ist durch die Blöcke ein Sägezahn. Im Betrieb ±150 ppm Korrektur, 0 Aussetzer in 2 min auf MOTU Out 2, CPU 0,3 %.
- Entfernte Routen werden 2 s auf dem Main Thread geparkt, damit der Render-Thread nie den letzten Verweis loslässt (sonst `free` im Callback).
- Sprechtaste global per Carbon-Hotkey (⌃⌥ Leertaste) – braucht keine Bedienungshilfen-Freigabe und meldet Drücken und Loslassen.
### M3 – Szenen und Fernsteuerung (umgesetzt 2026-10-21, Abnahme teilweise)
- Szene = Konfiguration der Regie + Konfiguration je Knoten. Bildschirme werden über den **Namen** erkannt (Position nur bei gleichnamigen Monitoren), weil `CGDirectDisplayID` je Rechner/Neustart wechselt.
- Musubi speichert den Live-Stand laufend (`~/Library/Application Support/Musubi/live-state.json`) und stellt ihn beim Start wieder her – nicht die zuletzt gespeicherte Szene. Ausgaben für fehlende Bildschirme bleiben vorgemerkt und starten, sobald der Bildschirm erscheint.
- Die Regie ist maßgeblich: Beim (Wieder-)Verbinden schickt sie ihre Soll-Belegung an den Knoten. Kennt sie den Knoten noch nicht, übernimmt sie dessen Stand.
- **Bonjour braucht die App in `/Applications`.** In privaten Ordnern (`~/Applications`, Build-Ordner) kann `mDNSResponder` die Info.plist nicht lesen und lehnt `NSBonjourServices` ab (`NoAuth -65555`). NDI ist nicht betroffen (eigenes Multicast).
- Direkt nach dem Start meldet macOS einige Sekunden „Local network prohibited“. Verbindungen brauchen daher Zeitgrenze + Wiederholung (6 s / 3 s).
- Signatur mit Developer ID + Hardened Runtime: Freigaben (lokales Netzwerk, Mikrofon) bleiben über Builds erhalten. Mikrofon braucht das Entitlement `com.apple.security.device.audio-input`.
- **Als Anmeldeobjekt startet macOS die App ohne Fenster.** Alles Wichtige startet deshalb in `applicationDidFinishLaunching`, nicht in einer SwiftUI-`.task`.
- Der Wechsel auf 50 Hz löst synchron „Bildschirme geändert“ aus. Ohne Schutz startete jede Ausgabe doppelt (zweites Fenster blieb nach „Beenden“ stehen). Ausgabe jetzt vor dem Moduswechsel eintragen, Abgleich nie verschachtelt.
- Offen: Beim ersten Neustart-Versuch hatte die Regie kurz vorher „keine Ausgaben“ für den Knoten gespeichert – Ursache nicht reproduzierbar (evtl. Klick auf „Ausgabe beenden“). Jede Änderung wird jetzt protokolliert (Kategorie `scene`).