Skip to content

fix(audio): remove subsampling from the zero-fill detector and share it across both paths - #425

Open
evinjohnn wants to merge 1 commit into
pr/windows-build-toolingfrom
pr/audio-zerofill-aliasing
Open

fix(audio): remove subsampling from the zero-fill detector and share it across both paths#425
evinjohnn wants to merge 1 commit into
pr/windows-build-toolingfrom
pr/audio-zerofill-aliasing

Conversation

@evinjohnn

Copy link
Copy Markdown
Collaborator

Stack 7 of 7. Base: pr/windows-build-tooling — merge that first.

Change summary

Commit Change
289553d8 Remove subsampling from the zero-fill detector and share it across both paths

Files: electron/audio/systemAudioHealthClassifier.mjs, systemAudioHealthClassifier.d.mts, electron/main.ts, plus SystemAudioHealthClassifier.test.mjs and ZerofillDetectorPeakToPeak.test.mjs.

Change detail

The zero-fill detector sampled a subset of frames, which aliases against periodic signals — a stream can read as silent when it is not, and vice versa. This removes the subsampling and consolidates the two divergent detector implementations into one shared path so both callers agree.

Cross-platform analysis

  • Expected macOS behavior: identical. The classifier has no process.platform branch — it operates on PCM buffers after capture, downstream of the platform-specific capture layer.
  • Expected Windows behavior: identical.
  • Existing implementations reviewed: both the macOS system-audio capture path and the Windows WASAPI loopback path feed this classifier. The change is in shared post-capture analysis, not in either capture pipeline.
  • Impact radius: system-audio health classification and whatever downstream logic reacts to a silent-stream verdict.

Validation

  • Covered by automated macOS branch tests — the classifier tests have no platform branch, so one run covers both.
  • Covered by automated Windows branch tests — same suite, executed on Windows.
  • Reviewed but not executed on macOS
  • Requires physical macOS verification — end-to-end system-audio capture behavior with the new detector.
  • Requires physical Windows verification — this development machine has faulty audio hardware, so live capture cannot be trusted here as a signal either way.

Commands executed

git cherry-pick -x   # 1 commit, zero conflicts

Remaining risks

Removing subsampling means the detector now examines every frame, which is more CPU per buffer. The tests assert correctness, not cost — worth a glance at capture-loop timing under load on both platforms.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UWCt3EHyDEzRYhZXJwudSf

…it across both paths

peakToPeakInt16LE strided ~32 points per chunk. Any tone whose period divided
the stride landed on an identical phase every time and measured peak-to-peak 0
at full amplitude. Captures emit 20ms at the emitted rate (lib.rs: chunk_size =
emitted_rate/1000*20) — 320 samples / 640 bytes at 16kHz — so the stride was 10
samples and 1600/3200 Hz read as silence; batched 1920-byte chunks stride 30 and
additionally alias 533/1067/2667/5333 Hz. It aliased identically on every chunk,
so the observation window never cleared, and the mic path raised a false
"TCC denial or device-mute suspected" banner. Measured before the fix: a
full-amplitude 1600 Hz tone read 0 for 600 of 600 consecutive chunks.

Scanning every sample costs ~2us per 1920-byte chunk — well under 1ms per second
of audio across both capture streams. readInt16LE is kept over an Int16Array
view so the result never depends on host endianness or byteOffset alignment.

Removing the stride also removed its incidental transient filter. Both
hasMeaningfulSignal and zerofillLatched are one-way latches, so a single glitch
chunk would have permanently suppressed the dead-capture diagnostic — the
opposite failure, and worse, since it hides a genuinely dead mic. Both paths now
require 3 above-threshold chunks, counted cumulatively so intermittent speech
still latches promptly.

wireMicCapture now calls the shared peakToPeakInt16LE instead of duplicating the
scan. The two copies had already drifted once (the system side moved into
SystemAudioHealthClassifier while the mic side stayed in main.ts) and only one
was under test.

Tests: aliasing and DC-bias cases are behavioral and use the real 16kHz/320-sample
shape; 8kHz is deliberately excluded as exactly Nyquist, where sin(pi*n) is zero
at every sample and the tone is genuinely silent rather than aliased. Restored
the mic banner payload assertions the earlier rewrite dropped, and the source
extractions are lazy so one refactor can no longer wipe every assertion in the
file. Verified by reverting each property in turn and confirming the guards fire.

NOT physically verified: the development machine's mic and speakers are faulty at
the hardware level, and SystemAudioCapture cannot be probed outside Electron
(WASAPI needs a COM apartment). Requires physical macOS and Windows verification.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWCt3EHyDEzRYhZXJwudSf
(cherry picked from commit 289553d899943cd9bf1ba3e2422f7ab664f04de8)
@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown

Greptile Summary

Consolidates zero-fill amplitude analysis into a shared full-sample peak-to-peak detector.

  • Removes subsampling that could alias periodic audio signals.
  • Requires three above-threshold chunks before permanently latching meaningful audio on both system and microphone paths.
  • Adds behavioral and structural regression coverage for DC bias, aliasing frequencies, transient chunks, and detector delegation.

Confidence Score: 5/5

The PR appears safe to merge, with the shared detector and both latch paths behaving consistently under the covered signal and silence scenarios.

The full-sample peak-to-peak scan removes the documented aliasing mechanism, both capture paths use equivalent cumulative latch behavior, lifecycle state resets with each wired capture, and no concrete changed-code failure remains.

Important Files Changed

Filename Overview
electron/audio/systemAudioHealthClassifier.mjs Replaces strided sampling with a complete PCM scan and introduces a configurable cumulative three-chunk meaningful-signal latch.
electron/main.ts Reuses the shared detector in the microphone path and aligns its latch threshold with system-audio classification.
electron/audio/systemAudioHealthClassifier.d.mts Declares the new meaningfulChunkCount classifier option.
electron/audio/tests/SystemAudioHealthClassifier.test.mjs Adds behavioral coverage for DC offsets, aliasing tones, transient rejection, sustained audio, and latch configuration.
electron/services/tests/ZerofillDetectorPeakToPeak.test.mjs Updates structural regression checks for the shared detector and lazy source extraction.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[PCM audio chunk] --> B[Scan every int16 sample]
  B --> C[Compute max minus min]
  C --> D{Peak-to-peak above 100?}
  D -- Yes --> E[Increment loud chunk count]
  E --> F{At least 3 loud chunks?}
  F -- Yes --> G[Latch meaningful signal]
  F -- No --> H[Continue observing]
  D -- No --> I{Observation window elapsed without latch?}
  I -- Yes --> J[Report sustained silence]
  I -- No --> H
Loading

Reviews (1): Last reviewed commit: "fix(audio): remove subsampling from the ..." | Re-trigger Greptile

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