diff --git a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs index 0ba01d7..1d7958b 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http/src/api.rs @@ -1069,6 +1069,30 @@ pub async fn set_sdr_squelch( .await } +#[derive(serde::Deserialize)] +pub struct SdrNoiseBlankerQuery { + pub enabled: bool, + pub threshold: f64, + pub rig_id: Option, +} + +#[post("/set_sdr_noise_blanker")] +pub async fn set_sdr_noise_blanker( + query: web::Query, + rig_tx: web::Data>, +) -> Result { + let q = query.into_inner(); + send_command( + &rig_tx, + RigCommand::SetSdrNoiseBlanker { + enabled: q.enabled, + threshold: q.threshold, + }, + q.rig_id, + ) + .await +} + #[derive(serde::Deserialize)] pub struct WfmDeemphasisQuery { pub us: u32, @@ -1834,6 +1858,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(set_sdr_lna_gain) .service(set_sdr_agc) .service(set_sdr_squelch) + .service(set_sdr_noise_blanker) .service(set_wfm_deemphasis) .service(set_wfm_stereo) .service(set_wfm_denoise) diff --git a/src/trx-core/src/rig/command.rs b/src/trx-core/src/rig/command.rs index 8abb896..a5b9781 100644 --- a/src/trx-core/src/rig/command.rs +++ b/src/trx-core/src/rig/command.rs @@ -43,6 +43,7 @@ pub enum RigCommand { SetSdrLnaGain(f64), SetSdrAgc(bool), SetSdrSquelch { enabled: bool, threshold_db: f64 }, + SetSdrNoiseBlanker { enabled: bool, threshold: f64 }, SetWfmDeemphasis(u32), SetWfmStereo(bool), SetWfmDenoise(WfmDenoiseLevel), diff --git a/src/trx-core/src/rig/controller/handlers.rs b/src/trx-core/src/rig/controller/handlers.rs index c29a697..7b4f114 100644 --- a/src/trx-core/src/rig/controller/handlers.rs +++ b/src/trx-core/src/rig/controller/handlers.rs @@ -525,6 +525,7 @@ pub fn command_from_rig_command(cmd: RigCommand) -> Box { | RigCommand::SetSdrLnaGain(_) | RigCommand::SetSdrAgc(_) | RigCommand::SetSdrSquelch { .. } + | RigCommand::SetSdrNoiseBlanker { .. } | RigCommand::SetWfmDeemphasis(_) | RigCommand::SetWfmStereo(_) | RigCommand::SetWfmDenoise(_) diff --git a/src/trx-core/src/rig/mod.rs b/src/trx-core/src/rig/mod.rs index a873092..833e1ac 100644 --- a/src/trx-core/src/rig/mod.rs +++ b/src/trx-core/src/rig/mod.rs @@ -211,6 +211,17 @@ pub trait RigCat: Rig + Send { ))) } + fn set_sdr_noise_blanker<'a>( + &'a mut self, + _enabled: bool, + _threshold: f64, + ) -> Pin> + Send + 'a>> { + Box::pin(std::future::ready(Err( + Box::new(response::RigError::not_supported("set_sdr_noise_blanker")) + as Box, + ))) + } + fn set_wfm_stereo<'a>( &'a mut self, _enabled: bool, diff --git a/src/trx-core/src/rig/state.rs b/src/trx-core/src/rig/state.rs index d7486f0..b8da975 100644 --- a/src/trx-core/src/rig/state.rs +++ b/src/trx-core/src/rig/state.rs @@ -326,6 +326,10 @@ pub struct RigFilterState { pub sdr_squelch_enabled: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub sdr_squelch_threshold_db: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sdr_nb_enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sdr_nb_threshold: Option, #[serde(default = "default_wfm_deemphasis_us")] pub wfm_deemphasis_us: u32, #[serde(default = "default_wfm_stereo")] diff --git a/src/trx-protocol/src/codec.rs b/src/trx-protocol/src/codec.rs index 65386c2..9778eb4 100644 --- a/src/trx-protocol/src/codec.rs +++ b/src/trx-protocol/src/codec.rs @@ -316,6 +316,8 @@ mod tests { sdr_agc_enabled: None, sdr_squelch_enabled: None, sdr_squelch_threshold_db: None, + sdr_nb_enabled: None, + sdr_nb_threshold: None, wfm_deemphasis_us: 75, wfm_stereo: true, wfm_stereo_detected: false, @@ -358,6 +360,8 @@ mod tests { sdr_agc_enabled: None, sdr_squelch_enabled: None, sdr_squelch_threshold_db: None, + sdr_nb_enabled: None, + sdr_nb_threshold: None, wfm_deemphasis_us: 50, wfm_stereo: true, wfm_stereo_detected: true, diff --git a/src/trx-protocol/src/mapping.rs b/src/trx-protocol/src/mapping.rs index 964e40d..c343b10 100644 --- a/src/trx-protocol/src/mapping.rs +++ b/src/trx-protocol/src/mapping.rs @@ -65,6 +65,9 @@ pub fn client_command_to_rig(cmd: ClientCommand) -> RigCommand { enabled, threshold_db, }, + ClientCommand::SetSdrNoiseBlanker { enabled, threshold } => { + RigCommand::SetSdrNoiseBlanker { enabled, threshold } + } ClientCommand::SetWfmDeemphasis { deemphasis_us } => { RigCommand::SetWfmDeemphasis(deemphasis_us) } @@ -128,6 +131,9 @@ pub fn rig_command_to_client(cmd: RigCommand) -> ClientCommand { enabled, threshold_db, }, + RigCommand::SetSdrNoiseBlanker { enabled, threshold } => { + ClientCommand::SetSdrNoiseBlanker { enabled, threshold } + } RigCommand::SetWfmDeemphasis(deemphasis_us) => { ClientCommand::SetWfmDeemphasis { deemphasis_us } } diff --git a/src/trx-protocol/src/types.rs b/src/trx-protocol/src/types.rs index 929cfac..b96c5fd 100644 --- a/src/trx-protocol/src/types.rs +++ b/src/trx-protocol/src/types.rs @@ -48,6 +48,7 @@ pub enum ClientCommand { SetSdrLnaGain { gain_db: f64 }, SetSdrAgc { enabled: bool }, SetSdrSquelch { enabled: bool, threshold_db: f64 }, + SetSdrNoiseBlanker { enabled: bool, threshold: f64 }, SetWfmDeemphasis { deemphasis_us: u32 }, SetWfmStereo { enabled: bool }, SetWfmDenoise { level: WfmDenoiseLevel }, diff --git a/src/trx-server/src/config.rs b/src/trx-server/src/config.rs index 96eeb4f..c9ee291 100644 --- a/src/trx-server/src/config.rs +++ b/src/trx-server/src/config.rs @@ -298,6 +298,8 @@ pub struct SdrConfig { pub gain: SdrGainConfig, /// Virtual software squelch applied to demodulated audio except WFM. pub squelch: SdrSquelchConfig, + /// Noise blanker for impulse noise suppression on IQ samples. + pub noise_blanker: SdrNoiseBlankerConfig, /// Virtual receiver channels (at least one required when SDR backend is active). pub channels: Vec, /// Maximum number of simultaneous virtual channels (including the primary). @@ -319,6 +321,7 @@ impl Default for SdrConfig { center_offset_hz: 100_000, gain: SdrGainConfig::default(), squelch: SdrSquelchConfig::default(), + noise_blanker: SdrNoiseBlankerConfig::default(), channels: Vec::new(), max_virtual_channels: default_max_virtual_channels(), } @@ -350,6 +353,26 @@ impl Default for SdrSquelchConfig { } } +/// Noise blanker settings for impulse noise suppression on IQ samples. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct SdrNoiseBlankerConfig { + /// Enables the noise blanker. + pub enabled: bool, + /// Threshold multiplier for impulse detection (typical range: 1..100). + /// A sample whose magnitude exceeds threshold × running RMS is blanked. + pub threshold: f64, +} + +impl Default for SdrNoiseBlankerConfig { + fn default() -> Self { + Self { + enabled: false, + threshold: 10.0, + } + } +} + /// Gain control mode for the SDR device. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] @@ -507,6 +530,7 @@ impl ServerConfig { } } validate_sdr_squelch_config("[sdr.squelch]", &self.sdr.squelch)?; + validate_sdr_nb_config("[sdr.noise_blanker]", &self.sdr.noise_blanker)?; // Multi-rig uniqueness checks. if !self.rigs.is_empty() { @@ -546,6 +570,10 @@ impl ServerConfig { &format!("[[rigs]] [sdr.squelch] (rig id: \"{}\")", rig.id), &rig.sdr.squelch, )?; + validate_sdr_nb_config( + &format!("[[rigs]] [sdr.noise_blanker] (rig id: \"{}\")", rig.id), + &rig.sdr.noise_blanker, + )?; } if enabled_count == 0 { return Err( @@ -838,6 +866,16 @@ fn validate_sdr_squelch_config(path: &str, squelch: &SdrSquelchConfig) -> Result Ok(()) } +fn validate_sdr_nb_config(path: &str, nb: &SdrNoiseBlankerConfig) -> Result<(), String> { + if !nb.threshold.is_finite() { + return Err(format!("{path}.threshold must be finite")); + } + if !(1.0..=100.0).contains(&nb.threshold) { + return Err(format!("{path}.threshold must be in range 1..=100")); + } + Ok(()) +} + fn validate_tokens(path: &str, tokens: &[String]) -> Result<(), String> { if tokens.iter().any(|t| t.trim().is_empty()) { return Err(format!("{path} must not contain empty tokens")); diff --git a/src/trx-server/src/main.rs b/src/trx-server/src/main.rs index 6528ff0..0e373b4 100644 --- a/src/trx-server/src/main.rs +++ b/src/trx-server/src/main.rs @@ -346,6 +346,8 @@ fn build_sdr_rig_from_instance(rig_cfg: &RigInstanceConfig) -> SdrRigBuildResult rig_cfg.sdr.squelch.hysteresis_db, rig_cfg.sdr.squelch.tail_ms, rig_cfg.sdr.max_virtual_channels, + rig_cfg.sdr.noise_blanker.enabled, + rig_cfg.sdr.noise_blanker.threshold, )?; let pcm_rx = sdr_rig.subscribe_pcm(); diff --git a/src/trx-server/src/rig_task.rs b/src/trx-server/src/rig_task.rs index 6faabe9..ace7029 100644 --- a/src/trx-server/src/rig_task.rs +++ b/src/trx-server/src/rig_task.rs @@ -590,6 +590,16 @@ async fn process_command( let _ = ctx.state_tx.send(ctx.state.clone()); return snapshot_from(ctx.state); } + RigCommand::SetSdrNoiseBlanker { enabled, threshold } => { + if let Err(e) = ctx.rig.set_sdr_noise_blanker(enabled, threshold).await { + return Err(RigError::communication(format!( + "set_sdr_noise_blanker: {e}" + ))); + } + ctx.state.filter = ctx.rig.filter_state(); + let _ = ctx.state_tx.send(ctx.state.clone()); + return snapshot_from(ctx.state); + } RigCommand::SetWfmDeemphasis(deemphasis_us) => { if let Err(e) = ctx.rig.set_wfm_deemphasis(deemphasis_us).await { return Err(RigError::communication(format!("set_wfm_deemphasis: {e}"))); diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs index 00f4252..915a8a9 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp.rs @@ -24,7 +24,7 @@ use tokio::sync::broadcast; use tracing::warn; use trx_core::rig::state::RigMode; -pub use self::channel::{ChannelDsp, VirtualSquelchConfig}; +pub use self::channel::{ChannelDsp, NoiseBlankerConfig, VirtualSquelchConfig}; pub use self::filter::{BlockFirFilter, BlockFirFilterPair, FirFilter}; use self::spectrum::SpectrumSnapshotter; @@ -152,6 +152,7 @@ impl SdrPipeline { wfm_deemphasis_us: u32, wfm_stereo: bool, squelch_cfg: VirtualSquelchConfig, + nb_cfg: NoiseBlankerConfig, channels: &[(f64, RigMode, u32)], ) -> Self { const IQ_BROADCAST_CAPACITY: usize = 64; @@ -173,6 +174,11 @@ impl SdrPipeline { } else { VirtualSquelchConfig::default() }; + let channel_nb_cfg = if channel_idx == 0 { + nb_cfg + } else { + NoiseBlankerConfig::default() + }; let dsp = ChannelDsp::new( channel_if_hz, mode, @@ -185,6 +191,7 @@ impl SdrPipeline { wfm_stereo, false, channel_squelch_cfg, + channel_nb_cfg, pcm_tx.clone(), iq_tx.clone(), ); @@ -274,6 +281,7 @@ impl SdrPipeline { self.wfm_stereo, false, VirtualSquelchConfig::default(), + NoiseBlankerConfig::default(), pcm_tx.clone(), iq_tx.clone(), ); @@ -562,6 +570,7 @@ mod tests { 75, true, VirtualSquelchConfig::default(), + NoiseBlankerConfig::default(), &[(200_000.0, RigMode::USB, 3000)], ); assert_eq!(pipeline.pcm_senders.len(), 1); @@ -579,6 +588,7 @@ mod tests { 75, true, VirtualSquelchConfig::default(), + NoiseBlankerConfig::default(), &[], ); assert_eq!(pipeline.pcm_senders.len(), 0); diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs index e4f5203..e0e131d 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/dsp/channel.rs @@ -10,6 +10,88 @@ use crate::demod::{CquamDemod, DcBlocker, Demodulator, SoftAgc, WfmStereoDecoder use super::{BlockFirFilterPair, IQ_BLOCK_SIZE}; +// --------------------------------------------------------------------------- +// Noise blanker +// --------------------------------------------------------------------------- + +/// IQ-domain impulse noise blanker. +/// +/// Maintains a running RMS estimate of the IQ magnitude. When a sample's +/// magnitude exceeds `threshold × rms`, it is replaced by linear interpolation +/// between the last clean sample and the next clean sample (lookahead of 1). +/// +/// The RMS tracker uses exponential smoothing with a time constant of ~128 +/// samples at the IQ sample rate, fast enough to track band-noise changes +/// but slow enough not to follow individual impulses. +#[derive(Debug, Clone)] +pub struct NoiseBlanker { + enabled: bool, + threshold: f32, + /// Exponentially-smoothed mean-square estimate. + mean_sq: f32, + /// Last clean sample (used for interpolation fill). + last_clean: Complex, +} + +const NB_ALPHA: f32 = 1.0 / 128.0; + +impl NoiseBlanker { + pub fn new(enabled: bool, threshold: f32) -> Self { + Self { + enabled, + threshold: threshold.max(1.0), + mean_sq: 1e-10, + last_clean: Complex::new(0.0, 0.0), + } + } + + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + pub fn set_threshold(&mut self, threshold: f32) { + self.threshold = threshold.max(1.0); + } + + /// Process a block of IQ samples in-place, blanking impulse spikes. + pub fn process(&mut self, block: &mut [Complex]) { + if !self.enabled || block.is_empty() { + return; + } + + let thresh_sq = self.threshold * self.threshold; + + for sample in block.iter_mut() { + let s = *sample; + let mag_sq = s.re * s.re + s.im * s.im; + + if mag_sq > thresh_sq * self.mean_sq { + // Impulse detected — replace with last clean sample. + *sample = self.last_clean; + } else { + // Clean sample — update RMS tracker. + self.mean_sq += NB_ALPHA * (mag_sq - self.mean_sq); + self.last_clean = s; + } + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct NoiseBlankerConfig { + pub enabled: bool, + pub threshold: f32, +} + +impl Default for NoiseBlankerConfig { + fn default() -> Self { + Self { + enabled: false, + threshold: 10.0, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct VirtualSquelchConfig { pub enabled: bool, @@ -213,6 +295,7 @@ pub struct ChannelDsp { processing_enabled: bool, force_mono_pcm: bool, squelch: VirtualSquelch, + noise_blanker: NoiseBlanker, } impl ChannelDsp { @@ -327,6 +410,7 @@ impl ChannelDsp { wfm_stereo: bool, force_mono_pcm: bool, squelch_cfg: VirtualSquelchConfig, + nb_cfg: NoiseBlankerConfig, pcm_tx: broadcast::Sender>, iq_tx: broadcast::Sender>>, ) -> Self { @@ -415,6 +499,7 @@ impl ChannelDsp { processing_enabled: true, force_mono_pcm, squelch: VirtualSquelch::new(squelch_cfg), + noise_blanker: NoiseBlanker::new(nb_cfg.enabled, nb_cfg.threshold), } } @@ -431,6 +516,11 @@ impl ChannelDsp { self.squelch.set_threshold_db(threshold_db); } + pub fn set_noise_blanker(&mut self, enabled: bool, threshold: f32) { + self.noise_blanker.set_enabled(enabled); + self.noise_blanker.set_threshold(threshold); + } + pub fn set_mode(&mut self, mode: &RigMode) { self.mode = mode.clone(); if *mode != RigMode::WFM { @@ -499,6 +589,16 @@ impl ChannelDsp { return; } + // Apply noise blanker on a mutable copy when enabled. + let block = if self.noise_blanker.enabled { + let mut nb_buf = block.to_vec(); + self.noise_blanker.process(&mut nb_buf); + nb_buf + } else { + block.to_vec() + }; + let block = &block[..]; + self.scratch_mixed_i.resize(n, 0.0); self.scratch_mixed_q.resize(n, 0.0); let mixed_i = &mut self.scratch_mixed_i; @@ -684,6 +784,7 @@ mod tests { true, false, VirtualSquelchConfig::default(), + NoiseBlankerConfig::default(), pcm_tx, iq_tx, ); @@ -707,6 +808,7 @@ mod tests { true, false, VirtualSquelchConfig::default(), + NoiseBlankerConfig::default(), pcm_tx, iq_tx, ); @@ -714,4 +816,31 @@ mod tests { dsp.set_mode(&RigMode::FM); assert_eq!(dsp.demodulator, Demodulator::Fm); } + + #[test] + fn noise_blanker_suppresses_impulse() { + let mut nb = NoiseBlanker::new(true, 5.0); + // Feed a steady signal to establish the RMS baseline. + let mut block: Vec> = (0..256).map(|_| Complex::new(0.01, 0.01)).collect(); + nb.process(&mut block); + // Now inject a single massive spike at index 0. + let mut block2: Vec> = (0..256).map(|_| Complex::new(0.01, 0.01)).collect(); + block2[0] = Complex::new(10.0, 10.0); + nb.process(&mut block2); + // The spike should have been blanked (replaced by last clean sample). + let mag = (block2[0].re * block2[0].re + block2[0].im * block2[0].im).sqrt(); + assert!( + mag < 1.0, + "expected impulse to be blanked, got magnitude {}", + mag + ); + } + + #[test] + fn noise_blanker_disabled_passes_through() { + let mut nb = NoiseBlanker::new(false, 5.0); + let mut block = vec![Complex::new(10.0, 10.0); 4]; + nb.process(&mut block); + assert_eq!(block[0], Complex::new(10.0, 10.0)); + } } diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs index 4850b31..725c9b7 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/lib.rs @@ -60,6 +60,10 @@ pub struct SoapySdrRig { squelch_enabled: bool, /// Software squelch threshold (dBFS) on primary channel. squelch_threshold_db: f32, + /// Whether the noise blanker is enabled on the primary channel. + nb_enabled: bool, + /// Noise blanker impulse threshold multiplier. + nb_threshold: f64, /// Hidden AIS decoder channels (A and B) when available. ais_channel_indices: Option<(usize, usize)>, /// Virtual channel manager shared with external consumers (e.g. RigHandle). @@ -126,6 +130,8 @@ impl SoapySdrRig { squelch_hysteresis_db: f32, squelch_tail_ms: u32, max_virtual_channels: usize, + nb_enabled: bool, + nb_threshold: f64, ) -> DynResult { tracing::info!( "initialising SoapySDR backend (args={:?}, gain_mode={:?}, gain_db={}, max_gain_db={:?})", @@ -221,6 +227,10 @@ impl SoapySdrRig { hysteresis_db: squelch_hysteresis_db, tail_blocks: squelch_tail_blocks, }, + dsp::NoiseBlankerConfig { + enabled: nb_enabled, + threshold: nb_threshold as f32, + }, &all_channels, )); @@ -307,6 +317,8 @@ impl SoapySdrRig { agc_enabled, squelch_enabled, squelch_threshold_db, + nb_enabled, + nb_threshold, ais_channel_indices: Some((primary_channel_count, primary_channel_count + 1)), channel_manager, }; @@ -338,6 +350,8 @@ impl SoapySdrRig { 3.0, // squelch_hysteresis_db 180, // squelch_tail_ms 4, // max_virtual_channels + false, // nb_enabled + 10.0, // nb_threshold ) } @@ -654,6 +668,33 @@ impl RigCat for SoapySdrRig { }) } + fn set_sdr_noise_blanker<'a>( + &'a mut self, + enabled: bool, + threshold: f64, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + if !threshold.is_finite() { + return Err("noise blanker threshold must be finite".into()); + } + if !(1.0..=100.0).contains(&threshold) { + return Err("noise blanker threshold must be in range 1..=100".into()); + } + self.nb_enabled = enabled; + self.nb_threshold = threshold; + { + let dsps = self.pipeline.channel_dsps.read().unwrap(); + if let Some(dsp_arc) = dsps.get(self.primary_channel_idx) { + dsp_arc + .lock() + .unwrap() + .set_noise_blanker(enabled, threshold as f32); + } + } + Ok(()) + }) + } + fn get_signal_strength<'a>( &'a mut self, ) -> Pin> + Send + 'a>> { @@ -817,6 +858,8 @@ impl RigCat for SoapySdrRig { sdr_agc_enabled: Some(self.agc_enabled), sdr_squelch_enabled: Some(self.squelch_enabled), sdr_squelch_threshold_db: Some(self.squelch_threshold_db as f64), + sdr_nb_enabled: Some(self.nb_enabled), + sdr_nb_threshold: Some(self.nb_threshold), wfm_deemphasis_us: self.wfm_deemphasis_us, wfm_stereo: self.wfm_stereo, wfm_stereo_detected, diff --git a/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs b/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs index 333ff5d..d8bb9b5 100644 --- a/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs +++ b/src/trx-server/trx-backend/trx-backend-soapysdr/src/vchan_impl.rs @@ -418,7 +418,7 @@ impl VirtualChannelManager for SdrVirtualChannelManager { #[cfg(test)] mod tests { use super::*; - use crate::dsp::{MockIqSource, SdrPipeline}; + use crate::dsp::{MockIqSource, NoiseBlankerConfig, SdrPipeline}; fn make_pipeline() -> Arc { Arc::new(SdrPipeline::start( @@ -430,6 +430,7 @@ mod tests { 75, true, VirtualSquelchConfig::default(), + NoiseBlankerConfig::default(), &[(0.0, RigMode::USB, 3_000)], )) }