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:
Brumi-2021
2025-03-05 04:27:18 +01:00
committed by GitHub
parent b6e498a6d3
commit 52c3760e90
19 changed files with 383 additions and 55 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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 */

View File

@@ -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__*/

View File

@@ -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;
}

View File

@@ -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{};