re: the capture dropouts are largely a client-buffer size, not the guest running slow

Withdraws my conclusion that the monitor-sink capture route cannot be fixed by
configuration, and with it the claim that only an in-emulator tap would work.

The container has no audio hardware -- no /proc/asound/cards, no /dev/snd, no
asound.conf -- so PulseAudio s stock default.pa module-always-sink supplies a
null sink, whose whole purpose is to exist when there is no device. A null sink
has no hardware clock: it is timer-driven, and anything the client fails to write
in time becomes silence in the monitor. That much was right.

What was wrong was inferring from it that the holes mean the guest runs below
real time. The alternative was never tested: xenia asks SDL for channel_samples_
= 256, which is 5.33 ms at 6 channels, and daemon.conf here is stock with no
fragment tuning. PULSE_LATENCY_MSEC overrides what SDL s PulseAudio backend
requests.

Measured, same title and sink and parec invocation:

  xenia default (~5.3 ms)   347.5 s   39.3 percent silence   30.5 gaps/s
  PULSE_LATENCY_MSEC=200     88.0 s   15.6 percent silence    3.5 gaps/s

An 8.7x reduction from one environment variable. Against the port s controls --
starved 32.9 gaps/s, genuine music bed 3.3, voice track 0.03, bar at 20 -- the
default is squarely starved and 200 ms lands at the level of real content.

Not yet a clean bill of health: the runs are not like-for-like at 88 s against
347 s, and the short one covers the splash logos where silence is real. What is
established is direction and scale. The consequence that matters is that the
capture route should be retried at raised latency before anyone spends a session
on a Canary rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
sylph-decoder
2026-08-29 16:34:34 +00:00
parent 55cdcac61b
commit b56d0e1fb6

View File

@@ -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)`