Skip to content

wgpu: collapse audio onto shared labelle-audio mixer (Phase 2 fan-out) - #389

Merged
apotema merged 3 commits into
mainfrom
feat/wgpu-audio-shared-mixer
Jun 27, 2026
Merged

wgpu: collapse audio onto shared labelle-audio mixer (Phase 2 fan-out)#389
apotema merged 3 commits into
mainfrom
feat/wgpu-audio-shared-mixer

Conversation

@apotema

@apotema apotema commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Fan-out of the Phase 2 audio pilot (after bgfx #388). Collapses wgpu's software audio onto labelle-audio's Mixer(NullSink) (wgpu is software-only — no OS device, so the NullSink/manual-pump path).

  • src/audio.zig: 297 → 233 lines — gone: slot arrays, loadWav, the mixOutput mixer body. Remains: Mixer(NullSink) + libc file-read shim + thin forwarders.
  • src/wav_parser.zig (416 lines) DELETED — it was the overflow-safe parser already ported into labelle-audio/src/wav.zig; the bug: wgpu audio WAV parser vulnerable to integer overflow on corrupt input #12 overflow regression-lock moved with it.
  • All 17 public fns preserved (incl. wgpu's mixOutput(output: []f32, frame_count) — the adapter mixes into an i16 scratch and converts to normalized f32; i16 is safe since the output is consumed nowhere outside audio.zig).
  • Depends on labelle-audio v0.2.1 (url+hash, copied from bgfx).

Build + test green (backends/wgpu). raylib/sdl assessed separately → leave as-is (they delegate to raudio/SDL_mixer, no duplication), so the fan-out ends at sokol+wgpu.

Summary by CodeRabbit

  • New Features

    • Audio playback now routes through a shared audio backend for consistent sound/music handling.
    • Added headless audio adapter smoke tests to validate mixing and output wiring.
  • Bug Fixes

    • Improved audio mixing: output is produced in frame-aligned chunks, with proper region writes (requested frames update; remaining tail is left untouched).
  • Tests

    • Removed the previous WAV parsing regression coverage and replaced it with adapter-level smoke tests.

Fan-out of the Phase 2 audio pilot (bgfx). wgpu has no real OS audio
device, so src/audio.zig now instantiates labelle_audio.Mixer(NullSink)
and forwards every public fn to it, software-pumped via mixOutput. The
duplicated overflow-safe WAV parser (wav_parser.zig, now in
labelle-audio/src/wav.zig) is deleted along with the slot arrays and the
f32 mixOutput mixer.

- build.zig.zon: add labelle_audio v0.2.1 dep
- build.zig: wire labelle-audio into the audio module + host audio test
  (replaces the now-removed wav_parser test)
- src/audio.zig: thin adapter, public API preserved byte-for-byte; the
  shared mixer is i16, mixOutput converts to normalized f32 (the old f32
  output had no consumer anywhere)
- delete src/wav_parser.zig
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ca1a490d-e084-4307-8e40-ea466a545819

📥 Commits

Reviewing files that changed from the base of the PR and between b38adc3 and 72baa58.

📒 Files selected for processing (1)
  • backends/wgpu/src/audio.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • backends/wgpu/src/audio.zig

📝 Walkthrough

Walkthrough

The wgpu audio backend now uses labelle_audio for loading, mixing, and tests. wav_parser.zig is removed, audio.zig becomes an adapter over labelle_audio.Mixer(NullSink), and the build manifest and test wiring are updated.

wgpu audio adapter migration

Layer / File(s) Summary
Package manifest and build wiring
backends/wgpu/build.zig.zon, backends/wgpu/build.zig
Adds labelle_audio to the package manifest and wires the lazy dependency, module import, host-targeted adapter tests, and top-level test step in the build script.
audio.zig adapter core
backends/wgpu/src/audio.zig
Replaces in-file WAV parsing and local mixer state with a shared Audio mixer, path-based file loading, and forwarding sound/music/volume APIs.
mixOutput and smoke tests
backends/wgpu/src/audio.zig
Changes mixOutput to pull interleaved i16 samples from the shared mixer, convert them to f32, and update tests to check silence and partial-buffer writes.

Sequence Diagram(s)

sequenceDiagram
  participant build.zig.zon
  participant build.zig
  participant src/audio.zig
  participant labelle_audio
  participant audio_tests

  build.zig.zon->>build.zig: provides labelle_audio dependency
  build.zig->>src/audio.zig: imports labelle-audio module
  src/audio.zig->>labelle_audio: forwards load/play/mix calls
  build.zig->>audio_tests: builds host-targeted smoke tests
  audio_tests->>src/audio.zig: exercises Audio.resetForTest() and mixOutput
Loading

Changes

wgpu audio adapter migration

Layer / File(s) Summary
Package manifest and build wiring
backends/wgpu/build.zig.zon, backends/wgpu/build.zig
Adds labelle_audio to the package manifest and wires the lazy dependency, module import, host-targeted adapter tests, and top-level test step in the build script.
audio.zig adapter core
backends/wgpu/src/audio.zig
Replaces in-file WAV parsing and local mixer state with a shared Audio mixer, path-based file loading, and forwarding sound/music/volume APIs.
mixOutput and smoke tests
backends/wgpu/src/audio.zig
Changes mixOutput to pull interleaved i16 samples from the shared mixer, convert them to f32, and update tests to check silence and partial-buffer writes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 Hop, hop, the old WAV code took a bow,
Shared mixer music is threading through now.
i16 to f32, soft and bright,
The rabbit taps a happy beat tonight 🎵

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: wgpu audio now uses the shared labelle-audio mixer as part of Phase 2 fan-out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wgpu-audio-shared-mixer

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the WebGPU audio backend to delegate WAV decoding, PCM mixing, and slot management to the shared labelle-audio package, allowing the deletion of the local wav_parser.zig file. The backend now instantiates labelle_audio.Mixer(labelle_audio.NullSink) and converts the mixed i16 samples to f32 in mixOutput. A potential issue was identified in mixOutput where an odd output.len can cause the loop to break early, leaving the last sample uninitialized. It is recommended to zero out any remaining samples in this scenario.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

// stereo interleave stays correct across chunk boundaries.
var chunk: usize = @min(remaining, scratch.len);
chunk -= chunk % CHANNELS;
if (chunk == 0) break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If mix_samples is odd (which can happen if output.len is odd), chunk will become 0 and the loop will break early. This leaves the last sample in output uninitialized/untouched, which could lead to uninitialized memory reads or minor audio artifacts. Consider zeroing out any remaining samples in output[done..mix_samples] before breaking.

        if (chunk == 0) {
            @memset(output[done..mix_samples], 0.0);
            break;
        }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backends/wgpu/src/audio.zig`:
- Around line 185-186: The mix sample calculation in audio mixing is clamping
too late, which can overflow when frame_count is large. Update the logic around
the total_samples and mix_samples computation in the audio path to clamp by
frames first using output.len / CHANNELS, then multiply by CHANNELS, and keep
the loop in the same full-stereo-frame invariant so the mix routine never reads
or writes a partial frame.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 12faf714-e212-4be1-81ed-994b3bf196a3

📥 Commits

Reviewing files that changed from the base of the PR and between a7e9f3f and b38adc3.

📒 Files selected for processing (4)
  • backends/wgpu/build.zig
  • backends/wgpu/build.zig.zon
  • backends/wgpu/src/audio.zig
  • backends/wgpu/src/wav_parser.zig
💤 Files with no reviewable changes (1)
  • backends/wgpu/src/wav_parser.zig

Comment thread backends/wgpu/src/audio.zig Outdated
apotema added 2 commits June 27, 2026 19:01
- Derive frames from output.len/CHANNELS before multiplying, so frame_count *
  CHANNELS can't overflow/trap for a large caller value (CodeRabbit); also makes
  mix_samples always frame-aligned, eliminating the odd-length early-break.
- Zero any tail we don't mix so the host never reads uninitialized samples —
  silence, not garbage (Gemini).
The zero-tail addition broke mixOutput's deliberate 'only writes the frames it
is asked for' contract (and its test). CodeRabbit's overflow fix alone (clamp
frames before multiply) also resolves Gemini's odd-sample break — mix_samples is
now always even, so the loop fully writes [0..mix_samples] with no early break.
Tail stays the caller's, per contract.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant