fix(audio): remove subsampling from the zero-fill detector and share it across both paths - #425
Open
evinjohnn wants to merge 1 commit into
Open
fix(audio): remove subsampling from the zero-fill detector and share it across both paths#425evinjohnn wants to merge 1 commit into
evinjohnn wants to merge 1 commit into
Conversation
…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)
|
| 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
Reviews (1): Last reviewed commit: "fix(audio): remove subsampling from the ..." | Re-trigger Greptile
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.
Stack 7 of 7. Base:
pr/windows-build-tooling— merge that first.Change summary
289553d8Files:
electron/audio/systemAudioHealthClassifier.mjs,systemAudioHealthClassifier.d.mts,electron/main.ts, plusSystemAudioHealthClassifier.test.mjsandZerofillDetectorPeakToPeak.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
process.platformbranch — it operates on PCM buffers after capture, downstream of the platform-specific capture layer.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 macOSRequires 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
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