Opt-in channel-power squelch for AM audio RX (#3315)

* AM: add opt-in channel-power squelch for AM audio RX

Add an SDR++-style power squelch on the complex channel signal for the
Analog Audio RX app in AM modes (DSB/SSB/CW). The squelch measures the
mean channel power, converts it to dBFS, and mutes the demodulated audio
when the signal is below a user-set threshold. Being carrier-based it
keys on the AM carrier (e.g. airband/ATC) rather than on audio-band
noise, which the existing FM squelch could not do for AM.

A new 'SQ' field (0-99, 0 = off) is added to the AM options view and is
persisted per app via app_settings. Default is 0, so existing AM
behaviour is unchanged unless the user enables it. Threshold maps 1..99
to -80..-20 dBFS.

Touches: message (AMConfigureMessage.squelch_level), baseband_api
(AMConfig::apply), receiver_model (am_squelch_level get/set),
app_settings (persistence), proc_am_audio (DSP), analog_audio_app (UI).

* Refactor audio power calculation to use magnitude squared

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Mo
2026-09-14 20:54:54 +02:00
committed by GitHub
parent d72b5ed0b1
commit b8ad44c431
11 changed files with 78 additions and 10 deletions
+28
View File
@@ -27,6 +27,7 @@
#include "event_m4.hpp"
#include <array>
#include <cmath>
#include "dsp_hilbert.hpp"
// Phase 2: Constructor to start threads AFTER object is fully initialized
@@ -72,6 +73,32 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
feed_channel_stats(channel_out);
auto audio = demodulate(channel_out); // now 3 AM demodulation types : demod_am, demod_ssb, demod_ssb_fm (for Wefax)
// SDR++-style power squelch on the complex channel signal (opt-in; 0 = off).
// Measures mean channel power, converts to dBFS, and mutes the audio when
// the signal is below the user threshold. Carrier-based, so it keys on the
// AM carrier (e.g. airband/ATC) rather than on audio-band noise.
if (squelch_level > 0 && channel_out.count > 0) {
uint64_t sum_mag_sq = 0;
for (size_t i = 0; i < channel_out.count; i++) {
const int32_t re = channel_out.p[i].real();
const int32_t im = channel_out.p[i].imag();
sum_mag_sq += static_cast<uint64_t>(re) * re + static_cast<uint64_t>(im) * im;
}
constexpr float full_scale_mag2 = 32768.0f * 32768.0f;
const float mean_mag2_norm =
static_cast<float>(sum_mag_sq) / (static_cast<float>(channel_out.count) * full_scale_mag2);
const float level_dbfs = (mean_mag2_norm > 0.0f) ? mag2_to_dbv_norm(mean_mag2_norm) : -200.0f;
// Map squelch_level 1..99 to a threshold of -80..-20 dBFS.
const float threshold_dbfs = -80.0f + (squelch_level - 1) * (60.0f / 98.0f);
if (level_dbfs < threshold_dbfs) {
for (size_t i = 0; i < audio.count; i++)
audio.p[i] = 0.0f;
}
}
audio_compressor.execute_in_place(audio);
audio_output.write(audio);
}
@@ -151,6 +178,7 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
spectrum_interval_samples =
decim_0_output_fs / spectrum_rate_hz;
audio_output.configure(message.audio_hpf_lpf_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
squelch_level = message.squelch_level;
configured = true;
}