# Fahrplan Jeder Meilenstein endet mit einem Abnahmetest. Erst wenn er bestanden ist, beginnt der nächste. Termine sind offen. > **Absprache 2026-10-21:** Die Reihenfolge darf nach Absprache angepasst werden. Langzeittests (z. B. M1: 4 h) laufen gesammelt am Wochenende mit einem getaggten Build (`m1-candidate`); die Entwicklung von M2 beginnt vorher. | | Meilenstein | Ohne neue Hardware testbar? | |---|---|---| | M0 | Grundgerüst | ja | | M1 | Bild | ja (NDI Tools / OBS als Quelle) | | M2 | Ton-Matrix | ja (MOTU M2 vorhanden) | | M3 | Szenen und Fernsteuerung | ja (MacBook + Mac mini) | | M4 | Intercom Stufe 1: Hören + PGM-Vorschau | ja (Heimnetz) | | M5 | Intercom Stufe 2: Sprechen | ja (Heimnetz, HTTPS) | | M6 | Tally | ja (ATEM vorhanden) | | M7 | Generalprobe | nein – braucht ZowieBoxen, Switches, Dante, Ui12 | ## M0 – Grundgerüst - [x] Xcode-Projekt „Musubi“, macOS 14, Universal Build (arm64 + x86_64), Bundle-ID `studio.zielke.musubi` - [x] Modulstruktur laut `02-architektur.md` - [x] NDI SDK eingebunden (Modulemap), Version wird beim Start geloggt - [x] `design/Theme.swift` übernommen, leeres Hauptfenster mit Quellenliste - [x] Swift-Testing-Target mit erstem Test für `MusubiCore` - **Abnahme:** Release-Build startet auf MacBook **und** Mac mini 2018; die Quellenliste zeigt eine NDI-Testquelle aus dem Netz. ## M1 – Bild - [ ] NDI-Empfang einer Quelle (HX3 und volles NDI) – volles NDI ✓, HX3 offen bis ZowieBoxen da sind - [x] Vollbild auf gewähltem Bildschirm, ohne Menüleiste, Mauszeiger ausgeblendet - [x] Anzeigemodus 50 Hz setzen, wenn verfügbar - [x] Zwei Quellen gleichzeitig auf zwei Bildschirmen (Mac mini: PGM + Multiview) - [x] Ruhezustand blockieren, Auto-Reconnect - **Abnahme:** 4 Stunden, 2 Quellen, 2 Bildschirme auf dem Mac mini 2018 – keine sichtbaren Aussetzer, CPU-Last notiert, verlorene Frames < 0,1 %. ## M2 – Ton-Matrix - [x] NDI-Ton einer Quelle auf beliebige Ausgangskanäle (z. B. MOTU M2 Out 2) - [x] Reiner Audio-Empfang (ohne Video-Bandbreite) - [x] Mikrofon-Eingang (Headset) auf Kanal (Out 1), Sprechtaste (Tastenkürzel, gedrückt halten / umschalten) - [x] Pegelanzeige, Gain, Mute je Route - [x] Drift-Ausgleich zwischen NDI-Takt und Interface-Takt - **Abnahme:** 4 Stunden ohne Knacksen und ohne hörbaren Versatz; Sprechtaste reagiert ohne Verzögerung. ## M3 – Szenen und Fernsteuerung - [ ] Szenen speichern, laden, exportieren (JSON) - [ ] Rolle Regie/Knoten; Knoten per Bonjour finden - [ ] Regie setzt Szene am Knoten; Knoten meldet Status zurück - [ ] Beim Start letzte Szene laden - **Abnahme:** Beide Macs neu starten – die Szene „OP-Live“ stellt sich ohne Eingriff wieder her. ## M4 – Intercom Stufe 1 - [ ] Web-Server nur auf der Klinik-Schnittstelle - [ ] QR-Code mit Adresse in der App - [ ] Handy-Seite: Name/Rolle, Talkback hören, PGM-Vorschau (WebRTC) - **Abnahme:** iPhone und Android im Heimnetz, Verzögerung der Vorschau gemessen < 0,5 s. ## M5 – Intercom Stufe 2 - [ ] HTTPS mit eigener Stammzertifizierung (Installationsanleitung fürs Handy) - [ ] Sprechtaste auf dem Handy → Ton-Matrix - [ ] Optional: TURN-Relay als Plan B konfigurierbar (`06-netzwerk.md`) - **Abnahme:** Gegensprechen iPhone ↔ Regie und Android ↔ Regie im selben Durchgang. ## M6 – Tally - [ ] ATEM-Status (PGM/Vorschau) lesen - [ ] Kamera-Eingang einer Handy-Rolle zuordnen - [ ] Handy-Rand rot (PGM) / grün (Vorschau) - **Abnahme:** Schnitt am Micro Panel färbt das richtige Handy in unter 200 ms. ## M7 – Generalprobe - [ ] Kompletter Aufbau laut `05-testplan-hardware.md` - **Abnahme:** 3 Stunden durchgehender Betrieb mit allen Geräten; OBS + DistroAV bleibt als Notlösung auf beiden Macs installiert. ## Erkenntnisse _(nach jedem Meilenstein ergänzen)_ ### M0 – Grundgerüst (bestanden 2026-10-21) - Abnahme: Release-Build auf Mac Studio (Apple Silicon, Entwicklungsrechner) und Mac mini 2018 (i5-8500B, macOS 15.7.7) gestartet; beide zeigen „NDI Test Patterns“ in der Quellenliste. Das MacBook nutzt denselben arm64-Teil und wurde nicht separat geprüft. - Xcode 27 baut weiterhin x86_64 für macOS 14. - NDI SDK 6.3.2: `libndi.dylib` ist universal und hat `@rpath/libndi.dylib` als Install-Name; Einbetten nach `Contents/Frameworks` reicht. - Hardened Runtime + Ad-hoc-Signatur → dyld lehnt die eingebettete `libndi.dylib` ab („different Team IDs“). Vorerst aus; mit echter Team-ID für die Notarisierung wieder einschalten. - Mac mini im Leerlauf mit Quellensuche: ca. 2 % CPU, ca. 52 MB RAM. - Mac mini ist per SSH erreichbar (Entfernte Anmeldung + Bildschirmfreigabe): App per `scp` verteilen, Quarantäne entfernen, Logs mit `log stream` mitlesen. ### M1 – Bild (umgesetzt 2026-10-21, Abnahme offen) - „NDI Test Patterns“ sendet ein Standbild nur einmal. Eine gehaltene NDI-Verbindung zählt deshalb als lebendig; neu verbunden wird nur, wenn Frames **und** Verbindung fehlen. - Volles NDI 1080p50 mit bewegtem Bild: ~128 Mbit/s, ~50 % eines Kerns auf dem Mac mini – fast nur NDI-Empfang/Dekodierung. HX3 (ZowieBox) noch zu messen. - Verlorene Frames können vom **Sender** kommen: OBS auf dem Mac Studio ließ Frames aus, während dort Release-Builds liefen (~335 Frames in 6 min). Für Langzeittests darf auf dem Sender nichts Schweres laufen. - Mauszeiger über Ausgaben per `NSCursor.hide/unhide` (gezählt, ausbalanciert), nicht `setHiddenUntilMouseMoves`. ### M2 – Ton-Matrix (umgesetzt 2026-10-21, Langzeittest offen) - Core Audio direkt (AUHAL je Gerät) statt `AVAudioEngine`: einzelne Kanäle beschreiben, mehrere Quellen pro Kanal mischen, Render-Callback garantiert ohne Allokation. - Lock-freier Ringpuffer in C (`stdatomic`), weil Swifts `Atomic` erst ab macOS 15 verfügbar ist. - NDI liefert beim Verbinden einen Rückstau (~140 ms). Nach dem Vorfüllen wird einmalig auf die Ziel-Latenz gekürzt, sonst startet der Ton mit zu viel Verzögerung. - OBS/DistroAV sendet Audio in 1024er-Blöcken mit bis zu ~36 ms Lücke. 50 ms Puffer gaben vereinzelte Unterläufe → NDI-Routen 80 ms, Mikro-Routen 12 ms. - 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.