From 52c3760e902647db215bdc63d37afd1855d2472a Mon Sep 17 00:00:00 2001 From: Brumi-2021 <86470699+Brumi-2021@users.noreply.github.com> Date: Wed, 5 Mar 2025 04:27:18 +0100 Subject: [PATCH] Adding Wefax demodulation mode inside Audio App (#2539) * Adding_new_WFAX_GUI_mode_Audio_App * Wefax_APT_demodulation_structure * Solving REC Apt signal.wav from WFAX * clang format issues * correcting comments --- .../application/apps/analog_audio_app.cpp | 34 ++++++- .../application/apps/analog_audio_app.hpp | 29 ++++-- firmware/application/baseband_api.cpp | 12 +-- firmware/application/baseband_api.hpp | 3 +- firmware/application/freqman.hpp | 1 + firmware/application/freqman_db.cpp | 9 +- firmware/application/receiver_model.cpp | 32 +++++-- firmware/application/receiver_model.hpp | 10 +- firmware/baseband/audio_output.cpp | 2 +- firmware/baseband/dsp_demodulate.cpp | 35 +++++-- firmware/baseband/dsp_demodulate.hpp | 12 +++ firmware/baseband/dsp_hilbert.cpp | 85 ++++++++++++++++ firmware/baseband/dsp_hilbert.hpp | 13 +++ firmware/baseband/proc_am_audio.cpp | 28 ++++-- firmware/baseband/proc_am_audio.hpp | 4 +- firmware/common/dsp_fir_taps.hpp | 96 +++++++++++++++++-- firmware/common/dsp_iir_config.hpp | 6 ++ firmware/common/dsp_sos_config.hpp | 20 +++- firmware/common/message.hpp | 7 +- 19 files changed, 383 insertions(+), 55 deletions(-) diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 4726f44b5..b55250a7c 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -102,6 +102,24 @@ WFMOptionsView::WFMOptionsView( }; } +/* AMFMAptOptionsView *********************************************************/ + +AMFMAptOptionsView::AMFMAptOptionsView( + Rect parent_rect, + const Style* style) + : View{parent_rect} { + set_style(style); + + add_children({ + &label_config, + &options_config, + }); + + freqman_set_bandwidth_option(AMFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config + options_config.set_by_value(receiver_model.amfm_configuration()); + receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal. +} + /* SPECOptionsView *******************************************************/ SPECOptionsView::SPECOptionsView( @@ -176,8 +194,8 @@ AnalogAudioView::AnalogAudioView( auto modulation = receiver_model.modulation(); // This app doesn't handle "Capture" mode. - if (modulation > ReceiverModel::Mode::SpectrumAnalysis) - modulation = ReceiverModel::Mode::SpectrumAnalysis; + if (modulation > ReceiverModel::Mode::SpectrumAnalysis) // This two should be together in the last index position : SpectrumAnalysis = 4, and Capture = 5 + modulation = ReceiverModel::Mode::SpectrumAnalysis; // For sw simplicity , Wefax_mode, should NOT be added between. options_modulation.set_by_value(toUType(modulation)); options_modulation.on_change = [this](size_t, OptionsField::value_t v) { @@ -346,6 +364,12 @@ void AnalogAudioView::on_show_options_modulation() { text_ctcss.hidden(true); break; + case ReceiverModel::Mode::AMAudioFMApt: + widget = std::make_unique(options_view_rect, Theme::getInstance()->option_active); + waterfall.show_audio_spectrum_view(false); + text_ctcss.hidden(true); + break; + case ReceiverModel::Mode::SpectrumAnalysis: widget = std::make_unique(this, nbfm_view_rect, Theme::getInstance()->option_active); waterfall.show_audio_spectrum_view(false); @@ -387,6 +411,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) { case ReceiverModel::Mode::WidebandFMAudio: image_tag = portapack::spi_flash::image_tag_wfm_audio; break; + case ReceiverModel::Mode::AMAudioFMApt: // TODO pending to update it. + image_tag = portapack::spi_flash::image_tag_am_audio; + break; case ReceiverModel::Mode::SpectrumAnalysis: image_tag = portapack::spi_flash::image_tag_wideband_spectrum; break; @@ -421,6 +448,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) { case ReceiverModel::Mode::WidebandFMAudio: sampling_rate = 48000; break; + case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode. + sampling_rate = 12000; + break; default: break; } diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp index 33153376a..29f01d514 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -53,6 +53,24 @@ class AMOptionsView : public View { }}; }; +class AMFMAptOptionsView : public View { + public: + AMFMAptOptionsView(Rect parent_rect, const Style* style); + + private: + Text label_config{ + {0 * 8, 0 * 16, 2 * 8, 1 * 16}, + "BW", + }; + + OptionsField options_config{ + {3 * 8, 0 * 16}, + 6, // Max option length + { + // Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal. + }}; +}; + class NBFMOptionsView : public View { public: NBFMOptionsView(Rect parent_rect, const Style* style); @@ -206,12 +224,11 @@ class AnalogAudioView : public View { OptionsField options_modulation{ {0 * 8, 0 * 16}, 4, - { - {" AM ", toUType(ReceiverModel::Mode::AMAudio)}, - {"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)}, - {"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)}, - {"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)}, - }}; + {{" AM ", toUType(ReceiverModel::Mode::AMAudio)}, + {"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)}, + {"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)}, + {"WFAX", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod + {"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)}}}; AudioVolumeField field_volume{ {28 * 8, 0 * 16}}; diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index e288d9d66..452a63b1a 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -66,12 +66,12 @@ static void send_message(const Message* const message) { void AMConfig::apply() const { const AMConfigureMessage message{ - taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 5 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW) - taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 5 x AM mod. types. - decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k all rest AM modes) - channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW) - modulation, // var parameter . - audio_12k_hpf_300hz_config}; + taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, WFAX) + taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 6 x AM mod. types. (") + decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k and all rest AM modes) + channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,WFAX) + modulation, // var parameter . enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2} + audio_12k_iir_filter_config}; // var parameter , 300 Hz hpf all except Wefax (1.500Hz lpf) send_message(&message); audio::set_rate(audio::Rate::Hz_12000); } diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index a594fc273..e8e1bd584 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -36,9 +36,10 @@ namespace baseband { struct AMConfig { - const fir_taps_real<32> decim_2; // added to handle two types decim_2 9k, 6k + const fir_taps_real<32> decim_2; // added to handle two var types decim_2 9k, 6k const fir_taps_complex<64> channel; const AMConfigureMessage::Modulation modulation; + const iir_biquad_config_t audio_12k_iir_filter_config; // added to handle two var IIR filter types : 300 hpf(as before) , 1500Hz lpf for Wefax. void apply() const; }; diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index 74bf9198e..18c179f26 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -41,6 +41,7 @@ enum freqman_entry_modulation : uint8_t { AM_MODULATION = 0, NFM_MODULATION, WFM_MODULATION, + AMFM_MODULATION, // Added for Wefax. SPEC_MODULATION }; diff --git a/firmware/application/freqman_db.cpp b/firmware/application/freqman_db.cpp index 89eb52b14..59613e682 100644 --- a/firmware/application/freqman_db.cpp +++ b/firmware/application/freqman_db.cpp @@ -51,7 +51,7 @@ options_t freqman_modulations = { {"SPEC", 3}, }; -options_t freqman_bandwidths[4] = { +options_t freqman_bandwidths[5] = { { // AM {"DSB 9k", 0}, @@ -72,6 +72,10 @@ options_t freqman_bandwidths[4] = { {"180k", 1}, {"200k", 0}, }, + { + // AMFM for Wefax- + {"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (Wfax) tones. + }, { // SPEC -- TODO: these should be indexes. {"12k5", 12500}, @@ -99,8 +103,7 @@ options_t freqman_bandwidths[4] = { {"4500k", 4500000}, {"5000k", 5500000}, {"5500k", 5500000}, // Max capture, needs /4 decimation, (22Mhz sampling ADC). - }, -}; + }}; // TODO: these should be indexes. options_t freqman_steps = { diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index f7c795555..2dcf6b03a 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -39,13 +39,14 @@ using namespace portapack; namespace { -static constexpr std::array am_configs{{ +static constexpr std::array am_configs{{ // we config here all the non COMMON parameters to each AM modulation type in RX. - {taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth . - {taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments. - {taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 2K8 (+ 2K8) - {taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB}, // SSB LSB BW 2K8 (- 2K8) - {taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 0K7 (+ 0K7) used to get audio tone from CW Morse, assuming tx shifted +700hz aprox + {taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth . + {taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments. + {taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments. + {taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments. + {taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox + {taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax. }}; static constexpr std::array nbfm_configs{{ @@ -145,6 +146,17 @@ void ReceiverModel::set_am_configuration(uint8_t n) { } } +uint8_t ReceiverModel::amfm_configuration() const { + return settings_.amfm_config_index; +} + +void ReceiverModel::set_amfm_configuration(uint8_t n) { + if (n < am_configs.size()) { + settings_.amfm_config_index = n; + update_modulation(); + } +} + uint8_t ReceiverModel::nbfm_configuration() const { return settings_.nbfm_config_index; } @@ -303,6 +315,10 @@ void ReceiverModel::update_modulation() { update_am_configuration(); break; + case Mode::AMAudioFMApt: // Wefax , first step , USB demodulation from the AMAudio group, index 2 (USB+3K), TODO +FM subcarrier demod ? + update_amfm_configuration(); + break; + case Mode::NarrowbandFMAudio: update_nbfm_configuration(); break; @@ -321,6 +337,10 @@ void ReceiverModel::update_am_configuration() { am_configs[am_configuration()].apply(); } +void ReceiverModel::update_amfm_configuration() { + am_configs[amfm_configuration()].apply(); // update with different index for Wefax. +} + void ReceiverModel::update_nbfm_configuration() { nbfm_configs[nbfm_configuration()].apply(squelch_level()); } diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index ecdb32236..36ab728c1 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -40,8 +40,9 @@ class ReceiverModel { AMAudio = 0, NarrowbandFMAudio = 1, WidebandFMAudio = 2, - SpectrumAnalysis = 3, - Capture = 4 + AMAudioFMApt = 3, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod + SpectrumAnalysis = 4, + Capture = 5 }; struct settings_t { @@ -54,6 +55,7 @@ class ReceiverModel { bool rf_amp = false; Mode mode = Mode::NarrowbandFMAudio; uint8_t am_config_index = 0; + uint8_t amfm_config_index = 0; uint8_t nbfm_config_index = 0; uint8_t wfm_config_index = 0; uint8_t squelch_level = 80; @@ -87,6 +89,9 @@ class ReceiverModel { uint8_t am_configuration() const; void set_am_configuration(uint8_t n); + uint8_t amfm_configuration() const; + void set_amfm_configuration(uint8_t n); + uint8_t nbfm_configuration() const; void set_nbfm_configuration(uint8_t n); @@ -140,6 +145,7 @@ class ReceiverModel { void update_modulation(); void update_am_configuration(); + void update_amfm_configuration(); void update_nbfm_configuration(); void update_wfm_configuration(); diff --git a/firmware/baseband/audio_output.cpp b/firmware/baseband/audio_output.cpp index 2a35db70a..313661dd6 100644 --- a/firmware/baseband/audio_output.cpp +++ b/firmware/baseband/audio_output.cpp @@ -72,7 +72,7 @@ void AudioOutput::on_block(const buffer_f32_t& audio) { if (do_processing) { const auto audio_present_now = squelch.execute(audio); - hpf.execute_in_place(audio); + hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except WFAX with "lpf-coef". deemph.execute_in_place(audio); audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0); diff --git a/firmware/baseband/dsp_demodulate.cpp b/firmware/baseband/dsp_demodulate.cpp index 695668a16..835e279fb 100644 --- a/firmware/baseband/dsp_demodulate.cpp +++ b/firmware/baseband/dsp_demodulate.cpp @@ -24,6 +24,8 @@ #include "complex.hpp" #include "fxpt_atan2.hpp" #include "utility_m4.hpp" +#include "dsp_hilbert.hpp" +#include "dsp_modulate.hpp" #include @@ -63,12 +65,7 @@ buffer_f32_t SSB::execute( return {dst.p, src.count, src.sampling_rate}; } -/* -static inline float angle_approx_4deg0(const complex32_t t) { - const auto x = static_cast(t.imag()) / static_cast(t.real()); - return 16384.0f * x; -} -*/ + static inline float angle_approx_0deg27(const complex32_t t) { if (t.real()) { const auto x = static_cast(t.imag()) / static_cast(t.real()); @@ -82,6 +79,32 @@ static inline float angle_precise(const complex32_t t) { return atan2f(t.imag(), t.real()); } +buffer_f32_t SSB_FM::execute( // Added to handle WFAX (HF weather map )- + const buffer_c16_t& src, // input arg , pointer Complex c16 i,q buffer. + const buffer_f32_t& dst) { // input arg , pointer f32 buffer audio demodulated + complex16_t* src_p = src.p; // removed const ; init src_p pointer with the mem address pointed by src.p. + const auto src_end = &src.p[src.count]; + auto dst_p = dst.p; + float mag_sq_lpf_norm; + + while (src_p < src_end) { + // FM APT audio tone demod: real part (USB-differentiator) and AM tone demodulation + lpf (to remove the subcarrier.) + real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm); + *(dst_p++) = mag_sq_lpf_norm; // already normalized/32.768f and clipped to +1.0f for the wav file. + + real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm); + *(dst_p++) = mag_sq_lpf_norm; + + real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm); + *(dst_p++) = mag_sq_lpf_norm; + + real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm); + *(dst_p++) = mag_sq_lpf_norm; + } + + return {dst.p, src.count, src.sampling_rate}; +} + buffer_f32_t FM::execute( const buffer_c16_t& src, const buffer_f32_t& dst) { diff --git a/firmware/baseband/dsp_demodulate.hpp b/firmware/baseband/dsp_demodulate.hpp index d965ca080..1cb48699b 100644 --- a/firmware/baseband/dsp_demodulate.hpp +++ b/firmware/baseband/dsp_demodulate.hpp @@ -23,6 +23,7 @@ #define __DSP_DEMODULATE_H__ #include "dsp_types.hpp" +#include "dsp_hilbert.hpp" namespace dsp { namespace demodulate { @@ -47,6 +48,17 @@ class SSB { static constexpr float k = 1.0f / 32768.0f; }; +class SSB_FM { // Added to handle WFAX- + public: + buffer_f32_t execute( + const buffer_c16_t& src, + const buffer_f32_t& dst); + + private: + static constexpr float k = 1.0f / 32768.0f; + dsp::Real_to_Complex real_to_complex; // It is a member variable of SSB_FM. +}; + class FM { public: buffer_f32_t execute( diff --git a/firmware/baseband/dsp_hilbert.cpp b/firmware/baseband/dsp_hilbert.cpp index 642fa3d69..8f62dae85 100644 --- a/firmware/baseband/dsp_hilbert.cpp +++ b/firmware/baseband/dsp_hilbert.cpp @@ -21,6 +21,7 @@ #include "dsp_hilbert.hpp" #include "dsp_sos_config.hpp" +#include "utility_m4.hpp" namespace dsp { @@ -83,4 +84,88 @@ void HilbertTransform::execute(float in, float& out_i, float& out_q) { n = (n + 1) % 4; } +Real_to_Complex::Real_to_Complex() { + // No need to call a separate configuration method like "Real_to_Complex()" externally before using the execute() method + // This is the constructor for the Real_to_Complex class. + // It initializes the member variables and calls the configure function for the sos_input, sos_i, and sos_q filters. + // to ensure the object is ready to use right after instantiation. + + n = 0; + + sos_input.configure(full_band_lpf_config); + sos_i.configure(full_band_lpf_config); + sos_q.configure(full_band_lpf_config); + sos_mag_sq.configure(quarter_band_lpf_config); // for APT LPF subcarrier filter. (1/4 Nyquist fs/2 = 1/4 * 12Khz/2 = 1.5khz) +} + +void Real_to_Complex::execute(float in, float& out_mag_sq_lpf) { + // Full_band LPF means a LP filter with f_cut_off = fs/2; Full band = Full max band = 1/2 * fs_max = 1.0 x f_Nyquist = 1 * fs/2 = fs/2 + float a = 0, b = 0; + float out_i = 0, out_q = 0, out_mag_sq = 0; + // int32_t packed; + + float in_filtered = sos_input.execute(in) * 1.0f; // Anti-aliasing full band LPF, fc = fs/2= 6k, audio filter front-end. + + switch (n) { + case 0: + a = in_filtered; + b = 0; + break; + case 1: + a = 0; + b = -in_filtered; + break; + case 2: + a = -in_filtered; + b = 0; + break; + case 3: + a = 0; + b = in_filtered; + break; + } + + float i = sos_i.execute(a) * 1.0f; // better keep <1.0f to minimize recorded APT(t) black level artifacts.- + float q = sos_q.execute(b) * 1.0f; + + switch (n) { // shifting down -fs4 (fs = 12khz , fs/4 = 3khz) + case 0: + out_i = i; + out_q = q; + break; + case 1: + out_i = -q; + out_q = i; + break; + case 2: + out_i = -i; + out_q = -q; + break; + case 3: + out_i = q; + out_q = -i; + break; + } + + n = (n + 1) % 4; + + /* res = __smuad(val1,val2); p1 = val1[15:0] × val2[15:0] + p2 = val1[31:16] × val2[31:16] + res[31:0] = p1 + p2 + return res; */ + + // Not strict Magnitude complex calculation, it is a cross multiplication (lower 16 bit real x lower 16 imag) + 0 (higher 16 bits comp), + // but better visual results comparing real magnitude calculation, (better map diagonal lines reproduction, and less artifacts in APT signal(t) + out_mag_sq = __SMUAD(out_i, out_q); // "cross-magnitude" of the complex (out_i + j out_q) + out_mag_sq_lpf = sos_mag_sq.execute((out_mag_sq)) * 2.0f; // LPF quater band = 1.5khz APT signal + + out_mag_sq_lpf /= 32768.0f; // normalize ; + // Compress clipping positive APT signal [-1.5 ..1.5] input , converted to [-1.0 ...1.0] with "S" compressor gain shape. + if (out_mag_sq_lpf > 1.0f) { + out_mag_sq_lpf = 1.0f; // clipped signal at +1.0f, APT signal is positive, no need to clip -1.0 + } else { + out_mag_sq_lpf = out_mag_sq_lpf * (1.5f - ((out_mag_sq_lpf * out_mag_sq_lpf) / 2.0f)); + } +} + } /* namespace dsp */ diff --git a/firmware/baseband/dsp_hilbert.hpp b/firmware/baseband/dsp_hilbert.hpp index 8aad9ea9c..11596a5dd 100644 --- a/firmware/baseband/dsp_hilbert.hpp +++ b/firmware/baseband/dsp_hilbert.hpp @@ -39,6 +39,19 @@ class HilbertTransform { SOSFilter<5> sos_q = {}; }; +class Real_to_Complex { + public: + Real_to_Complex(); // Additional initialization + void execute(float in, float& out_mag_sq_lpf); + + private: + uint8_t n = 0; + SOSFilter<5> sos_input = {}; + SOSFilter<5> sos_i = {}; + SOSFilter<5> sos_q = {}; + SOSFilter<5> sos_mag_sq = {}; +}; + } /* namespace dsp */ #endif /*__DSP_HILBERT_H__*/ diff --git a/firmware/baseband/proc_am_audio.cpp b/firmware/baseband/proc_am_audio.cpp index a1304c464..a1e0c8330 100644 --- a/firmware/baseband/proc_am_audio.cpp +++ b/firmware/baseband/proc_am_audio.cpp @@ -27,6 +27,7 @@ #include "event_m4.hpp" #include +#include "dsp_hilbert.hpp" void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) { if (!configured) { @@ -44,16 +45,28 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) { // TODO: Feed channel_stats post-decimation data? feed_channel_stats(channel_out); - auto audio = demodulate(channel_out); + auto audio = demodulate(channel_out); // now 3 AM demodulation types : demod_am, demod_ssb, demod_ssb_fm (for Wefax) audio_compressor.execute_in_place(audio); audio_output.write(audio); } buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) { - if (modulation_ssb) { - return demod_ssb.execute(channel, audio_buffer); - } else { - return demod_am.execute(channel, audio_buffer); + switch (modulation_ssb) { // enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2} + case (int)(AMConfigureMessage::Modulation::DSB): + return demod_am.execute(channel, audio_buffer); + break; + + case (int)(AMConfigureMessage::Modulation::SSB): + return demod_ssb.execute(channel, audio_buffer); + break; + + case (int)(AMConfigureMessage::Modulation::SSB_FM): // Added to handle Weather Fax mode. + // chDbgPanic("case SSB_FM demodulation"); // Debug. + return demod_ssb_fm.execute(channel, audio_buffer); // Calling a derivative of demod_ssb (USB) , but with different FIR taps + FM audio tones demod. + break; + + default: + break; } } @@ -98,8 +111,9 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) { channel_filter_high_f = message.channel_filter.high_frequency_normalized * channel_filter_input_fs; channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs; channel_spectrum.set_decimation_factor(1.0f); - modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB); - audio_output.configure(message.audio_hpf_config); + // modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB); // originally we had just 2 AM types of demod. (DSB , SSB) + modulation_ssb = (int)message.modulation; // now sending by message , 3 types of AM demod : enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2} + audio_output.configure(message.audio_hpf_lpf_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there). configured = true; } diff --git a/firmware/baseband/proc_am_audio.hpp b/firmware/baseband/proc_am_audio.hpp index d63481186..8f651bb5b 100644 --- a/firmware/baseband/proc_am_audio.hpp +++ b/firmware/baseband/proc_am_audio.hpp @@ -63,9 +63,11 @@ class NarrowbandAMAudio : public BasebandProcessor { int32_t channel_filter_transition = 0; bool configured{false}; - bool modulation_ssb = false; + // bool modulation_ssb = false; // Origianlly we only had 2 AM demod types {DSB = 0, SSB = 1} , and we could handle it with bool var , 1 bit. + int8_t modulation_ssb = 0; // Now we have 3 AM demod types we will send now index integer {DSB = 0, SSB = 1, SSB_FM = 2} dsp::demodulate::AM demod_am{}; dsp::demodulate::SSB demod_ssb{}; + dsp::demodulate::SSB_FM demod_ssb_fm{}; // added for Wfax mode. FeedForwardCompressor audio_compressor{}; AudioOutput audio_output{}; diff --git a/firmware/common/dsp_fir_taps.hpp b/firmware/common/dsp_fir_taps.hpp index 7ba171a32..c04fe190d 100644 --- a/firmware/common/dsp_fir_taps.hpp +++ b/firmware/common/dsp_fir_taps.hpp @@ -553,14 +553,14 @@ constexpr fir_taps_real<32> taps_6k0_decim_2{ }}, }; -// IFIR prototype filter fs=48000 ; pass=4500 (cutt off -3dBs) , stop=8000 (<-60dBs), decim=4, fout=12000 +// IFIR prototype filter fs=48000 ; pass=4500 (cutoff -3dBs) , stop=8000 (<-60dBs), decim=4, fout=12000 // For Europe AM commercial broadcasting stations in LF/MF/HF, Emissions Designator 9K00A3E Bandwidth: 9.00 kHz (derivated from taps_6k0_decim_2 ) -// Pre-decimate LPF FIR filter design Created with SciPy Python with the "window method", num_taps = 32, cut_off = 5150. sample_rate = 48000 # Hz, -// Created with h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2, window=('chebwin',50)) , achieving good STOP band plot < -60 dB's with some ripple. +// Pre-decimate LPF FIR filter design Created with SciPy Python with the "window method", num_taps = 32, cutoff = 5150. sample_rate = 48000 # Hz, +// Created with h = signal.firwin(num_taps, cutoff, nyq=sample_rate/2, window=('chebwin',50)) , achieving good STOP band plot < -60 dB's with some ripple. // post-scaled h taps to avoid decimals , targeting <= similar int values as previous taps_6k0_dsb_channel peak < 32.767 (2 exp 15) and similar H(f)gain constexpr fir_taps_real<32> taps_9k0_decim_2{ - .low_frequency_normalized = -4500.0f / 48000.0f, // Negative -cutt off freq -3dB (real achieved data ,in the plot and measurements) - .high_frequency_normalized = 4500.0f / 48000.0f, // Positive +cutt off freq -3dB (idem) + .low_frequency_normalized = -4500.0f / 48000.0f, // Negative -cutoff freq -3dB (real achieved data ,in the plot and measurements) + .high_frequency_normalized = 4500.0f / 48000.0f, // Positive +cutoff freq -3dB (idem) .transition_normalized = 3500.0f / 48000.0f, // 3500 Hz = (8000 Hz - 4500 Hz) (both from plot H(f) curve plot) .taps = {{-53, -30, 47, 198, 355, 372, 89, -535, -1307, -1771, -1353, 370, 3384, 7109, 10535, 12591, @@ -644,14 +644,14 @@ constexpr fir_taps_complex<64> taps_6k0_dsb_channel{ }}, }; -// Channel filter: fs=12000, pass=4500 (cutt off -3dBs), stop=4940 (<-60dBs), decim=1, fout=12000 (*1) real frec pass / stop , based on plotted H(f) curve) +// Channel filter: fs=12000, pass=4500 (cutoff -3dBs), stop=4940 (<-60dBs), decim=1, fout=12000 (*1) real frec pass / stop , based on plotted H(f) curve) // For Europe AM commercial broadcasting stations in LF/MF/HF, Emissions Designator 9K00A3E Bandwidth: 9.00 kHz (derivative from taps_6k0_dsb_channel) -// FIR filter design created with SciPy Python using "window method"; selected design parameters: num_taps = 64, cut_off = 4575. sample_rate = 12000 # Hz, -// Created with : h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2, window=('chebwin',50)) , achieving real plot curve (*1) with peak stop band ripple -60dBs. +// FIR filter design created with SciPy Python using "window method"; selected design parameters: num_taps = 64, cutoff = 4575. sample_rate = 12000 # Hz, +// Created with : h = signal.firwin(num_taps, cutoff, nyq=sample_rate/2, window=('chebwin',50)) , achieving real plot curve (*1) with peak stop band ripple -60dBs. // post-scaled h taps to avoid decimals , targeting <= similar int values as previous taps_6k0_dsb_channel peak < 32.767 (2 exp 15), (29625) and similar H(f)gain constexpr fir_taps_complex<64> taps_9k0_dsb_channel{ - .low_frequency_normalized = -4500.0f / 12000.0f, // Negative -cutt off freq -3dB (in the H(f) curve plot) - .high_frequency_normalized = 4500.0f / 12000.0f, // Positive +cutt off freq -3dB (in the H(f) curve plot) + .low_frequency_normalized = -4500.0f / 12000.0f, // Negative -cutoff freq -3dB (in the H(f) curve plot) + .high_frequency_normalized = 4500.0f / 12000.0f, // Positive +cutoff freq -3dB (in the H(f) curve plot) .transition_normalized = 440.0f / 12000.0f, // 440Hz = (4940 Hz -4500 Hz) cut-3dB's (both data comes from H(f) curve plot and confirmed by measurements ) .taps = {{ {2, 0}, @@ -945,6 +945,82 @@ constexpr fir_taps_complex<64> taps_0k7_usb_channel{ }}, }; +// USB AM+FM for Wefax (Weather fax RX) , based USB AM with truncated Differentiator band limmited cuttoff 2.400Hz for Audio Tones FM dem. /////////////////// + +// IFIR prototype filter: fs=12000, pass=2600, stop=3200, decim=1, fout=12000 // stop band minimum att < -48 dB's (+3300 Hz min atten peak) , rest <50 to -60dB's +constexpr fir_taps_complex<64> taps_2k6_usb_wefax_channel{ + .low_frequency_normalized = 0, + .high_frequency_normalized = 2600.0f / 12000.0f, + .transition_normalized = 600.0f / 12000.0f, + .taps = {{{-14 + 2}, + {-11 - 5}, + {-2 - 8}, + {6 - 5}, + {13 + 1}, + {15 + 14}, + {0 + 26}, + {-22 + 13}, + {-13 - 11}, + {7 - 1}, + {-20 + 17}, + {-47 - 37}, + {33 - 89}, + {122 + 8}, + {19 + 131}, + {-124 + 26}, + {1 - 123}, + {158 + 52}, + {-94 + 245}, + {-363 - 91}, + {36 - 468}, + {524 - 37}, + {67 + 531}, + {-552 + 5}, + {136 - 686}, + {1013 + 258}, + {-204 + 1527}, + {-2104 + 168}, + {-900 - 2529}, + {2577 - 1881}, + {2868 + 2122}, + {-1209 + 3570}, + {-3768 - 52}, + {-1043 - 3412}, + {2634 - 1801}, + {2083 + 1693}, + {-861 + 1927}, + {-1507 - 318}, + {95 - 1041}, + {692 + 100}, + {-189 + 519}, + {-478 - 241}, + {210 - 481}, + {454 + 122}, + {-35 + 372}, + {-262 + 7}, + {4 - 166}, + {116 + 40}, + {-66 + 108}, + {-117 - 62}, + {33 - 117}, + {95 - 2}, + {19 + 57}, + {-23 + 13}, + {3 - 7}, + {6 + 16}, + {-20 + 16}, + {-25 - 9}, + {-8 - 19}, + {4 - 12}, + {7 - 4}, + {7 + 4}, + {2 + 12}, + {-7 + 13} + + }} + +}; + // WFM 200KF8E emission type ////////////////////////////////////////////// // IFIR image-reject filter: fs=3072000, pass=100000, stop=484000, decim=4, fout=768000 diff --git a/firmware/common/dsp_iir_config.hpp b/firmware/common/dsp_iir_config.hpp index 177475df8..93e93d179 100644 --- a/firmware/common/dsp_iir_config.hpp +++ b/firmware/common/dsp_iir_config.hpp @@ -55,6 +55,12 @@ constexpr iir_biquad_config_t audio_12k_hpf_300hz_config{ {0.89485861f, -1.78971721f, 0.89485861f}, {1.00000000f, -1.77863178f, 0.80080265f}}; +// scipy.signal.butter(2, 1500 / 6000.0, 'low', analog=False) +constexpr iir_biquad_config_t audio_12k_lpf_1500hz_config{ + // Added to lpf the audio in wefax mode , before sending to SD card or spk. + {0.09763107f, 0.19526215f, 0.09763107f}, + {1.00000000f, -0.94280904f, 0.33333333f}}; + // scipy.signal.butter(2, 300 / 4000.0, 'highpass', analog=False) constexpr iir_biquad_config_t audio_8k_hpf_300hz_config{ {0.84645925f, -1.69291851f, 0.84645925f}, diff --git a/firmware/common/dsp_sos_config.hpp b/firmware/common/dsp_sos_config.hpp index 6a0ecb00f..b55dc01d6 100644 --- a/firmware/common/dsp_sos_config.hpp +++ b/firmware/common/dsp_sos_config.hpp @@ -25,7 +25,7 @@ #include "dsp_iir.hpp" // scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.5, btype = 'lowpass', output="sos") - +// 3khz cutofff @fs:12Khz , used in Hilbert constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = { {0.02339042f, 0.0411599f, 0.02339042f, 1.0f, -0.95317621f, 0.33446485f}, {1.0f, 0.82196114f, 1.0f, 1.0f, -0.50327735f, 0.63611027f}, @@ -33,4 +33,22 @@ constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = { {1.0f, 0.14394122f, 1.0f, 1.0f, -0.04368236f, 0.94798064f}, {1.0f, 0.08720754, 1.0f, 1.0f, 0.00220944f, 0.98743139f}}; +// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.99, btype = 'lowpass', output="sos") +// 6khz cutofff @fs:12Khz , used in WFAX demod. +constexpr iir_biquad_df2_config_t full_band_lpf_config[5] = { + {0.88095275f, 1.76184993f, 0.88095275f, 1.0f, 1.89055677f, 0.89616378f}, + {1.0f, 1.99958798f, 1.0f, 1.0f, 1.9781807f, 0.98002549f}, + {1.0f, 1.99928911f, 1.0f, 1.0f, 1.99328036f, 0.99447816f}, + {1.0f, 1.99914562f, 1.0f, 1.0f, 1.997254f, 0.99828526f}, + {1.0f, 1.99909558f, 1.0f, 1.0f, 1.9986187f, 0.99960319f}}; + +// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5 , rs = 60.0, Wn = 0.25, btype = 'lowpass', output="sos") +// 1.5khz cutofff @fs:12Khz, used in WFAX demod. +constexpr iir_biquad_df2_config_t quarter_band_lpf_config[5] = { + {0.00349312f, 0.00319397f, 0.00349312f, 1.0f, -1.53025211f, 0.6203438f}, + {1.0f, -0.83483341f, 1.0f, 1.0f, -1.47619047f, 0.77120659f}, + {1.0f, -1.23050154f, 1.0f, 1.0f, -1.43058949f, 0.9000896f}, + {1.0f, -1.33837384f, 1.0f, 1.0f, -1.41007744f, 0.96349953f}, + {1.0f, -1.36921549f, 1.0f, 1.0f, -1.40680439f, 0.9910884f}}; + #endif /*__DSP_SOS_CONFIG_H__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index a619816ca..080519255 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -585,6 +585,7 @@ class AMConfigureMessage : public Message { enum class Modulation : int32_t { DSB = 0, SSB = 1, + SSB_FM = 2, // Added new for RX Wefax mode, to demodulate APT signal ,FM modulated inside audio subcarrier tones, and then up broadcasted in SSB USB . }; constexpr AMConfigureMessage( @@ -593,14 +594,14 @@ class AMConfigureMessage : public Message { const fir_taps_real<32> decim_2_filter, const fir_taps_complex<64> channel_filter, const Modulation modulation, - const iir_biquad_config_t audio_hpf_config) + const iir_biquad_config_t audio_hpf_lpf_config) : Message{ID::AMConfigure}, decim_0_filter(decim_0_filter), decim_1_filter(decim_1_filter), decim_2_filter(decim_2_filter), channel_filter(channel_filter), modulation{modulation}, - audio_hpf_config(audio_hpf_config) { + audio_hpf_lpf_config(audio_hpf_lpf_config) { } const fir_taps_real<24> decim_0_filter; @@ -608,7 +609,7 @@ class AMConfigureMessage : public Message { const fir_taps_real<32> decim_2_filter; const fir_taps_complex<64> channel_filter; const Modulation modulation; - const iir_biquad_config_t audio_hpf_config; + const iir_biquad_config_t audio_hpf_lpf_config; }; // TODO: Put this somewhere else, or at least the implementation part.