Fix LF/MF RX centering with adaptive FS4 tuning (#3319)

* Fix LF/MF RX tuning with adaptive FS4 shift

* Enable adaptive FS4 tuning in Waterfall Designer
This commit is contained in:
Brumi-2021
2026-09-15 19:11:30 +02:00
committed by GitHub
parent b8ad44c431
commit b01b23981a
14 changed files with 191 additions and 16 deletions
+23 -1
View File
@@ -45,6 +45,10 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
return;
}
// Only the processing thread applies frequency-only direction changes.
if (requested_fs4_direction_.load(std::memory_order_relaxed) != applied_fs4_direction_)
configure_fs4(decim_0_taps_);
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto audio_decim_0_out = audio_decim_0.execute(decim_0_out, dst_buffer);
@@ -125,8 +129,26 @@ buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) {
}
}
void NarrowbandAMAudio::configure_fs4(const std::array<int16_t, 24>& taps) {
// Keep the short first-stage update coherent with the processing thread.
chSysLock();
decim_0_taps_ = taps;
const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed);
using Shift = dsp::decimate::FIRC8xR16x24FS4Decim4::Shift;
decim_0.configure(decim_0_taps_, 33554432,
direction == RxFs4Direction::Up ? Shift::Up : Shift::Down);
applied_fs4_direction_ = direction;
chSysUnlock();
}
void NarrowbandAMAudio::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::RxFs4Config:
requested_fs4_direction_.store(
static_cast<const RxFs4ConfigMessage*>(message)->direction,
std::memory_order_relaxed);
break;
case Message::ID::UpdateSpectrum:
case Message::ID::SpectrumStreamingConfig:
channel_spectrum.on_message(message);
@@ -162,7 +184,7 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
constexpr size_t channel_filter_input_fs = decim_2_output_fs;
// const size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor;
decim_0.configure(message.decim_0_filter.taps, 33554432);
configure_fs4(message.decim_0_filter.taps);
audio_decim_0.configure(taps_audio_wide_halfband_0.taps);
translating_decim_1.configure(
message.decim_1_filter.taps, audio_decim_0_output_fs);
+5
View File
@@ -34,6 +34,7 @@
#include "audio_output.hpp"
#include "filtered_spectrum_collector.hpp"
#include <atomic>
#include <cstdint>
class NarrowbandAMAudio : public BasebandProcessor {
@@ -59,6 +60,10 @@ class NarrowbandAMAudio : public BasebandProcessor {
audio.size()};
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
std::array<int16_t, 24> decim_0_taps_{};
std::atomic<RxFs4Direction> requested_fs4_direction_{RxFs4Direction::Down};
RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down};
void configure_fs4(const std::array<int16_t, 24>& taps);
dsp::decimate::FIRC16xR16x16Decim2 audio_decim_0{};
dsp::FrequencyTranslatingDecimator32By8 translating_decim_1{};
dsp::decimate::FIRAndDecimateComplex decim_2{};
+18 -6
View File
@@ -34,6 +34,12 @@ CaptureProcessor::CaptureProcessor() {
}
void CaptureProcessor::execute(const buffer_c8_t& buffer) {
const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed);
if (direction != applied_fs4_direction_) {
decim_0.configure_fs4(decim_0_taps_, direction);
applied_fs4_direction_ = direction;
}
auto decim_0_out = decim_0.execute(buffer, dst_buffer);
auto out_buffer = decim_1.execute(decim_0_out, dst_buffer);
@@ -67,6 +73,12 @@ void CaptureProcessor::on_beep_message(const AudioBeepMessage& message) {
void CaptureProcessor::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::RxFs4Config:
requested_fs4_direction_.store(
static_cast<const RxFs4ConfigMessage*>(message)->direction,
std::memory_order_relaxed);
break;
case Message::ID::UpdateSpectrum:
case Message::ID::SpectrumStreamingConfig:
channel_spectrum.on_message(message);
@@ -125,34 +137,34 @@ void CaptureProcessor::sample_rate_config(const SampleRateConfigMessage& message
switch (message.oversample_rate) {
case OversampleRate::x4:
// M4 can't handle 2 decimation passes for sample rates needing x4.
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
configure_fs4<FIRC8xR16x24FS4Decim4>(taps_200k_decim_0.taps);
decim_1.set<NoopDecim>();
break;
case OversampleRate::x8:
// M4 can't handle 2 decimation passes for sample rates <= 600k.
if (message.sample_rate < 600'000) {
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
configure_fs4<FIRC8xR16x24FS4Decim4>(taps_200k_decim_0.taps);
decim_1.set<FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps);
} else {
// Using 180k taps to provide better filtering with a single pass.
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_180k_wfm_decim_0.taps);
configure_fs4<FIRC8xR16x24FS4Decim8>(taps_180k_wfm_decim_0.taps);
decim_1.set<NoopDecim>();
}
break;
case OversampleRate::x16:
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps);
configure_fs4<FIRC8xR16x24FS4Decim8>(taps_200k_decim_0.taps);
decim_1.set<FIRC16xR16x16Decim2>().configure(taps_200k_decim_1.taps);
break;
case OversampleRate::x32:
decim_0.set<FIRC8xR16x24FS4Decim4>().configure(taps_200k_decim_0.taps);
configure_fs4<FIRC8xR16x24FS4Decim4>(taps_200k_decim_0.taps);
decim_1.set<FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps);
break;
case OversampleRate::x64:
decim_0.set<FIRC8xR16x24FS4Decim8>().configure(taps_200k_decim_0.taps);
configure_fs4<FIRC8xR16x24FS4Decim8>(taps_200k_decim_0.taps);
decim_1.set<FIRC16xR16x32Decim8>().configure(taps_16k0_decim_1.taps);
break;
+27
View File
@@ -33,6 +33,8 @@
#include "message.hpp"
#include <array>
#include <atomic>
#include <type_traits>
#include <memory>
#include <tuple>
#include <variant>
@@ -65,6 +67,16 @@ class MultiDecimator {
decimator_);
}
// Reconfigure only the active first-stage variant; preserve its factor.
void configure_fs4(const std::array<int16_t, 24>& taps, RxFs4Direction direction) {
std::visit([&](auto& decimator) {
using Shift = typename std::decay_t<decltype(decimator)>::Shift;
decimator.configure(taps, dsp::decimate::c8_to_c32_sat_scalar,
direction == RxFs4Direction::Up ? Shift::Up : Shift::Down);
},
decimator_);
}
size_t decimation_factor() const {
return std::visit(
[](auto&& arg) -> size_t {
@@ -125,6 +137,21 @@ class CaptureProcessor : public BasebandProcessor {
size_t spectrum_interval_samples = 0;
size_t spectrum_samples = 0;
std::array<int16_t, 24> decim_0_taps_{};
std::atomic<RxFs4Direction> requested_fs4_direction_{RxFs4Direction::Down};
RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down};
template <typename Decimator>
void configure_fs4(const std::array<int16_t, 24>& taps) {
// Variant selection, taps and direction must change together.
chSysLock();
decim_0.set<Decimator>();
decim_0_taps_ = taps;
applied_fs4_direction_ = requested_fs4_direction_.load(std::memory_order_relaxed);
decim_0.configure_fs4(decim_0_taps_, applied_fs4_direction_);
chSysUnlock();
}
/* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
+23 -1
View File
@@ -50,6 +50,10 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
return;
}
// Only the processing thread applies frequency-only direction changes.
if (requested_fs4_direction_.load(std::memory_order_relaxed) != applied_fs4_direction_)
configure_fs4(decim_0_taps_);
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto audio_decim_0_out = audio_decim_0.execute(decim_0_out, dst_buffer);
@@ -137,8 +141,26 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
}
}
void NarrowbandFMAudio::configure_fs4(const std::array<int16_t, 24>& taps) {
// Keep the short first-stage update coherent with the processing thread.
chSysLock();
decim_0_taps_ = taps;
const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed);
using Shift = dsp::decimate::FIRC8xR16x24FS4Decim4::Shift;
decim_0.configure(decim_0_taps_, 33554432,
direction == RxFs4Direction::Up ? Shift::Up : Shift::Down);
applied_fs4_direction_ = direction;
chSysUnlock();
}
void NarrowbandFMAudio::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::RxFs4Config:
requested_fs4_direction_.store(
static_cast<const RxFs4ConfigMessage*>(message)->direction,
std::memory_order_relaxed);
break;
case Message::ID::UpdateSpectrum:
case Message::ID::SpectrumStreamingConfig:
channel_spectrum.on_message(message);
@@ -177,7 +199,7 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) {
const size_t demod_input_fs = channel_filter_output_fs;
decim_0.configure(message.decim_0_filter.taps, 33554432);
configure_fs4(message.decim_0_filter.taps);
audio_decim_0.configure(taps_audio_wide_halfband_0.taps);
translating_decim_1.configure(
message.decim_1_filter.taps, audio_decim_0_output_fs);
+5
View File
@@ -35,6 +35,7 @@
#include "audio_output.hpp"
#include "filtered_spectrum_collector.hpp"
#include <atomic>
#include <cstdint>
#define Z_MIN_FILTER_COUNT 224
@@ -72,6 +73,10 @@ class NarrowbandFMAudio : public BasebandProcessor {
sizeof(tone) / sizeof(int16_t)};
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
std::array<int16_t, 24> decim_0_taps_{};
std::atomic<RxFs4Direction> requested_fs4_direction_{RxFs4Direction::Down};
RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down};
void configure_fs4(const std::array<int16_t, 24>& taps);
dsp::decimate::FIRC16xR16x16Decim2 audio_decim_0{};
dsp::FrequencyTranslatingDecimator32By8 translating_decim_1{};
dsp::decimate::FIRAndDecimateComplex channel_filter{};
+24 -2
View File
@@ -35,6 +35,10 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
return;
}
// Only the processing thread applies frequency-only direction changes.
if (requested_fs4_direction_.load(std::memory_order_relaxed) != applied_fs4_direction_)
configure_fs4(decim_0_taps_);
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto channel = decim_1.execute(decim_0_out, dst_buffer);
@@ -159,8 +163,26 @@ void WidebandFMAudio::post_message(const buffer_c16_t& data) {
fft_step = 0;
}
void WidebandFMAudio::configure_fs4(const std::array<int16_t, 24>& taps) {
// Keep the short first-stage update coherent with the processing thread.
chSysLock();
decim_0_taps_ = taps;
const auto direction = requested_fs4_direction_.load(std::memory_order_relaxed);
using Shift = dsp::decimate::FIRC8xR16x24FS4Decim4::Shift;
decim_0.configure(decim_0_taps_, dsp::decimate::c8_to_c32_sat_scalar,
direction == RxFs4Direction::Up ? Shift::Up : Shift::Down);
applied_fs4_direction_ = direction;
chSysUnlock();
}
void WidebandFMAudio::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::RxFs4Config:
requested_fs4_direction_.store(
static_cast<const RxFs4ConfigMessage*>(message)->direction,
std::memory_order_relaxed);
break;
case Message::ID::UpdateSpectrum:
case Message::ID::SpectrumStreamingConfig:
channel_spectrum.on_message(message);
@@ -188,7 +210,7 @@ void WidebandFMAudio::configure_wfm(const WFMConfigureMessage& message) {
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
constexpr size_t decim_1_input_fs = decim_0_output_fs;
decim_0.configure(message.decim_0_filter.taps);
configure_fs4(message.decim_0_filter.taps);
// decim_1.configure(message.decim_1_filter.taps); // Original .
// TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps .
@@ -219,7 +241,7 @@ void WidebandFMAudio::configure_wfmam(const WFMAMConfigureMessage& message) {
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
constexpr size_t decim_1_input_fs = decim_0_output_fs;
decim_0.configure(message.decim_0_filter.taps);
configure_fs4(message.decim_0_filter.taps);
// decim_1.configure(message.decim_1_filter.taps); // Original .
// TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps .
+6
View File
@@ -23,6 +23,8 @@
#ifndef __PROC_WFM_AUDIO_H__
#define __PROC_WFM_AUDIO_H__
#include <atomic>
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
@@ -99,6 +101,10 @@ class WidebandFMAudio : public BasebandProcessor {
complex_audio.size()};
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{};
std::array<int16_t, 24> decim_0_taps_{};
std::atomic<RxFs4Direction> requested_fs4_direction_{RxFs4Direction::Down};
RxFs4Direction applied_fs4_direction_{RxFs4Direction::Down};
void configure_fs4(const std::array<int16_t, 24>& taps);
// dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; //original condition , before adding wfmam
// decim_1 will handle different types of FIR filters depending on selection.