Skip to content

Channel Bank: remove clicks and pops from recordings - #2

Draft
wdarney wants to merge 2 commits into
integration/mainfrom
claude/channel-bank-recording-clicks
Draft

wdarney wants to merge 2 commits into
integration/mainfrom
claude/channel-bank-recording-clicks

Conversation

@wdarney

@wdarney wdarney commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Requested by Will · project thread

Before: Channel Bank WAV/M4A recordings had clicks and pops, mostly at the start of files and worse on channels that had just carried a transmission. They also showed up near where the pre-roll meets live audio, where detection dropped out and came back mid-transmission (especially in AM speech pauses), and at the start and end of normalized files. Live VFO audio was clean.

After: AM key-ups no longer spike at the start of a recording, and the recording chain no longer splices or gates audio with hard steps.

The clicks came from five places in misc_modules/channel_bank/src/main.cpp:

  1. Pre-roll duplicated a block. The current block is pushed into the pre-roll ring, the whole ring is flushed on file open, then the same block is written again by the live path. Now the flush excludes the current block, and the first 10 ms of the ring is tapered.
  2. Pre-roll bypassed RNNoise, and the denoiser carried state across files. The pre-roll was written raw while live audio went through the 480-sample RNNoise frame buffer, which still held a partial frame and overlap/GRU history from the previous transmission. Both now share writeRecordingSamples(), and the RNNoise state and nrInPos are reset in openNewFile(). Pre-roll frames are kept out of the RNNoise voice-gate tally so gate behavior is unchanged.
  3. Tail fade snapped back to full gain. tailFade was computed once per block (a stair-step) and fadeOutRemaining jumped straight to 2400 when raw detection re-qualified, so gain went from 0 to 1 in one sample. It is now per-sample, fading out over 50 ms as before and back in over 10 ms.
  4. Normalization butted boosted audio against zero padding. normalizeWavFile() cuts at the 250 ms trim or onset point (mid-signal by design) and the offset point, after applying up to 20x scale plus compression, then writes zero padding directly against them. It now tapers 10 ms at each edge.
  5. Carrier AGC overshot at key-up on running slots. AM slots used carrier AGC, which winds its gain up on noise between transmissions. On a slot still running from the last transmission, the next key-up overshoots by up to ~12x the speech peaks, and the pre-roll writes it to the start of the file. Fresh slots skip the key-up (they spawn mid-transmission and discard 200 ms of warmup), which is why busy channels clicked more. AM slots now use audio AGC, the radio module's default and what the live VFO uses. Audio AGC runs 12-20 dB hotter, so AM audio is scaled by 0.25 (AM_AUDIO_LEVEL) before recGain to keep levels close to before and peaks off the clamp. As in the live VFO, the first ~300 ms after a strong key-up comes out quieter while the AGC recovers.

How it was checked: main.cpp compiles on Linux with RNNoise both on and off (no new warnings). The full module doesn't link on Linux for unrelated reasons (whisper.h, M_PI in vendored rnnoise). A Python model of the recorder (733 Hz tone, 240-sample blocks, two raw-detection dropouts) found 6 click samples up to 8.4x the tone's own max step before changes 1-4, and 0 after. A Python model of the AM demod (same AGC and DC-blocker math as core/src/dsp/demod/am.h, 10-30 dB SNR, 2 s gap) gave key-up peaks up to 11.9x the speech peak with carrier AGC and at most 1.6x with audio AGC. Not yet tested on hardware.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tkty4ckSA6gCEd35PAfAij


Generated by Claude Code

- Pre-roll flush no longer repeats the current audio block (it was
  written once from the ring and again by the live path).
- Pre-roll goes through the same RNNoise/write path as live audio, and
  the denoiser state + partial frame are reset per file, so no stale
  audio from the previous transmission is spliced in.
- Tail fade gain is computed per sample and ramps back up over 10 ms
  instead of snapping from silence to full when detection returns.
- normalizeWavFile tapers 10 ms at both trim points so the boosted
  audio no longer butts against the digital-silence padding.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tkty4ckSA6gCEd35PAfAij
@wdarney wdarney self-assigned this Oct 2, 2026

wdarney commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

The Build Binaries failures on this PR aren't caused by this change. For example, build_debian_bullseye_amd64 dies in the dependency install step before any code is compiled: apt-get gets a 404 from deb.debian.org/debian-security for packages like libfreetype6 and mesa because the package index is stale. The same workflow is also red on the base branch: every job failed on the latest integration/main push (run 36480045969, including macOS and Windows), along with the four pushes before it.

I don't have a fix for the workflow yet, and repairing CI is outside this PR's scope, so I haven't re-run the jobs. This PR changes only misc_modules/channel_bank/src/main.cpp, which compiles locally with RNNoise both on and off.


Generated by Claude Code

Carrier AGC winds its gain up on noise between transmissions. On a slot
that is still running from the previous transmission, the next key-up
overshoots by up to ~12x the speech peaks, and the pre-roll writes that
spike to the start of the file. Fresh slots never record the key-up,
which is why busy channels clicked and quiet ones didn't.

Switch AM slots to audio AGC, the radio module's default and what the
live VFO uses. It caps the key-up at about speech level. Audio AGC runs
12-20 dB hotter than carrier AGC, so scale AM audio by 0.25 before
recGain to keep levels near where they were and peaks off the clamp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tkty4ckSA6gCEd35PAfAij
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.

2 participants