# Spike C — Core Audio Process Taps auf M5 Pro / macOS 26.6.1 Gemessen am 10.08.2026. Werkzeug: `Spikes/audiotap-probe.swift`, signiert mit dem Team-Zertifikat. Der Tap lief mit `muteBehavior = .unmuted` — der Spike hört nur zu und verändert die Wiedergabe nicht. ## Ergebnis: bestätigt | Schritt | Ergebnis | |---|---| | `kAudioHardwarePropertyProcessObjectList` | 35 Audio-Prozesse, mit PID, Bundle-ID und `IsRunningOutput` | | `AudioHardwareCreateProcessTap` | erzeugt, Tap-Objekt 195, Format 48 kHz / 2 Kanäle / 32 bit Float | | `AudioHardwareCreateAggregateDevice` (privat) | erzeugt, Device 196 | | IOProc über 5 s | 469 Callbacks (93/s), 480 256 Frames, Spitzenpegel 0,4516, **0 stille Puffer** | 480 256 Frames ÷ 2 Kanäle ÷ 5 s = 48 025 Hz — deckt sich exakt mit der Abtastrate. 93 Callbacks/s entsprechen ~512 Frames je Puffer, also rund 10,7 ms Latenz. Für einen Lautstärkemixer unkritisch. Der bekannte Fehlerfall „Puffer laufen nach einiger Zeit auf Null" trat im 5-Sekunden-Fenster nicht auf. Das widerlegt ihn nicht — der Watchdog aus dem Plan bleibt. **Kein TCC-Dialog erschienen.** Das ist noch kein Freibrief: `.unmuted` ist der harmlose Fall. Ob `.mutedWhenTapped` — der Modus, den der Mixer wirklich braucht — eine Berechtigung anfordert, muss in Phase 7 gegengeprüft werden. ## Fallstrick beim Bauen Die verbreitete Kurzform für Core-Audio-Property-Arrays ```swift var out = [T](repeating: unsafeBitCast(0 as Int, to: T.self), count: count) ``` **stürzt mit SIGTRAP ab**: `unsafeBitCast` verlangt identische Größen, `Int` hat 8 Byte, `AudioObjectID` 4. Da stdout in eine Pipe voll gepuffert ist, kam nicht einmal die erste Zeile Ausgabe — der Absturz sah aus wie ein stiller Fehlstart. `MetricsProvider` und `AudioProvider` verwenden deshalb die Variante über `UnsafeMutableRawPointer.allocate` mit `MemoryLayout.stride`. ## Konflikt: SoundSource ist bereits installiert Die Prozessliste zeigt `com.rogueamoeba.soundsource` und `com.rogueamoeba.arkaudiod`, dazu liegt `/Library/Audio/Plug-Ins/HAL/ARK.driver` im System. **SoundSource 6.1.1** ist Rogue Amoebas Per-App-Lautstärkemixer — funktional genau das, was Phase 7 bauen soll. Zwei Folgen: 1. **Doppelung.** Der Mixer aus Phase 7 wäre ein Nachbau einer bereits vorhandenen, ausgereiften und bezahlten App. 2. **Technisches Risiko.** ARK.driver ist ein HAL-Plugin, das Audio abfängt, bevor es das Ausgabegerät erreicht. Leitet SoundSource einen Prozess bereits um, sieht ein Onyx-Process-Tap auf denselben Prozess unter Umständen nichts mehr — oder beide Ebenen schalten sich gegenseitig stumm. Sauber vorhersagen lässt sich das nicht; es müsste gemessen werden. Vor Phase 7 zu klären. Bis dahin ist der Befund neutral festgehalten, nicht entschieden. ## Nebenbefund `Alcove.app` und `Sapphire.app` sind beide installiert — also die beiden Apps, die als Vorbild genannt wurden. Für die Notch-Shell in Phase 1 heißt das: mindestens eine davon greift denselben Bildschirmbereich ab. Für die Entwicklung sollten sie beendet sein, sonst überlagern sich Hover-Zonen und die Panels streiten sich um dieselbe Fläche. --- # Spike C2 + C3 — der Wiedergabeweg (13.08.2026) Spike C hat nur zugehört: `muteBehavior = .unmuted`, kein einziges Sample zurück ans Gerät. Genau der ungeprüfte Teil war der, der im Betrieb still blieb. Werkzeuge: `Spikes/mixer-path-probe.swift` und `Spikes/multi-tap-probe.swift`, Testquelle ein eigens gebautes App-Bundle mit 440-Hz-Dauerton (`/tmp/OnyxTone.app`). ## Der Weg selbst ist in Ordnung | Messung | Ergebnis | |---|---| | Tap `.unmuted`, ein Prozess | 469 Aufrufe/5 s, Spitze 0,366 | | Tap `.mutedWhenTapped` + Ausgabe über das Aggregate-Gerät | 469 Aufrufe/5 s, Spitze 0,366 | | Puffergeometrie | Eingang 1 Puffer / 2 Kanäle / 4096 Byte, Ausgang identisch | | Zwei Aggregate-Geräte gleichzeitig auf demselben Lautsprecher | beide 375+ Aufrufe, beide Spitze 0,366 | | Drei Aggregate-Geräte gleichzeitig | alle drei 375+ Aufrufe, alle Spitze 0,366 | Mehrere Geräte auf einem Ausgabegerät waren der Hauptverdacht — Chrome bringt mehrere Tonprozesse mit, also legt der Mixer mehrere Geräte an. **Der Verdacht ist widerlegt:** keines verhungert. ## Der Fund: der Mixer greift sich selbst ab Sobald der Mixer ein Programm bearbeitet, gibt **er** dessen Ton aus. Damit steht er selbst in `kAudioHardwarePropertyProcessObjectList` — und in seiner eigenen Programmliste. Eine Zeile wird geregelt, sobald man ihren Regler anfasst. Ein einziger Klick auf Onyx' eigene Zeile genügt also. Was dann passiert, ist gemessen: | Zustand | Spitze der Onyx-Ausgabe | |---|---| | Onyx bearbeitet nur den Testton | 0,388 | | Onyx bearbeitet zusätzlich sich selbst | **2,833** | Der Pegel schaukelt sich auf, weil Onyx seine eigene Ausgabe wieder abgreift und erneut ausgibt. Entscheidend ist aber die andere Hälfte: der Selbst-Tap läuft mit `.mutedWhenTapped` und schaltet damit **Onyx' gesamte Ausgabe** stumm — also die Wiedergabe *aller* bearbeiteten Programme. Ergebnis: völlige Stille, während die Pegelanzeigen weiter ausschlagen, weil die Taps ja Signal bekommen. Und der Zustand steht in den Einstellungen: er überlebt jeden Neustart. **Abhilfe** (`ProcessVisibility.isExcluded`): Onyx erscheint nicht in der eigenen Liste, lässt sich nicht auswählen, und ein gespeicherter Eintrag wird beim Laden entfernt und zurückgeschrieben. ## Nebenbefund Ein Tap sieht das Signal **vor** dem Stummschalten. Ein globaler Tap zeigt deshalb weiterhin den vollen Pegel eines bereits stummgeschalteten Programms — als Nachweis, ob das Stummschalten greift, taugt er nicht.