mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-03-08 22:46:08 -05:00
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
This commit is contained in:
parent
b6e498a6d3
commit
52c3760e90
@ -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<AMFMAptOptionsView>(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<SPECOptionsView>(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;
|
||||
}
|
||||
|
@ -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}};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -41,6 +41,7 @@ enum freqman_entry_modulation : uint8_t {
|
||||
AM_MODULATION = 0,
|
||||
NFM_MODULATION,
|
||||
WFM_MODULATION,
|
||||
AMFM_MODULATION, // Added for Wefax.
|
||||
SPEC_MODULATION
|
||||
};
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -39,13 +39,14 @@ using namespace portapack;
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr std::array<baseband::AMConfig, 5> am_configs{{
|
||||
static constexpr std::array<baseband::AMConfig, 6> 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<baseband::NBFMConfig, 3> 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());
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "complex.hpp"
|
||||
#include "fxpt_atan2.hpp"
|
||||
#include "utility_m4.hpp"
|
||||
#include "dsp_hilbert.hpp"
|
||||
#include "dsp_modulate.hpp"
|
||||
|
||||
#include <hal.h>
|
||||
|
||||
@ -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<float>(t.imag()) / static_cast<float>(t.real());
|
||||
return 16384.0f * x;
|
||||
}
|
||||
*/
|
||||
|
||||
static inline float angle_approx_0deg27(const complex32_t t) {
|
||||
if (t.real()) {
|
||||
const auto x = static_cast<float>(t.imag()) / static_cast<float>(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) {
|
||||
|
@ -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(
|
||||
|
@ -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 */
|
||||
|
@ -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__*/
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include <array>
|
||||
#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;
|
||||
}
|
||||
|
@ -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{};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
|
@ -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__*/
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user