commit 86d1b4400018798b72f43bea2277774200ca5222 Author: Samuel Zielke Date: Wed Oct 21 10:15:55 2026 +0200 chore: project brief and docs diff --git a/.claude/commands/hardwaretest.md b/.claude/commands/hardwaretest.md new file mode 100644 index 0000000..42c0e9c --- /dev/null +++ b/.claude/commands/hardwaretest.md @@ -0,0 +1,2 @@ +Lies docs/05-testplan-hardware.md und docs/06-netzwerk.md. +Erstelle für den aktuellen Stand der App eine Checkliste, die ich im Testaufbau abarbeiten kann: welche Geräte, welche IP-Adressen, welche Szene, welche Messung (Latenz, CPU-Last, Aussetzer über Zeit). Ergänze fehlende Schritte direkt in docs/05-testplan-hardware.md. diff --git a/.claude/commands/meilenstein.md b/.claude/commands/meilenstein.md new file mode 100644 index 0000000..e270dab --- /dev/null +++ b/.claude/commands/meilenstein.md @@ -0,0 +1,6 @@ +Lies docs/04-fahrplan.md und finde den ersten Meilenstein, der noch nicht abgehakt ist. +1. Fasse Ziel und Abnahmetest in 3 Sätzen zusammen. +2. Mach einen Plan in 3–6 Schritten und warte auf mein OK. +3. Setze um, mit Tests für reine Logik. +4. Beschreibe am Ende genau, wie ich den Abnahmetest durchführe (Geräte, Klicks, worauf achten). +5. Hake erst ab, wenn ich den Test als bestanden bestätige. diff --git a/.claude/commands/status.md b/.claude/commands/status.md new file mode 100644 index 0000000..839fa57 --- /dev/null +++ b/.claude/commands/status.md @@ -0,0 +1 @@ +Gib mir einen kurzen Stand: welcher Meilenstein ist offen, was ist fertig, was ist ungetestet, welche offenen Fragen stehen in den docs. Maximal 10 Zeilen. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c58025 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# macOS +.DS_Store +# Xcode +build/ +DerivedData/ +*.xcuserstate +xcuserdata/ +*.xcscmblueprint +*.moved-aside +# SwiftPM +.build/ +.swiftpm/ +# NDI SDK – Lizenz: nicht ins Repo +Vendor/NDI/ +# Zertifikate und Schlüssel (Intercom HTTPS) +*.pem +*.key +*.p12 +Secrets/ +Vendor/ATEM/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..914d685 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,52 @@ +# CLAUDE.md – Musubi + +Du arbeitest an **Musubi**, einer nativen macOS-App von Zielke Studio für kleine Live-Übertragungen. Lies vor jeder Aufgabe die relevanten Dateien in `docs/`. Der Fahrplan in `docs/04-fahrplan.md` ist verbindlich: Ein Meilenstein gilt erst als fertig, wenn sein Abnahmetest bestanden ist. + +## Kontext in drei Sätzen + +Ein ATEM steht im OP, die Regie sitzt im Saal, dazwischen liegt eine einzige Netzwerkleitung. Zwei ZowieBoxen schicken Multiview und PGM als NDI|HX3 ins Netz. Musubi läuft auf dem MacBook (Regie) und dem Mac mini (Bühne), zeigt die NDI-Bilder auf Bildschirmen, legt Ton auf Kanäle des MOTU M2 und stellt später ein Intercom fürs Handy bereit. + +## Technische Leitplanken + +- **Plattform:** macOS, Deployment Target **macOS 14**, **Universal Build** (arm64 + x86_64). Der Mac mini 2018 (Intel, macOS 15 Sequoia) ist Zielgerät und muss jede Version flüssig ausführen. +- **Sprache:** Swift 6, SwiftUI für die Oberfläche, AppKit wo nötig (Vollbildfenster auf bestimmten `NSScreen`, Anzeigemodi). +- **Abhängigkeiten:** Keine Drittanbieter-Pakete ohne Rückfrage. Erlaubt ohne Rückfrage: Apple-Frameworks, das NDI SDK (C-Bibliothek, via Modulemap/Bridging), später das Blackmagic ATEM SDK. +- **NDI SDK:** liegt lokal in `Vendor/NDI/` und wird nie eingecheckt. Fehlt es, brich nicht ab, sondern erkläre, was zu tun ist (`Vendor/README.md`). +- **Module** (lokale Swift Packages oder Gruppen, siehe `docs/02-architektur.md`): + - `MusubiCore` – Modelle, Szenen, Einstellungen (reine Logik, testbar) + - `NDIKit` – dünner Swift-Wrapper ums NDI SDK (Finder, Receiver, Audio-only) + - `VideoOutput` – Darstellung per Metal bzw. `AVSampleBufferDisplayLayer`, Vollbild je Bildschirm, 50 Hz + - `AudioMatrix` – Core Audio / `AVAudioEngine`, Quelle → Ausgangskanal + - `Remote` – Regie steuert Knoten per Bonjour (ab Meilenstein 3) + - `Intercom` – HTTPS + WebRTC, nur an die Klinik-Schnittstelle gebunden (ab Meilenstein 4) + - `Tally` – ATEM-Status (ab Meilenstein 6) +- **Echtzeit:** Nie Video- oder Audioarbeit auf dem Main Thread. Keine Allokationen im Audio-Render-Callback. +- **Betriebssicherheit:** Ruhezustand per `IOPMAssertion` verhindern, solange eine Szene läuft. Bei Verbindungsabbruch automatisch neu verbinden. Beim Start letzte Szene laden. +- **Netze trennen:** Technik-Netz und Klinik-Netz werden nie verbunden. Dienste binden explizit an eine Schnittstelle. Keine Internetfreigabe. + +## Design + +Halte dich an `docs/03-design.md` und `design/Theme.swift`. Kurz: dunkle Regie-Oberfläche, Indigo als einzige Markenfarbe, Rot/Grün ausschließlich für Tally (PGM/Vorschau), SF Pro in der App, SF Mono für Zahlen. Texte auf Deutsch, in Du-Form, kurz, im Stil von apps.zielke.studio („NDI rein. Bild, Ton und Stimme raus.“). + +## Arbeitsweise + +1. Vor Code: kurzer Plan in 3–6 Punkten, dann umsetzen. +2. Kleine Schritte, nach jedem lauffähigen Stand committen (Conventional Commits auf Englisch). +3. Reine Logik (Szenen, Kanalzuordnung, Parser) bekommt Unit-Tests mit Swift Testing. +4. Was sich nur mit Hardware prüfen lässt, beschreibst du als Schritt in `docs/05-testplan-hardware.md`, statt es zu behaupten. +5. Nach jedem Meilenstein: Checkbox in `docs/04-fahrplan.md` abhaken, kurze Notiz unter „Erkenntnisse“ ergänzen. +6. Code und Kommentare auf Englisch, UI-Texte auf Deutsch über einen String Catalog. +7. Wenn etwas unklar ist (Lizenz, Netz der Klinik, Hardware-Verhalten): fragen, nicht raten. + +## Testen ohne Hardware + +- **NDI-Quellen:** NDI Tools (Test Patterns, Screen Capture) auf einem zweiten Mac oder OBS mit DistroAV als NDI-Ausgang. +- **Audio:** MOTU M2 ist vorhanden; ohne M2 das eingebaute Ausgabegerät mit zwei Kanälen nutzen. +- **ATEM:** vorhanden, für Tally ab Meilenstein 6. +- **Hardware im Zulauf:** 2× ZowieBox, 2× TP-Link TL-SG2008P, MT-VIKI Dante-Adapter, Soundcraft Ui12. + +## Nicht tun + +- Kein Senden/Encoding von NDI, keine Aufnahme, keine Bildmischung (macht der ATEM). +- Keine Cloud-Dienste. Ein Relay (TURN) ist nur Plan B und läuft auf dem eigenen Server. +- Keine Funktionen eines späteren Meilensteins vorziehen. diff --git a/README.md b/README.md new file mode 100644 index 0000000..41d82f5 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Musubi + +**NDI rein. Bild, Ton und Stimme raus.** + +Musubi (結び, japanisch: das Knüpfen, Verbinden) ist eine macOS-App von Zielke Studio für kleine Live-Übertragungen. Sie macht MacBook und Mac mini zu den fehlenden Bausteinen einer Regie: + +- NDI-Quellen im Vollbild auf beliebige Bildschirme +- NDI-Ton (auch ohne Bild) gezielt auf einzelne Kanäle eines Audio-Interfaces +- Talkback per Sprechtaste +- Intercom, PGM-Vorschau und Tally fürs Handy im Browser +- Szenen, die Regie-Mac und Bühnen-Mac auf einen Klick einrichten + +Erster Einsatz: OP-Liveübertragung (ATEM im OP, Regie im Saal, eine Netzwerkleitung dazwischen). + +## Status + +Planung abgeschlossen, Entwicklung startet mit Meilenstein 0. Siehe [docs/04-fahrplan.md](docs/04-fahrplan.md). + +## Dokumente + +| Datei | Inhalt | +|---|---| +| [CLAUDE.md](CLAUDE.md) | Arbeitsanweisungen für Claude Code | +| [START.md](START.md) | Startbefehl für Claude Code | +| [docs/01-konzept.md](docs/01-konzept.md) | Idee, Rollen, Funktionsumfang | +| [docs/02-architektur.md](docs/02-architektur.md) | Module, Frameworks, Datenflüsse | +| [docs/03-design.md](docs/03-design.md) | Designsystem, Farben, Typografie, Texte | +| [docs/04-fahrplan.md](docs/04-fahrplan.md) | Meilensteine mit Abnahmetests | +| [docs/05-testplan-hardware.md](docs/05-testplan-hardware.md) | Tests, sobald die Hardware da ist | +| [docs/06-netzwerk.md](docs/06-netzwerk.md) | IP-Plan, Schnittstellen, Relay als Plan B | + +## Voraussetzungen + +- Xcode (aktuell), macOS auf Apple Silicon zum Entwickeln +- NDI SDK für macOS (nicht im Repo, siehe [Vendor/README.md](Vendor/README.md)) +- Zielgeräte: MacBook (Apple Silicon) und Mac mini 2018 (Intel, macOS 15) → Universal Build + +© Zielke Studio diff --git a/START.md b/START.md new file mode 100644 index 0000000..1d062a3 --- /dev/null +++ b/START.md @@ -0,0 +1,21 @@ +# Start mit Claude Code + +## Einmalig vorbereiten + +```bash +cd ~/Developer/musubi # dein Repo +git init && git add . && git commit -m "chore: project brief and docs" +# NDI SDK installieren und nach Vendor/NDI/ kopieren (siehe Vendor/README.md) +``` + +## Startbefehl + +```bash +claude "Lies CLAUDE.md und alle Dateien in docs/. Fasse in 5 Sätzen zusammen, was Musubi ist und was Meilenstein 0 verlangt. Mach dann einen Plan für Meilenstein 0 (Xcode-Projekt als Universal Build für macOS 14, Modulstruktur laut docs/02-architektur.md, NDI SDK einbinden, Designsystem aus design/Theme.swift, leeres Hauptfenster mit Quellenliste). Warte auf mein OK, bevor du Dateien anlegst." +``` + +## Danach + +- Nächster Meilenstein: `/meilenstein` (Slash-Command in `.claude/commands/`) +- Hardware-Test vorbereiten: `/hardwaretest` +- Stand prüfen: `/status` diff --git a/Vendor/README.md b/Vendor/README.md new file mode 100644 index 0000000..f7996d2 --- /dev/null +++ b/Vendor/README.md @@ -0,0 +1,21 @@ +# Vendor + +## NDI SDK + +Das NDI SDK wird **nicht** eingecheckt (Lizenzbedingungen). + +1. NDI SDK für macOS von ndi.video herunterladen und installieren. +2. Header und `libndi` nach `Vendor/NDI/` kopieren: + ``` + Vendor/NDI/include/Processing.NDI.Lib.h (+ übrige Header) + Vendor/NDI/lib/libndi.dylib (Universal: arm64 + x86_64) + ``` +3. Prüfen, dass die Bibliothek beide Architekturen enthält: + ``` + lipo -info Vendor/NDI/lib/libndi.dylib + ``` +4. Lizenz- und Namensregeln (NDI®-Kennzeichnung, Weitergabe) vor jedem Verkauf prüfen. + +## Blackmagic ATEM Switchers SDK (ab Meilenstein 6) + +Aus dem Blackmagic Support Center laden (Developer SDK zu ATEM Switchers). Ablage in `Vendor/ATEM/`, nicht einchecken, falls die Lizenz es verlangt. diff --git a/design/Theme.swift b/design/Theme.swift new file mode 100644 index 0000000..42bdac0 --- /dev/null +++ b/design/Theme.swift @@ -0,0 +1,49 @@ +import SwiftUI + +/// Musubi design tokens. Source of truth: design/tokens.json and docs/03-design.md. +enum Theme { + enum Color { + static let sumi = adaptive(dark: 0x14151B, light: 0xF4F4F6) // background + static let kasumi = adaptive(dark: 0x1E2029, light: 0xFFFFFF) // surface + static let usuzumi = adaptive(dark: 0x2B2E3A, light: 0xDADBE2) // lines + static let washi = adaptive(dark: 0xECE9E2, light: 0x16171D) // text + static let nezumi = adaptive(dark: 0x8E8C96, light: 0x6A6974) // muted text + static let ai = adaptive(dark: 0x8A99F2, light: 0x4F63D9) // the one brand accent + + // Status colours – only for states, never decoration. + static let shu = SwiftUI.Color(hex: 0xE5484D) // tally PGM, talk active + static let moegi = SwiftUI.Color(hex: 0x3DBE7A) // tally preview, connection ok + static let kohaku = SwiftUI.Color(hex: 0xE8A23A) // warning + } + + enum Radius { static let card: CGFloat = 12, button: CGFloat = 8, chip: CGFloat = 6 } + enum Space { static let unit: CGFloat = 8, card: CGFloat = 16 } + enum Stroke { static let line: CGFloat = 1, cord: CGFloat = 2, knot: CGFloat = 6 } + + enum Font { + static let title = SwiftUI.Font.system(.title3, weight: .semibold) + static let body = SwiftUI.Font.system(.body) + static let caption = SwiftUI.Font.system(.caption) + static let number = SwiftUI.Font.system(.body, design: .monospaced).monospacedDigit() + } + + private static func adaptive(dark: UInt32, light: UInt32) -> SwiftUI.Color { + SwiftUI.Color(nsColor: NSColor(name: nil) { appearance in + appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua + ? NSColor(hex: dark) : NSColor(hex: light) + }) + } +} + +extension SwiftUI.Color { + init(hex: UInt32) { self.init(nsColor: NSColor(hex: hex)) } +} + +extension NSColor { + convenience init(hex: UInt32) { + self.init(srgbRed: CGFloat((hex >> 16) & 0xFF) / 255, + green: CGFloat((hex >> 8) & 0xFF) / 255, + blue: CGFloat(hex & 0xFF) / 255, + alpha: 1) + } +} diff --git a/design/tokens.json b/design/tokens.json new file mode 100644 index 0000000..980ced8 --- /dev/null +++ b/design/tokens.json @@ -0,0 +1,21 @@ +{ + "name": "Musubi", + "color": { + "sumi": { "dark": "#14151B", "light": "#F4F4F6", "role": "background" }, + "kasumi": { "dark": "#1E2029", "light": "#FFFFFF", "role": "surface" }, + "usuzumi": { "dark": "#2B2E3A", "light": "#DADBE2", "role": "line" }, + "washi": { "dark": "#ECE9E2", "light": "#16171D", "role": "text" }, + "nezumi": { "dark": "#8E8C96", "light": "#6A6974", "role": "textMuted" }, + "ai": { "dark": "#8A99F2", "light": "#4F63D9", "role": "accent" }, + "shu": { "value": "#E5484D", "role": "statusLive" }, + "moegi": { "value": "#3DBE7A", "role": "statusPreview" }, + "kohaku": { "value": "#E8A23A", "role": "statusWarning" } + }, + "radius": { "card": 12, "button": 8, "chip": 6 }, + "spacing": { "unit": 8, "cardPadding": 16 }, + "stroke": { "line": 1, "cord": 2, "knot": 6 }, + "type": { + "app": { "ui": "SF Pro", "numbers": "SF Mono" }, + "brand": { "heading": "Kanit 600", "body": "Kanit 400", "highlight": "Instrument Serif Italic" } + } +} diff --git a/docs/01-konzept.md b/docs/01-konzept.md new file mode 100644 index 0000000..2841325 --- /dev/null +++ b/docs/01-konzept.md @@ -0,0 +1,44 @@ +# Konzept + +Musubi macht MacBook und Mac mini zu den fehlenden Bausteinen einer kleinen Übertragungsregie: NDI-Bilder auf Bildschirme, NDI-Ton auf Kanäle eines Audio-Interfaces, Talkback und ein Intercom fürs Handy – in einer App mit gespeicherten Szenen. + +## Problem + +Dieselbe Aufgabe braucht heute mehrere Programme: NDI Tools oder OBS fürs Bild, Loopback oder BlackHole fürs Audio-Routing, und fürs Intercom gibt es nichts Passendes. Im OP ist klassisches Intercom wegen Kabeln, Sterilität und Funkfreigabe besonders schwierig. + +## Rollen + +| Rolle | Gerät | Aufgaben | +|---|---|---| +| Regie | MacBook am Regieplatz | Multiview anzeigen, PGM-Ton und Talkback auf MOTU-Kanäle legen, Intercom-Server, Tally aus dem ATEM, Szenen laden, Knoten steuern | +| Knoten | Mac mini (2018, Intel) auf der Bühne | PGM auf den Saal-Screen, Multiview auf den Moderator-Monitor; folgt den Szenen der Regie | +| Gast | Handy im OP (Browser) | Talkback hören, per Taste sprechen, PGM-Vorschau, Tally der eigenen Kamera | + +Regie und Knoten finden sich per Bonjour im Technik-Netz. Fällt die Regie aus, zeigt der Knoten seine letzte Szene weiter. + +## Funktionen Version 1 + +- **Quellen:** NDI-Quellen automatisch finden, Liste mit kleiner Vorschau. +- **Bildausgabe:** Quelle → Bildschirm, Vollbild ohne Menüleiste, Anzeige auf 50 Hz. +- **Ton-Matrix:** NDI-Ton (auch reiner Audio-Empfang) und Mikrofone auf einzelne Ausgangskanäle, z. B. MOTU M2 Out 1/2; Pegel, Gain, Mute. +- **Talkback:** Headset-Mikro per Sprechtaste auf einen Kanal. +- **Szenen:** komplette Belegung speichern, laden, exportieren („OP-Live“). +- **Betriebssicherheit:** kein Ruhezustand, Auto-Reconnect, letzte Szene beim Start. +- **Status:** Verbindung, verlorene Frames, Pegel je Quelle. +- **Fernsteuerung:** Regie belegt den Knoten. + +## Intercom und Tally fürs Handy + +Das MacBook stellt über seine zweite Netzwerkschnittstelle (Klinik-Netz bzw. eigenes Netz der IT) eine Web-Seite bereit. Keine App-Installation auf dem Handy. + +- **Verbinden:** QR-Code scannen, Name und Rolle wählen („Kamera 2, Handkamera“). +- **Hören:** Talkback der Regie, wahlweise Moderator. +- **Sprechen:** große Sprechtaste; Ton landet in der Ton-Matrix. +- **PGM-Vorschau:** Ziel ca. 640 × 360, 25 fps, unter 0,5 s Verzögerung. +- **Tally:** Rand rot bei PGM, grün bei Vorschau. + +Reihenfolge der Netzwege: 1. eigenes Netz der Klinik-IT (lokal, schnellster Weg), 2. Klinik-WLAN, 3. **Plan B: eigener TURN-Relay** (siehe `06-netzwerk.md`). + +## Nicht in Version 1 + +Senden/Encoding von NDI, Aufnahme, Bildmischung, Cloud-Dienste. diff --git a/docs/02-architektur.md b/docs/02-architektur.md new file mode 100644 index 0000000..591d349 --- /dev/null +++ b/docs/02-architektur.md @@ -0,0 +1,51 @@ +# Architektur + +## Überblick + +``` +Technik-Netz Musubi (MacBook, Regie) Ausgänge +────────────── ─────────────────────────── ───────────────────── +ZowieBox MV ─NDI|HX3─▶ NDIKit ──▶ VideoOutput ───────────────▶ Regie-Bildschirm +ZowieBox PGM ─NDI|HX3─▶ │ + └──▶ AudioMatrix ──────────────────▶ MOTU M2 Out 1/2 → Ui12 +Headset-Mikro ─────────────────▶ AudioMatrix +ATEM ─────────Status───▶ Tally ──▶ Intercom ◀──WebRTC──▶ Handy im OP (Klinik-Netz) + MusubiCore (Szenen) ─Bonjour─▶ Mac mini (Knoten, Bühne) +``` + +Der Mac mini empfängt NDI selbst direkt aus dem Technik-Netz und wird von der Regie nur gesteuert. + +## Module + +| Modul | Aufgabe | Technik | +|---|---|---| +| MusubiCore | Modelle (Quelle, Ausgang, Route, Szene), Persistenz, Einstellungen | Swift, Codable/JSON, Swift Testing | +| NDIKit | Quellen finden, empfangen (Video+Audio oder nur Audio), Statistik | NDI SDK (C), `NDIlib_find_*`, `NDIlib_recv_*`, Bandbreite `audio_only` für reinen Ton | +| VideoOutput | Frames darstellen, ein Vollbildfenster je `NSScreen`, Anzeigemodus 50 Hz | Metal oder `AVSampleBufferDisplayLayer`, `CGDisplayCopyAllDisplayModes` | +| AudioMatrix | Quellen → Ausgangskanäle, Gain, Mute, Pegel, Sprechtaste | Core Audio / `AVAudioEngine`, Kanalzuordnung (`channelMap`) | +| Remote | Regie ↔ Knoten: Szene setzen, Status melden | Bonjour (`NWListener`/`NWBrowser`), JSON-Nachrichten | +| Intercom | Web-Seite, Signalisierung, Audio hin/zurück, PGM-Vorschau | eingebetteter HTTPS-Server, WebRTC (Opus, H.264 via VideoToolbox), Bindung an Schnittstelle | +| Tally | PGM/Vorschau-Eingänge des ATEM lesen | Blackmagic ATEM Switchers SDK | + +## Datenmodell (Entwurf) + +```swift +struct Source: Codable, Hashable { var ndiName: String; var label: String } +enum Output: Codable, Hashable { case screen(displayID: UInt32), audioChannel(deviceUID: String, channel: Int) } +struct Route: Codable, Hashable { var source: Source; var output: Output; var mode: Mode + enum Mode: String, Codable { case videoAndAudio, videoOnly, audioOnly } } +struct Scene: Codable, Identifiable { var id: UUID; var name: String; var role: Role; var routes: [Route] } +enum Role: String, Codable { case control, node } +``` + +## Threads + +- NDI-Empfang je Quelle auf eigener Queue. +- Video: Frames ohne Kopie an die Darstellung (CVPixelBuffer), Rendern im Takt des Displays. +- Audio: Ringpuffer zwischen NDI-Empfang und Render-Callback, keine Allokationen im Callback, Drift-Ausgleich (Resampling) zwischen NDI-Takt und Interface-Takt. + +## Netzwerk-Bindung + +- NDI, Bonjour, Remote: nur Technik-Schnittstelle. +- Intercom: nur Klinik-Schnittstelle (oder TURN-Relay nach außen). +- Schnittstellen in den Einstellungen per Name wählbar (z. B. `en7` Technik, `en0` Klinik). diff --git a/docs/03-design.md b/docs/03-design.md new file mode 100644 index 0000000..97996eb --- /dev/null +++ b/docs/03-design.md @@ -0,0 +1,73 @@ +# Design + +Musubi gehört zur Familie von apps.zielke.studio (Patchcord, drop.two, drop.look): gleiche Typografie, gleiche Tonalität, gleiche Zurückhaltung – aber eine **eigene Farbwelt**. Die Farben kommen aus dem japanischen Färbehandwerk (Indigo/Ai-zome), passend zum Namen. + +> Hinweis: Die Farben der anderen Apps ließen sich von der Website nicht automatisch auslesen. Vor dem Festlegen einmal nebeneinanderlegen und prüfen, dass Indigo sich klar abhebt. + +## Leitidee + +Ein Knoten verbindet Fäden. In Musubi werden Quellen und Ausgänge **geknüpft**: Die zentrale Ansicht zeigt links die Quellen, rechts Bildschirme und Kanäle, dazwischen Schnüre. Eine aktive Verbindung ist eine Indigo-Schnur mit kleinem Knoten an der Stelle, wo sie anliegt. Das ist das eine wiedererkennbare Element; alles andere bleibt ruhig. + +## Farben + +Regie-Räume sind dunkel. **Dunkel ist Standard**, hell ist unterstützt. + +| Name | Rolle | Dunkel | Hell | +|---|---|---|---| +| Sumi (Tusche) | Hintergrund | `#14151B` | `#F4F4F6` | +| Kasumi (Dunst) | Flächen, Karten | `#1E2029` | `#FFFFFF` | +| Usuzumi (verdünnte Tusche) | Linien, Rahmen, gehobene Flächen | `#2B2E3A` | `#DADBE2` | +| Washi (Papier) | Text | `#ECE9E2` | `#16171D` | +| Nezumi (Maus­grau) | gedämpfter Text, Beschriftungen | `#8E8C96` | `#6A6974` | +| **Ai (Indigo)** | **einzige Markenfarbe**: aktive Schnüre, Auswahl, Hauptaktion | `#8A99F2` | `#4F63D9` | + +### Signalfarben – nur für Zustände, nie als Deko + +| Name | Bedeutung | Wert | +|---|---|---| +| Shu (Zinnober) | Tally PGM / „auf Sendung“, Sprechtaste aktiv | `#E5484D` | +| Moegi (Frühlingsgrün) | Tally Vorschau, Verbindung gut | `#3DBE7A` | +| Kohaku (Bernstein) | Warnung: Frames verloren, Pegel nah an 0 dBFS | `#E8A23A` | + +Rot und Grün tauchen ausschließlich in diesen Bedeutungen auf. So bleibt Tally auf einen Blick lesbar. + +## Typografie + +| Einsatz | Schrift | +|---|---| +| App-Oberfläche | SF Pro (System) – Titel 600, Text 400 | +| Zahlen: Pegel, IP-Adressen, Latenz, Frames | SF Mono, tabellarische Ziffern | +| Website, Onboarding, App-Store-Bilder | Kanit 600 für Überschriften, Kanit 400 für Text | +| Ein hervorgehobenes Wort pro Überschrift | Instrument Serif *italic*, gleiche Größe wie Kanit | + +Beispiel-Hero (Website): „Ein Kabel. Alle *Signale*.“ + +## Formen und Abstände + +- Abstände in 8er-Schritten, innerhalb von Karten 16. +- Ecken: Karten 12, Schaltflächen 8, Chips 6. +- Linien 1 pt in Usuzumi. Keine Schatten, keine Verläufe. +- Schnüre in der Knoten-Ansicht: 2 pt, inaktiv Usuzumi, aktiv Ai; Knoten = gefüllter Kreis 6 pt. + +## Texte + +Stil wie auf apps.zielke.studio: kurz, konkret, Du-Form. + +- Untertitel: **„NDI rein. Bild, Ton und Stimme raus.“** +- Begriffe durchgängig: *Quelle, Bildschirm, Kanal, Szene, Regie, Knoten, Sprechtaste, Vorschau, Sendung*. +- Schaltflächen sagen, was passiert: „Szene laden“, „Mit Knoten verbinden“, „Sprechen“. +- Fehler erklären Ursache und Ausweg: + - „Keine NDI-Quellen gefunden. Prüfe, ob das Technik-Netz an `en7` hängt.“ + - „ZowieBox PGM antwortet nicht mehr. Neuer Versuch in 3 s.“ +- Leere Zustände laden zum Handeln ein: „Noch keine Szene. Zieh eine Quelle auf einen Bildschirm.“ + +## Bildschirme (Version 1) + +1. **Knoten-Ansicht** (Hauptfenster): Quellen links, Ausgänge rechts, Schnüre dazwischen; oben Szenenname, rechts oben Status (Netz, Knoten verbunden, Ruhezustand blockiert). +2. **Ausgabe-Fenster:** randloses Vollbild je Bildschirm; optional dezente Statuszeile nur für die Regie. +3. **Ton:** Kanalzüge je Ausgangskanal mit Pegel, Gain, Mute; Sprechtaste groß unten rechts. +4. **Einstellungen:** Schnittstellen (Technik/Klinik), Audio-Gerät, Startverhalten. + +## App-Icon (Idee) + +Eine Indigo-Schnur, die einen einfachen Knoten um ein kleines, abgerundetes Bildschirm-Rechteck bildet, auf Sumi-Grund. Kein Text. diff --git a/docs/04-fahrplan.md b/docs/04-fahrplan.md new file mode 100644 index 0000000..02251f6 --- /dev/null +++ b/docs/04-fahrplan.md @@ -0,0 +1,70 @@ +# Fahrplan + +Jeder Meilenstein endet mit einem Abnahmetest. Erst wenn er bestanden ist, beginnt der nächste. Termine sind offen. + +| | 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 +- [ ] Xcode-Projekt „Musubi“, macOS 14, Universal Build (arm64 + x86_64), Bundle-ID `studio.zielke.musubi` +- [ ] Modulstruktur laut `02-architektur.md` +- [ ] NDI SDK eingebunden (Modulemap), Version wird beim Start geloggt +- [ ] `design/Theme.swift` übernommen, leeres Hauptfenster mit Quellenliste +- [ ] 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) +- [ ] Vollbild auf gewähltem Bildschirm, ohne Menüleiste, Mauszeiger ausgeblendet +- [ ] Anzeigemodus 50 Hz setzen, wenn verfügbar +- [ ] Zwei Quellen gleichzeitig auf zwei Bildschirmen (Mac mini: PGM + Multiview) +- [ ] 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 +- [ ] NDI-Ton einer Quelle auf beliebige Ausgangskanäle (z. B. MOTU M2 Out 2) +- [ ] Reiner Audio-Empfang (ohne Video-Bandbreite) +- [ ] Mikrofon-Eingang (Headset) auf Kanal (Out 1), Sprechtaste (Tastenkürzel, gedrückt halten / umschalten) +- [ ] Pegelanzeige, Gain, Mute je Route +- [ ] 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)_ diff --git a/docs/05-testplan-hardware.md b/docs/05-testplan-hardware.md new file mode 100644 index 0000000..36a4fea --- /dev/null +++ b/docs/05-testplan-hardware.md @@ -0,0 +1,37 @@ +# Testplan Hardware + +Sobald die bestellte Hardware da ist, wird der echte Aufbau zu Hause nachgestellt – zuerst ohne Musubi (Basis), dann mit. + +## Geräte + +| Gerät | Status | +|---|---| +| ATEM Mini Extreme ISO G2 + Micro Panel | vorhanden | +| MacBook (Apple Silicon), Mac mini 2018 (Intel) | vorhanden | +| MOTU M2 | vorhanden | +| 2× ZowieBox (NDI|HX3) | bestellt | +| 2× TP-Link TL-SG2008P | bestellt | +| MT-VIKI Dante-Adapter (Ein- und Ausgang) | bestellt | +| Soundcraft Ui12 | bestellt | +| langes Netzwerkkabel (Ersatz für die Klinikleitung) | besorgen | + +## Phase A – Basis ohne Musubi + +- [ ] Switches: EEE aus, QoS DSCP, IGMP Snooping an, feste IPs, Konfig auf beide übertragen +- [ ] `iperf3` zwischen den Switches: stabil ~940 Mbit/s +- [ ] ZowieBoxen: 1080p50, NDI|HX3, Audioquelle HDMI; MV-Stream trägt PGM-Ton +- [ ] NDI Tools: beide Streams auf MacBook und Mac mini sichtbar +- [ ] Dante Controller: MT-VIKI erkannt, Pegel einstellbar, PoE, nach Stromausfall von selbst wieder da +- [ ] Ui12 im Browser über das Technik-Netz erreichbar + +## Phase B – mit Musubi (je nach Meilenstein) + +- [ ] M1: Mac mini zeigt PGM + Multiview 4 h ohne Aussetzer; CPU-Last: ___ % +- [ ] M1: Latenz Multiview messen (Uhr mit Millisekunden vor Kamera → ATEM → ZowieBox → Musubi): ___ ms +- [ ] M2: PGM-Ton über M2 Out 2 → Ui12 Kanal 4; Talkback M2 Out 1 → Ui12 Kanal 3 (nur Aux 2) +- [ ] M3: Szene „OP-Live“ übersteht Neustart beider Macs +- [ ] M4/M5: Handy über zweite Schnittstelle: Hören, Sprechen, Vorschau +- [ ] M6: Tally auf dem Handy folgt dem Micro Panel + +## Messwerte +_(hier eintragen)_ diff --git a/docs/06-netzwerk.md b/docs/06-netzwerk.md new file mode 100644 index 0000000..4869ba0 --- /dev/null +++ b/docs/06-netzwerk.md @@ -0,0 +1,41 @@ +# Netzwerk + +## Zwei Netze, strikt getrennt + +| Netz | Schnittstelle MacBook | Dienste | +|---|---|---| +| Technik-Netz (eigene Switches, 1:1-Strecke) | USB-C-Ethernet-Adapter, z. B. `en7` | NDI, Dante, ATEM-Steuerung, Ui12, Bonjour (Musubi Remote) | +| Klinik-Netz bzw. eigenes Netz der IT | WLAN oder zweiter Adapter, z. B. `en0` | nur Musubi Intercom (HTTPS/WebRTC) | + +Keine Internetfreigabe, kein Routing zwischen den Netzen. Musubi bindet jeden Dienst explizit an eine Schnittstelle. + +## IP-Plan Technik-Netz (Vorschlag) + +| Adresse | Gerät | +|---|---| +| 192.168.10.2 | Switch OP | +| 192.168.10.3 | Switch Saal | +| 192.168.10.10 | ATEM Mini Extreme ISO G2 | +| 192.168.10.21 | ZowieBox Multiview | +| 192.168.10.22 | ZowieBox PGM | +| 192.168.10.30 | Soundcraft Ui12 | +| 192.168.10.40 | Mac mini (Knoten) | +| 192.168.10.50 | MacBook (Regie) | +| automatisch | Dante-Adapter (Link-Local, eigene Erkennung) | + +Maske 255.255.255.0, kein Gateway. + +## Intercom: Reihenfolge der Wege + +1. **Eigenes Netz der Klinik-IT** – lokal, schnellster Weg. Bevorzugt. +2. **Klinik-WLAN** – nur wenn Geräte sich dort gegenseitig erreichen dürfen (keine Client-Isolation). +3. **Plan B: TURN-Relay auf eigenem Server** + - coturn im Homelab, erreichbar über TLS auf Port 443 (kommt durch fast jede Firewall) + - Handy und MacBook verbinden sich beide nach außen; WebRTC nutzt das Relay nur, wenn kein direkter Weg geht + - Zusätzliche Verzögerung grob 40–80 ms innerhalb Deutschlands + - Medien bleiben Ende-zu-Ende verschlüsselt (DTLS-SRTP); **kein SFU**, das die Medien entschlüsseln würde + - OP-Bild verlässt damit das Klinik-Netz → vorher mit Klinik und Datenschutz abstimmen + +## Bandbreite + +NDI|HX3 1080p: ca. 30–80 Mbit/s je Abruf. Jeder Empfänger bekommt eine eigene Kopie. Planungsgrenze: 70 % der Gigabit-Strecke pro Richtung.