feat(sampler): Ultimate Audio FPGA PCM sampler as the default U64 video-audio backend#46
Merged
Conversation
… ring New c64cast/sampler.py: the U64 "Ultimate Audio" ($DF20-$DFFF) FPGA PCM sampler as the high-fidelity video-audio path. Plays 8/16-bit PCM up to 48 kHz straight from REU SDRAM with zero SID/$D418/NMI/CPU/turbo, so it is immune to the bus-halt/badline problems the 4-bit DAC fights. Two halves: - Pure register helpers (unit-testable): divider_for_rate, control_byte, pack_pcm (signed 8-bit / int16-LE), channel_register_writes (big-endian layout), program_channel/gate_off. - UltimateAudioSampler: the scene-facing object (sample_rate, position_seconds, push_samples, get_recent_samples, stop) built on a streaming REU ring. Programs channel 0 as an A<->B loop over the ring; a writer thread REUWRITEs PCM ahead of a wall-clock-computed read head ((monotonic - gate_time) * rate), wrapping, NEUTRAL-padding only past a low watermark. Open-loop on the crystal-exact FPGA clock -> drift-free, no servo/governor. De-risked on .64 + Cam Link before any integration: a 30 s 440 Hz tone (zero-crossing 439.2 Hz = correct rate, 0 dropouts) and a 180 s music clip (best_s 1.01 after correcting the lossy avfoundation capture clock; write-ahead lead stayed bounded -> drift < 0.3%). User-confirmed: clear, steady, high-quality, no wobble. Not yet wired into config/scenes/cli/doctor -- next.
…udio backend Integrate the HW-de-risked UltimateAudioSampler (b6f4e59) end to end so a video scene on the U64 plays its soundtrack through the FPGA PCM sampler (high fidelity, off the C64 bus) instead of the 4-bit $D418 DAC. - config: [audio].backend ("auto"|"dac"|"sampler", default auto), sampler_sample_rate (44100), sampler_bits (16); resolve_audio_backend (mirrors resolve_use_reu_staged) + validate_sampler_cfg. build_scene swaps the DAC streamer for a per-scene UltimateAudioSampler when the backend resolves to "sampler". Bitmap-video fps default is no longer capped at 20 for sampler audio (it's off the bus) — defaults to the muted half-rate (30/25) pending an fps A/B. - backend: HardwareProfile.supports_sampler (True Ultimate, False TR). - doctor: sampler_is_available + provision_sampler/restore_sampler + _wants_sampler + a --doctor probe. The sampler's ring lives in REU SDRAM, so _wants_sampler also pulls the REU into _wants_reu — a sampler run provisions the REU (16 MB), which makes overlay-free bitmap video resolve to the tear-free REU bank-swap path (no $0314 contention since the sampler installs no IRQ). - cli/ensemble: _resolve_sampler_available + provision/restore wired into build_stack/teardown_stack; SystemStack.sampler_available/sampler_restore threaded through scenes_from_config + reloads/ensemble followers. - scenes: VideoScene drives the sampler polymorphically (isinstance branch → start() + push_samples + mark_eof); Scene.audio widened to a union; mic paths narrowed to AudioStreamer. - wizard: prompts for the backend (+ bits/rate) when audio is on. - example config scene-video-sampler.toml, schema regen, CLAUDE.md + docs/{caveats,usage}.md, 51 new tests (helpers/resolve/validate/streamer/ provision/restore/_wants coupling). make check green.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #46 +/- ##
==========================================
- Coverage 79.76% 79.49% -0.28%
==========================================
Files 68 69 +1
Lines 13020 13491 +471
Branches 1924 2013 +89
==========================================
+ Hits 10385 10724 +339
- Misses 2195 2303 +108
- Partials 440 464 +24 ☔ View full report in Codecov by Harness. |
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
Adds the U64 firmware's "Ultimate Audio" FPGA PCM sampler (
$DF20-$DFFF) as the high-fidelity, default video-audio backend on the Ultimate 64. It plays 8/16-bit PCM straight out of REU SDRAM with zero SID /$D418/ NMI / CPU / turbo involvement — so it's immune to the bus-halt/badline problems the 4-bit DAC fights, and sounds like an actual sound card instead of a digi-player. The 4-bit$D418DAC stays for TeensyROM (no FPGA sampler) and as an opt-in lo-fi path; mic/webcam audio always uses the DAC.How
c64cast/sampler.py(new): pure register helpers (divider / control byte / 8-/16-bit PCM pack / big-endian channel layout) +UltimateAudioSampler, a streaming REU ring. Channel 0 is programmed as an A↔B loop; a writer thread REUWRITEs decoded PCM ahead of a wall-clock-computed read head ((monotonic − gate_time) · rate), wrapping, NEUTRAL-padding only past a low watermark. The FPGA clock is crystal-exact, so the read head is computed (never read back) — open-loop and drift-free, no servo/governor/NMI.[audio].backend(auto/dac/sampler, defaultauto),sampler_sample_rate(44100),sampler_bits(16);resolve_audio_backend(mirrorsresolve_use_reu_staged) +validate_sampler_cfg.VideoScenedrives whichever audio object it's handed polymorphically.HardwareProfile.supports_sampler;doctor.provision_sampler/restore_sampler/sampler_is_available/_wants_sampler+ a--doctorprobe, all live + volatile + restored at teardown. The ring lives in REU SDRAM, so a sampler run also provisions the REU (16 MB) — which makes overlay-free bitmap video resolve to the tear-free REU bank-swap path (the sampler installs no$0314IRQ, so video staging and the sampler coexist with no contention).config/examples/scene-video-sampler.toml, regenerated schema, CLAUDE.md + docs/{caveats,usage}.md, and 51 new tests.Verification
make checkgreen: ruff + mypy--strict+ pyright + 1657 tests (51 new).--doctorreports the sampler mapped + audible and the REU enabled for it; a livebackend = "sampler"mhires video run selected the sampler + REU-staged video, rendered, and played clean high-fidelity audio.Follow-ups (deferred)