diff --git a/firmware/application/apps/analog_audio_app.cpp b/firmware/application/apps/analog_audio_app.cpp index 840475cc6..a5c51f4a5 100644 --- a/firmware/application/apps/analog_audio_app.cpp +++ b/firmware/application/apps/analog_audio_app.cpp @@ -114,6 +114,26 @@ WFMOptionsView::WFMOptionsView( }; } +/* WFMAMAptOptionsView *******************************************************/ + +WFMAMAptOptionsView::WFMAMAptOptionsView( + Rect parent_rect, + const Style* style) + : View{parent_rect} { + set_style(style); + + add_children({ + &label_config, + &options_config, + }); + + freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config + options_config.set_by_value(receiver_model.wfmam_configuration()); + options_config.on_change = [this](size_t, OptionsField::value_t n) { + receiver_model.set_wfmam_configuration(n); + }; +} + /* AMFMAptOptionsView *********************************************************/ AMFMAptOptionsView::AMFMAptOptionsView( @@ -415,6 +435,12 @@ void AnalogAudioView::on_show_options_modulation() { text_ctcss.hidden(true); break; + case ReceiverModel::Mode::WFMAudioAMApt: + widget = std::make_unique(options_view_rect, Theme::getInstance()->option_active); + waterfall.show_audio_spectrum_view(true); + text_ctcss.hidden(true); + break; + case ReceiverModel::Mode::AMAudioFMApt: widget = std::make_unique(this, options_view_rect, Theme::getInstance()->option_active); waterfall.show_audio_spectrum_view(false); @@ -462,7 +488,10 @@ 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. + case ReceiverModel::Mode::WFMAudioAMApt: + image_tag = portapack::spi_flash::image_tag_wfm_audio; + break; + case ReceiverModel::Mode::AMAudioFMApt: image_tag = portapack::spi_flash::image_tag_am_audio; break; case ReceiverModel::Mode::SpectrumAnalysis: @@ -501,7 +530,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) { case ReceiverModel::Mode::WidebandFMAudio: sampling_rate = 48000; break; - case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode. + case ReceiverModel::Mode::WFMAudioAMApt: + sampling_rate = 12000; + break; + case ReceiverModel::Mode::AMAudioFMApt: sampling_rate = 12000; break; default: diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp index 39814039a..6a239eb17 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -74,7 +74,7 @@ class AMFMAptOptionsView : public View { OptionsField options_config{ {3 * 8, 0 * 16}, - 6, // Max option length chars + 6, // Max option length chars "USB+FM" { // 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. }}; @@ -132,6 +132,23 @@ class WFMOptionsView : public View { }}; }; +class WFMAMAptOptionsView : public View { + public: + WFMAMAptOptionsView(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}, + 10, // Max option char length "FM+AM(DSB)" + { + // Using common messages from freqman_ui.cpp + }}; +}; + class SPECOptionsView : public View { public: SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style); @@ -215,7 +232,7 @@ class AnalogAudioView : public View { uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2) uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2) - // + app_settings::SettingsManager settings_{ "rx_audio", app_settings::Mode::RX, @@ -261,7 +278,8 @@ class AnalogAudioView : public View { {"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)}, {"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)}, {"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)}, - {"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)} // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod + {"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod + {"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT }}; AudioVolumeField field_volume{ diff --git a/firmware/application/apps/ui_freqman.cpp b/firmware/application/apps/ui_freqman.cpp index 8df9c8872..e543fa00e 100644 --- a/firmware/application/apps/ui_freqman.cpp +++ b/firmware/application/apps/ui_freqman.cpp @@ -45,7 +45,7 @@ using option_db_t = std::pair; using options_db_t = std::vector; extern options_db_t freqman_modulations; -extern options_db_t freqman_bandwidths[5]; +extern options_db_t freqman_bandwidths[6]; // extern options_db_t freqman_steps; // now included via ui_receiver.hpp extern options_db_t freqman_steps_short; diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index e989ea52e..2b5d8ae46 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -93,9 +93,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const { void WFMConfig::apply() const { const WFMConfigureMessage message{ - decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0 - decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1 - taps_64_lp_156_198, + decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0 + decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1 + taps_64_lp_156_198, // Fixed channel audio filter 15khz 75000, audio_48k_hpf_30hz_config, audio_48k_deemph_2122_6_config}; @@ -103,6 +103,18 @@ void WFMConfig::apply() const { audio::set_rate(audio::Rate::Hz_48000); } +void WFMAMConfig::apply() const { + const WFMAMConfigureMessage message{ + decim_0, // Fixed 24 taps array : taps_16k0_decim_0 + decim_1, // Fixed 32 taps array : taps_84k_wfm_decim_1 + taps_64_lp_1875_2166, // Fixed channel audio filter , 64 taps array , to filter DSB AM 2k4 carrier before demod. AM . + 17000, // NOAA satellite tx , FM deviation = +-17Khz. + apt_audio_12k_notch_2k4_config, + apt_audio_12k_lpf_2000hz_config}; + send_message(&message); + audio::set_rate(audio::Rate::Hz_12000); +} + void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) { shared_memory.bb_data.tones_data.tone_defs[index].delta = delta; shared_memory.bb_data.tones_data.tone_defs[index].duration = duration; diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index 06598962b..43153b336 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -56,12 +56,19 @@ struct NBFMConfig { }; struct WFMConfig { - const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT + const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 40K- const fir_taps_real<16> decim_1; void apply() const; }; +struct WFMAMConfig { + const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT + const fir_taps_real<32> decim_1; + + void apply() const; +}; + void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration); void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out); void kill_tone(); diff --git a/firmware/application/external/debug_pmem/ui_debug_pmem.cpp b/firmware/application/external/debug_pmem/ui_debug_pmem.cpp index 2c9dbfdfc..8356569f2 100644 --- a/firmware/application/external/debug_pmem/ui_debug_pmem.cpp +++ b/firmware/application/external/debug_pmem/ui_debug_pmem.cpp @@ -157,6 +157,9 @@ bool DebugDumpView::debug_dump_func() { case ReceiverModel::Mode::WidebandFMAudio: pmem_dump_file.write_line("modulation: Mode::WidebandFMAudio"); break; + case ReceiverModel::Mode::WFMAudioAMApt: + pmem_dump_file.write_line("modulation: Mode::WFMAudioAMApt"); + break; case ReceiverModel::Mode::SpectrumAnalysis: pmem_dump_file.write_line("modulation: Mode::SpectrumAnalysis"); break; diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index c4eba59e0..ab39a08e0 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -42,7 +42,8 @@ enum freqman_entry_modulation : uint8_t { NFM_MODULATION, WFM_MODULATION, SPEC_MODULATION, - AMFM_MODULATION // Added for Wefax. + AMFM_MODULATION, // Added for HF Wefax.demod APT signal + WFMAM_MODULATION // Added for NOAA 137 Mhz satellite band, demod APT signal. }; // TODO: Can these be removed after Recon is migrated to FreqmanDB? diff --git a/firmware/application/freqman_db.cpp b/firmware/application/freqman_db.cpp index 61150816f..85c83a755 100644 --- a/firmware/application/freqman_db.cpp +++ b/firmware/application/freqman_db.cpp @@ -49,10 +49,11 @@ options_t freqman_modulations = { {"NFM", 1}, {"WFM", 2}, {"SPEC", 3}, - {"AMFM", 4}, + {"AMFM", 4}, // To handle HF Wefax AM and FM demod. inside Audio App. + {"FMAM", 5}, // To handle NOAA 137 Mhz Sat FM and AM demod inside Audio App. }; -options_t freqman_bandwidths[5] = { +options_t freqman_bandwidths[6] = { { // AM {"DSB 9k", 0}, @@ -103,7 +104,11 @@ options_t freqman_bandwidths[5] = { }, { // AMFM for Wefax- - {"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones. + {"USB+FM", 5}, // Fixed RX demod. AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones. + }, + { + // WFMAM for NOAA satellites, 137 Mhz band + {"FM+AM(DSB)", 1}, // Fixed RX demod- WFM config Index 1 : FM+AM for Audio NOAA APT ones. }, }; diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index 465550184..0db3f5aaf 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -41,20 +41,20 @@ namespace { static constexpr std::array am_configs{{ // we config here all the non COMMON parameters to each AM modulation type in RX. - {taps_6k0_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth . - {taps_6k0_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments. - {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments. - {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments. - {taps_6k0_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox - {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax. + {taps_6k0_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth . + {taps_6k0_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments. + {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments. + {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments. + {taps_6k0_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox + {taps_6k0_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, apt_audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_1}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax. // below options for Waterfall zoom x 2 - {taps_6k0_narrow_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth . - {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments. - {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments. - {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments. - {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox - {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax with waterfall zoom x 2 (we need taps_6k0_narrow_decim_1 to minimize aliasing) + {taps_6k0_narrow_decim_1, taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth . + {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments. + {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments. + {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments. + {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox + {taps_6k0_narrow_decim_1, taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, apt_audio_12k_lpf_1500hz_config, (int)AMConfigureMessage::Zoom_waterfall::ZOOM_x_2}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax with waterfall zoom x 2 (we need taps_6k0_narrow_decim_1 to minimize aliasing) }}; static constexpr std::array nbfm_configs{{ @@ -69,6 +69,10 @@ static constexpr std::array wfm_configs{{ {taps_40k_wfm_decim_0, taps_40k_wfm_decim_1}, }}; +static constexpr std::array wfmam_configs{{ + {taps_16k0_decim_0, taps_84k_wfmam_decim_1}, +}}; + } /* namespace */ rf::Frequency ReceiverModel::target_frequency() const { @@ -187,6 +191,17 @@ void ReceiverModel::set_wfm_configuration(uint8_t n) { } } +uint8_t ReceiverModel::wfmam_configuration() const { + return settings_.wfmam_config_index; +} + +void ReceiverModel::set_wfmam_configuration(uint8_t n) { + if (n < wfmam_configs.size()) { + settings_.wfmam_config_index = n; + update_modulation(); + } +} + uint8_t ReceiverModel::squelch_level() const { return settings_.squelch_level; } @@ -340,6 +355,10 @@ void ReceiverModel::update_modulation() { update_wfm_configuration(); break; + case Mode::WFMAudioAMApt: + update_wfmam_configuration(); + break; + case Mode::SpectrumAnalysis: case Mode::Capture: break; @@ -362,6 +381,10 @@ void ReceiverModel::update_wfm_configuration() { wfm_configs[wfm_configuration()].apply(); } +void ReceiverModel::update_wfmam_configuration() { + wfmam_configs[wfmam_configuration()].apply(); // update with different index for Wefax. +} + void ReceiverModel::update_antenna_bias() { if (enabled_) radio::set_antenna_bias(portapack::get_antenna_bias()); diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index fd1142bd3..27e8e8dfa 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -41,8 +41,9 @@ class ReceiverModel { NarrowbandFMAudio = 1, WidebandFMAudio = 2, SpectrumAnalysis = 3, - AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod - Capture = 5, + AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod + WFMAudioAMApt = 5, // Added to handle SAT Weather map , NOAA 137 Mhz. + Capture = 6, }; struct settings_t { @@ -56,6 +57,7 @@ class ReceiverModel { Mode mode = Mode::NarrowbandFMAudio; uint8_t am_config_index = 0; uint8_t amfm_config_index = 0; + uint8_t wfmam_config_index = 0; uint8_t nbfm_config_index = 0; uint8_t wfm_config_index = 0; uint8_t squelch_level = 80; @@ -98,6 +100,9 @@ class ReceiverModel { uint8_t wfm_configuration() const; void set_wfm_configuration(uint8_t n); + uint8_t wfmam_configuration() const; + void set_wfmam_configuration(uint8_t n); + uint8_t squelch_level() const; void set_squelch_level(uint8_t v); @@ -151,6 +156,7 @@ class ReceiverModel { void update_amfm_configuration(); void update_nbfm_configuration(); void update_wfm_configuration(); + void update_wfmam_configuration(); void update_antenna_bias(); void update_headphone_volume(); diff --git a/firmware/baseband/audio_output.cpp b/firmware/baseband/audio_output.cpp index b5e82a778..f9b529786 100644 --- a/firmware/baseband/audio_output.cpp +++ b/firmware/baseband/audio_output.cpp @@ -52,6 +52,19 @@ void AudioOutput::write_unprocessed(const buffer_s16_t& audio) { }); } +void AudioOutput::apt_write(const buffer_s16_t& audio) { + std::array audio_f; + for (size_t i = 0; i < audio.count; i++) { + cur = audio.p[i]; + cur2 = cur * cur; + mag_am = sqrtf(prev2 + cur2 - (2 * prev * cur * cos_theta)) / sin_theta; + audio_f[i] = mag_am * ki; // normalize. + prev = cur; + prev2 = cur2; + } + write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate}); +} + void AudioOutput::write(const buffer_s16_t& audio) { std::array audio_f; for (size_t i = 0; i < audio.count; i++) { @@ -72,8 +85,8 @@ 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); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except AMFM (WFAX) with "lpf-coef" - deemph.execute_in_place(audio); + hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except AMFM (WFAX) with "lpf-coef" and notch for WFMAM (NOAA) + deemph.execute_in_place(audio); // IIRBiquadFilter name is "deemph", but we will call LPF de-emphasis or other LPF for WFAM (NOAA). audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0); audio_present = (audio_present_history != 0); diff --git a/firmware/baseband/audio_output.hpp b/firmware/baseband/audio_output.hpp index 5cbc6de01..1c3b41378 100644 --- a/firmware/baseband/audio_output.hpp +++ b/firmware/baseband/audio_output.hpp @@ -46,6 +46,7 @@ class AudioOutput { const float squelch_threshold = 0.0f); void write_unprocessed(const buffer_s16_t& audio); + void apt_write(const buffer_s16_t& audio); void write(const buffer_s16_t& audio); void write(const buffer_f32_t& audio); @@ -58,6 +59,10 @@ class AudioOutput { private: static constexpr float k = 32768.0f; static constexpr float ki = 1.0f / k; + static constexpr float cos_theta = 0.30901699437494742410f; + static constexpr float sin_theta = 0.95105651629515357212f; + + float cur = 0.0f, cur2 = 0.0f, prev = 0.0f, prev2 = 0.0f, mag_am = 0.0f; BlockDecimator block_buffer_s16{1}; BlockDecimator block_buffer{1}; diff --git a/firmware/baseband/proc_wefaxrx.cpp b/firmware/baseband/proc_wefaxrx.cpp index 26e965c8a..07c24fdff 100644 --- a/firmware/baseband/proc_wefaxrx.cpp +++ b/firmware/baseband/proc_wefaxrx.cpp @@ -161,7 +161,7 @@ void WeFaxRx::configure(const WeFaxRxConfigureMessage& message) { channel_filter_high_f = taps_2k6_usb_wefax_channel.high_frequency_normalized * channel_filter_input_fs; channel_filter_transition = taps_2k6_usb_wefax_channel.transition_normalized * channel_filter_input_fs; channel_spectrum.set_decimation_factor(1.0f); - audio_output.configure(audio_12k_lpf_1500hz_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there). + audio_output.configure(apt_audio_12k_lpf_1500hz_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there). lpm = message.lpm; ioc_mode = message.ioc; diff --git a/firmware/baseband/proc_wfm_audio.cpp b/firmware/baseband/proc_wfm_audio.cpp index 7a59c73fa..de265aaf5 100644 --- a/firmware/baseband/proc_wfm_audio.cpp +++ b/firmware/baseband/proc_wfm_audio.cpp @@ -47,23 +47,37 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) { channel_spectrum.feed(channel, channel_filter_low_f, channel_filter_high_f, channel_filter_transition); } - /* 384kHz complex[256] + /* 384kHz complex[256] for wfm * -> FM demodulation * -> 384kHz int16_t[256] */ /* TODO: To improve adjacent channel rejection, implement complex channel filter: * pass < +/- 100kHz, stop > +/- 200kHz */ - auto audio_oversampled = demod.execute(channel, work_audio_buffer); + /* 96kHz complex[64] for wfmam NOAA + * -> FM demodulation + * -> 96kHz int16_t[64] */ - /* 384kHz int16_t[256] + auto audio_oversampled = demod.execute(channel, work_audio_buffer); // fs 384khz wfm , 96khz wfmam for NOAA + + /* 384kHz int16_t[256] for wfm * -> 4th order CIC decimation by 2, gain of 1 * -> 192kHz int16_t[128] */ + + /* 96kHz int16_t[64] for wfam + * -> 4th order CIC decimation by 2, gain of 1 + * -> 48kHz int16_t[32] */ + auto audio_4fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer); - /* 192kHz int16_t[128] + /* 192kHz int16_t[128] for wfm * -> 4th order CIC decimation by 2, gain of 1 * -> 96kHz int16_t[64] */ + + /* 48kHz int16_t[32] for wfman + * -> 4th order CIC decimation by 2, gain of 1 + * -> 24kHz int16_t[16] */ + auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer); // Input: 96kHz int16_t[64] @@ -118,13 +132,24 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) { break; } - /* 96kHz int16_t[64] + /* 96kHz int16_t[64] for wfm * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1 * -> 48kHz int16_t[32] */ + + /* 24kHz int16_t[16] for wfmam + * -> FIR filter, <4.5kHz (0.1875fs) pass, >5.2kHz (0.2166fs) stop, gain of 1 + * -> 12kHz int16_t[8] */ + auto audio = audio_filter.execute(audio_2fs, work_audio_buffer); - /* -> 48kHz int16_t[32] */ - audio_output.write(audio); + /* -> 48kHz int16_t[32] for wfm , */ + /* -> 12kHz int16_t[8] for wfmam , */ + + if (decim_1.decimation_factor() == 2) { + audio_output.write(audio); // we are in original wfm , decim_1.decimation_factor == 2 + } else { + audio_output.apt_write(audio); // we are in added wfmam (noaa), decim_1.decimation_factor == 8 + } } void WidebandFMAudio::post_message(const buffer_c16_t& data) { @@ -142,7 +167,11 @@ void WidebandFMAudio::on_message(const Message* const message) { break; case Message::ID::WFMConfigure: - configure(*reinterpret_cast(message)); + configure_wfm(*reinterpret_cast(message)); + break; + + case Message::ID::WFMAMConfigure: + configure_wfmam(*reinterpret_cast(message)); break; case Message::ID::CaptureConfig: @@ -154,20 +183,56 @@ void WidebandFMAudio::on_message(const Message* const message) { } } -void WidebandFMAudio::configure(const WFMConfigureMessage& message) { +void WidebandFMAudio::configure_wfm(const WFMConfigureMessage& message) { constexpr size_t decim_0_input_fs = baseband_fs; 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; - constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; - constexpr size_t demod_input_fs = decim_1_output_fs; + decim_0.configure(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 . + // Temptatively , I splitted, in two WidebandFMAudio::configure_wfm / WidebandFMAudio::configure_wfmam and dynamically /2, /8 (here /2) + // decim_1.set().configure(message.decim_1_filter.taps); // for wfm + // decim_1.set().configure(taps_84k_wfmam_decim_1.taps); // for wfmam + decim_1.set().configure(message.decim_1_filter.taps); // for wfm + size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor(); // wfm , decim_1.decimation_factor() = /2 , if applied after the line : decim_1.set().configure(message.decim_1_filter.taps); + size_t demod_input_fs = decim_1_output_fs; spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz; spectrum_samples = 0; + channel_filter_low_f = message.decim_1_filter.low_frequency_normalized * decim_1_input_fs; + channel_filter_high_f = message.decim_1_filter.high_frequency_normalized * decim_1_input_fs; + channel_filter_transition = message.decim_1_filter.transition_normalized * decim_1_input_fs; + demod.configure(demod_input_fs, message.deviation); + audio_filter.configure(message.audio_filter.taps); + audio_output.configure(message.audio_hpf_config, message.audio_deemph_config); + + channel_spectrum.set_decimation_factor(1); + + configured = true; +} + +void WidebandFMAudio::configure_wfmam(const WFMAMConfigureMessage& message) { + constexpr size_t decim_0_input_fs = baseband_fs; + 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); - decim_1.configure(message.decim_1_filter.taps); + + // decim_1.configure(message.decim_1_filter.taps); // Original . + // TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps . + // Temptatively , I splitted, in two WidebandFMAudio::configure_wfm / WidebandFMAudio::configure_wfmam and dynamically /2, /8 . (here /8) + // decim_1.set().configure(message.decim_1_filter.taps); // for wfm + // decim_1.set().configure(message.decim_1_filter.taps); // for wfmam + decim_1.set().configure(message.decim_1_filter.taps); // for wfmam + size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor(); // wfmam, decim_1.decimation_factor() = /8 ,if applied after the line, decim_1.set().configure(message.decim_1_filter.taps); + size_t demod_input_fs = decim_1_output_fs; + + spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz; + spectrum_samples = 0; + channel_filter_low_f = message.decim_1_filter.low_frequency_normalized * decim_1_input_fs; channel_filter_high_f = message.decim_1_filter.high_frequency_normalized * decim_1_input_fs; channel_filter_transition = message.decim_1_filter.transition_normalized * decim_1_input_fs; diff --git a/firmware/baseband/proc_wfm_audio.hpp b/firmware/baseband/proc_wfm_audio.hpp index d003e8fa9..a9ebd87d5 100644 --- a/firmware/baseband/proc_wfm_audio.hpp +++ b/firmware/baseband/proc_wfm_audio.hpp @@ -35,6 +35,46 @@ #include "audio_output.hpp" #include "spectrum_collector.hpp" +#include +#include +#include +#include + +template +class MultiDecimator { + public: + /* Dispatches to the underlying type's execute. */ + template + Destination execute( + const Source& src, + const Destination& dst) { + return std::visit( + [&src, &dst](auto&& arg) -> Destination { + return arg.execute(src, dst); + }, + decimator_); + } + + size_t decimation_factor() const { + return std::visit( + [](auto&& arg) -> size_t { + return arg.decimation_factor; + }, + decimator_); + } + + /* Sets this decimator to a new instance of the specified decimator type. + * NB: The instance is returned by-ref so 'configure' can easily be called. */ + template + Decimator& set() { + decimator_ = Decimator{}; + return std::get(decimator_); + } + + private: + std::variant decimator_{}; +}; + class WidebandFMAudio : public BasebandProcessor { public: void execute(const buffer_c8_t& buffer) override; @@ -59,7 +99,16 @@ class WidebandFMAudio : public BasebandProcessor { complex_audio.size()}; dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; - dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; + // dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; //original condition , before adding wfmam + + // decim_1 will handle different types of FIR filters depending on selection. + MultiDecimator< + dsp::decimate::FIRC16xR16x16Decim2, + dsp::decimate::FIRC16xR16x32Decim8> + decim_1{}; + + // dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; // For FMAM + int32_t channel_filter_low_f = 0; int32_t channel_filter_high_f = 0; int32_t channel_filter_transition = 0; @@ -94,7 +143,8 @@ class WidebandFMAudio : public BasebandProcessor { BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive}; RSSIThread rssi_thread{}; - void configure(const WFMConfigureMessage& message); + void configure_wfm(const WFMConfigureMessage& message); + void configure_wfmam(const WFMAMConfigureMessage& message); void capture_config(const CaptureConfigMessage& message); void post_message(const buffer_c16_t& data); }; diff --git a/firmware/common/dsp_fir_taps.hpp b/firmware/common/dsp_fir_taps.hpp index 48b429271..e5f0f78da 100644 --- a/firmware/common/dsp_fir_taps.hpp +++ b/firmware/common/dsp_fir_taps.hpp @@ -1325,6 +1325,128 @@ constexpr fir_taps_real<16> taps_40k_wfm_decim_1 = { }}, }; +// WFMAM decimation filters //////////////////////////////////////////////// +// Used for NOAA 137 Mhz APT sat demod. +// IFIR prototype filter: fs=768000, pass=42000, stop=95000, decim=8, fout=96000 +constexpr fir_taps_real<32> taps_84k_wfmam_decim_1 = { + .low_frequency_normalized = -42000.0f / 768000.0f, + .high_frequency_normalized = 42000.0f / 768000.0f, + .transition_normalized = 53000.0f / 768000.0f, + .taps = {{ + 13, + -6, + -47, + -116, + -207, + -294, + -332, + -266, + -39, + 386, + 1012, + 1795, + 2648, + 3452, + 4079, + 4423, + 4423, + 4079, + 3452, + 2648, + 1795, + 1012, + 386, + -39, + -266, + -332, + -294, + -207, + -116, + -47, + -6, + 13, + }}, +}; + +/* 1st Wideband FM demod baseband filter of audio AM tones , + to pass all DSB band of AM fsubcarrier 2.4Khz mod. with APT */ +/* 24kHz int16_t input + * -> FIR filter, <4.5kHz (0.1875fs) pass, >5.2kHz (0.2166fs) stop + * -> 12kHz int16_t output, gain of 1.0 (I think). + * sum(abs(taps)): 125152 , before <125270>, very similar. + */ +constexpr fir_taps_real<64> taps_64_lp_1875_2166{ + .low_frequency_normalized = -0.1875f, + .high_frequency_normalized = 0.1875f, + .transition_normalized = 0.03f, + .taps = {{ + 38, + -21, + -51, + -9, + 77, + 82, + -50, + -168, + -67, + 190, + 253, + -61, + -403, + -243, + 356, + 616, + 15, + -814, + -671, + 550, + 1335, + 334, + -1527, + -1689, + 725, + 2978, + 1455, + -3277, + -5361, + 830, + 13781, + 24549, + 24549, + 13781, + 830, + -5361, + -3277, + 1455, + 2978, + 725, + -1689, + -1527, + 334, + 1335, + 550, + -671, + -814, + 15, + 616, + 356, + -243, + -403, + -61, + 253, + 190, + -67, + -168, + -50, + 82, + 77, + -9, + -51, + -21, + 38, + }}, +}; + // TPMS decimation filters //////////////////////////////////////////////// // IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400 diff --git a/firmware/common/dsp_iir_config.hpp b/firmware/common/dsp_iir_config.hpp index 93e93d179..b1ab54506 100644 --- a/firmware/common/dsp_iir_config.hpp +++ b/firmware/common/dsp_iir_config.hpp @@ -56,8 +56,8 @@ constexpr iir_biquad_config_t audio_12k_hpf_300hz_config{ {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. +constexpr iir_biquad_config_t apt_audio_12k_lpf_1500hz_config{ + // Added to lpf the apt audio in wefax mode , before sending to SD card or spk. {0.09763107f, 0.19526215f, 0.09763107f}, {1.00000000f, -0.94280904f, 0.33333333f}}; @@ -108,4 +108,15 @@ constexpr iir_biquad_config_t audio_48k_deemph_2122_6_config{ {0.12264116f, 0.12264116f, 0.00000000f}, {1.00000000f, -0.75471767f, 0.00000000f}}; +// scipy.signal.iirnotch(f0, Q, fs) = signal.iirnotch(2400, 15, 12000) +constexpr iir_biquad_config_t apt_audio_12k_notch_2k4_config{ + {0.95977357f, -0.59317269f, 0.95977357f}, + {1.00000000f, -0.59317269f, 0.91954714f}}; + +// scipy.signal.butter(2, 2000 / 6000.0, 'low', analog=False) +constexpr iir_biquad_config_t apt_audio_12k_lpf_2000hz_config{ + // Added to lpf the apt audio in NOAA mode , before sending to SD card or spk. + {0.15505103f, 0.31010205f, 0.15505103f}, + {1.00000000f, -0.6202041f, 0.24040821f}}; + #endif /*__DSP_IIR_CONFIG_H__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 14e899f9a..cdf86f641 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -130,7 +130,7 @@ class Message { WeFaxRxConfigure = 73, WeFaxRxStatusData = 74, WeFaxRxImageData = 75, - + WFMAMConfigure = 76, MAX }; @@ -584,6 +584,32 @@ class WFMConfigureMessage : public Message { const iir_biquad_config_t audio_deemph_config; }; +class WFMAMConfigureMessage : public Message { + public: + constexpr WFMAMConfigureMessage( + const fir_taps_real<24> decim_0_filter, + const fir_taps_real<32> decim_1_filter, + const fir_taps_real<64> audio_filter, + const size_t deviation, + const iir_biquad_config_t audio_hpf_config, + const iir_biquad_config_t audio_deemph_config) + : Message{ID::WFMAMConfigure}, + decim_0_filter(decim_0_filter), + decim_1_filter(decim_1_filter), + audio_filter(audio_filter), + deviation{deviation}, + audio_hpf_config(audio_hpf_config), + audio_deemph_config(audio_deemph_config) { + } + + const fir_taps_real<24> decim_0_filter; + const fir_taps_real<32> decim_1_filter; + const fir_taps_real<64> audio_filter; + const size_t deviation; + const iir_biquad_config_t audio_hpf_config; + const iir_biquad_config_t audio_deemph_config; +}; + class AMConfigureMessage : public Message { public: enum class Modulation : int32_t {