[fix](trx-rs): add EMA smoothing to signal strength and fix freq input clobbering

EMA (α=0.4) smooths the carrier power estimate across DSP blocks. Custom PartialEq on VchanRdsEntry excludes signal_db so rapidly-changing levels do not trigger main state SSE updates that overwrite the frequency input.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-27 15:24:18 +01:00
parent 5220e606c1
commit 6440f67d94
2 changed files with 15 additions and 3 deletions
+11 -1
View File
@@ -430,7 +430,11 @@ pub struct RdsData {
}
/// RDS metadata snapshot for a virtual channel.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
///
/// `PartialEq` intentionally ignores `signal_db` so that rapidly-changing
/// signal levels do not cause the main state snapshot to diff on every poll
/// cycle (signal_db flows through the spectrum SSE instead).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VchanRdsEntry {
/// Virtual channel UUID.
pub id: Uuid,
@@ -442,6 +446,12 @@ pub struct VchanRdsEntry {
pub signal_db: Option<f32>,
}
impl PartialEq for VchanRdsEntry {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.rds == other.rds
}
}
/// Read-only projection of state shared with clients.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RigSnapshot {
@@ -675,12 +675,14 @@ impl ChannelDsp {
// Carrier power: DC component of the mixed signal (narrow-band estimate
// at the tuned frequency). Correlates with the spectrum FFT peak.
// EMA smoothing (α ≈ 0.4) for fast response with light jitter reduction.
{
const SIGNAL_EMA_ALPHA: f32 = 0.4;
let inv_n = 1.0 / n as f32;
let dc_i: f32 = mixed_i.iter().sum::<f32>() * inv_n;
let dc_q: f32 = mixed_q.iter().sum::<f32>() * inv_n;
let carrier_power = dc_i * dc_i + dc_q * dc_q;
self.last_signal_db = 10.0 * carrier_power.max(1e-12).log10();
let carrier_db = 10.0 * (dc_i * dc_i + dc_q * dc_q).max(1e-12).log10();
self.last_signal_db += SIGNAL_EMA_ALPHA * (carrier_db - self.last_signal_db);
}
self.lpf_iq.filter_block_into(