Repository navigation
A11a: the ALSA bridge becomes an instrument, with its report - #94
Merged
Merged
Conversation
The first part of the roadmap's A11, the harness Block B's bench runs.
examples/alsa_bridge.cpp:
- Only hw: devices. --in and --out are required, and a plugin device
("default", plughw:, dmix) is refused before anything is opened. On a
Pi, "default" routes through PipeWire and its resampler, and the
bridge would measure that converter's clock, not the crystal's.
- Per-device counters. Each audio thread counts the frames it moves and
its ALSA xruns (-EPIPE), kept apart from its other recoveries.
- Hardware timestamps. Both devices timestamp every pointer update on
CLOCK_MONOTONIC_RAW. After each transfer the thread publishes the last
update (snd_pcm_htimestamp) as the frames the hardware had moved at
that instant, through a sequence counter, so the main thread never
pairs a position with another update's time. A driver without
timestamps leaves the fields empty.
- --csv writes, no longer appends, the per-second record: status(),
CLOCK_MONOTONIC_RAW, both frame counts, xruns, other recoveries and
the hardware pairs.
- --rt runs both audio threads SCHED_FIFO (priority 80) with every page
locked (mlockall), and stops the run if the system refuses either.
- The file is now clang-tidy-clean: braces throughout, and the two
conversion helpers in snake_case.
examples/alsa_bridge_report.py (standard library; --plot uses matplotlib)
reads that CSV:
- A least-squares line through each device's frames against time gives
each crystal's rate, from the thread counts and, where present, from
the hardware pairs. Their ratio is the true offset in ppm.
- It compares that offset with the servo's mean while locked.
- It states the ppm wobble and the FIFO's breathing (standard deviation
and peak to peak) and the final underrun, overrun, resync and xrun
counts.
- --self-test checks the arithmetic on a synthetic hour with an 80 ppm
offset. The thread counts recover 80.025 ppm and the hardware pairs
80.000; wobble and breathing match the injected noise.
CI:
- The Linux build-and-test legs and the clang-tidy job install
libasound2-dev, so the bridge is built and tidy-gated on every PR.
- Two tests run without hardware: AlsaBridgeRefusesPluginDevices (built
with the bridge) and AlsaBridgeReportSelfTest (wherever Python is).
HARDWARE_TESTING.md, the book's hardware chapter and the roadmap describe
it; A11b (pico2_cyccnt cases) and A11c (the UAC2 firmware) follow.
Locally, with ALSA installed: clang 18 -Werror and GCC 13 build the bridge
without a warning, clang-tidy is clean, and the full clang suite passes,
329 of 329, the two new tests included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GKzq1VSdcMYCRMhddiMaf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
This is the first of three PRs for the roadmap's A11, the harness Block B's bench runs. It changes the ALSA bridge (
async/examples/alsa_bridge.cpp), adds a CSV report script, and makes both CI-proven on Linux.The bridge:
hw:devices only.--inand--outare now required, and a plugin device (default,plughw:, dmix) is refused before anything is opened.-EPIPE), kept apart from its other recoveries.CLOCK_MONOTONIC_RAW. After each transfer the thread publishes the last one (snd_pcm_htimestamp) as the frames the hardware had moved at that instant, through a sequence counter. A driver without timestamps leaves those fields empty.--csvnow writes rather than appends. Each per-second row holds:status();CLOCK_MONOTONIC_RAW;--rtruns both audio threadsSCHED_FIFOwithmlockall, and stops if the system refuses either.examples/alsa_bridge_report.py(standard library;--plotuses matplotlib) reads that CSV:CI: the Linux build-and-test legs and the clang-tidy job install
libasound2-dev.Why
Block B's verdict on
async(row P8) is "clock recovery proven on independent crystals". The bridge reported the servo's own estimate, but nothing measured the crystals independently to check it. It would also silently accept a plugin device that resamples (on a Pi,defaultis PipeWire), and then measure the wrong clock.Verification
--self-testbuilds a synthetic hour with an 80 ppm offset. The thread counts recover 80.025 ppm and the hardware pairs 80.000 ppm, and the wobble and breathing match the injected noise.async.Examples.AlsaBridgeReportSelfTestruns the self-test wherever Python is.async.Examples.AlsaBridgeRefusesPluginDeviceschecks that--in defaultis refused.libasound2-devinstalled:-Werrorand GCC 13 build the bridge without a warning;Notes for the reviewer
"default";--in/--outmust behw:devices. The book's commands already usedhw:1,0/hw:2,0.pico2_cyccntgainsbridgeandrationalcases andcopy_to_ram) and A11c (the UAC2 asynchronous-mode firmware) follow as their own PRs.🤖 Generated with Claude Code
https://claude.ai/code/session_016GKzq1VSdcMYCRMhddiMaf
Generated by Claude Code