diff --git a/docs/re/audio-capture-channel-map-trap.md b/docs/re/audio-capture-channel-map-trap.md index 507f68d4..1e662ea7 100644 --- a/docs/re/audio-capture-channel-map-trap.md +++ b/docs/re/audio-capture-channel-map-trap.md @@ -191,13 +191,47 @@ the dropout schedule rather than anything musical. (Estimator controlled: it recovers a synthetic 37.487 s loop as **37.480 s**, and scores non-repeating noise at r = 0.019.) -### Why the monitor-sink route cannot fix this +### Why the sink starves — and 🔴 why "it cannot be fixed by configuration" was WRONG `parec` reads a sink **monitor**, which advances at wall-clock rate and -substitutes silence whenever nothing is written. So any moment the emulator runs -below real time becomes a hole in the file, and the timebase is warped -non-uniformly — deleting the silences does not repair it, it just compresses time -unevenly. +substitutes silence whenever nothing is written. **And every sink in this +container is a null sink**, because there is no audio hardware at all — no +`/proc/asound/cards`, no `/dev/snd`, no `/etc/asound.conf` — so PulseAudio's +stock `default.pa:109` `module-always-sink` supplies one, whose stated purpose is +to "make sure we always have a sink around, **even if it is a null sink**". A +null sink has no hardware clock: it is driven on a timer, and anything the client +fails to write in time becomes silence in the monitor. + +From that I concluded the route "cannot be fixed by configuration" and that only +an in-emulator tap would work. **That was wrong, and it was wrong because I +assumed the holes meant the guest was running below real time without testing the +alternative** — that the *client buffer* is simply too small. Xenia asks SDL for +`channel_samples_ = 256`, i.e. **5.33 ms** at 6 channels, and `daemon.conf` here +is stock with no fragment tuning at all. + +`PULSE_LATENCY_MSEC` overrides what SDL's PulseAudio backend requests. Measured, +same title, same sink, same `parec` invocation: + +| client buffer | duration | silence | **gaps/s** | median gap | +|---|---|---|---|---| +| xenia default (~5.3 ms) | 347.5 s | 39.3 % | **30.5** | 3.94 ms | +| `PULSE_LATENCY_MSEC=200` (114.7 ms reported) | 88.0 s | 15.6 % | **3.5** | 37.33 ms | + +**An 8.7× reduction in gap rate from one environment variable.** For scale, the +port's controls put a *starved* file at 32.9 gaps/s, a **genuine music bed at +3.3**, and a voice track that is 53 % pauses at 0.03, with their bar at 20 — +so the default lands squarely in "starved" and 200 ms lands at the level of real +content. + +⚠️ **Not yet a clean bill of health.** The two runs are not like-for-like: 88 s +against 347 s, and the short one covers the splash logos, where silence between +cards is real. What is established is the *direction and scale* — the dropouts +were substantially a **client-buffer** problem, not proof that the guest runs +below real time. + +✅ **Consequence: the in-emulator tap may not be needed.** The capture route is +worth retrying at a raised latency before anyone spends a session on a Canary +rebuild. ❔ **The route that would work is an internal tap**, and it does not exist yet. `SDLAudioDriver::SubmitFrame(float* frame)`