mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-28 18:39:03 +00:00
Add AM spectrum ZOOM x3/x4 with optimized x4 decimation (#3304)
* Add AM spectrum zoom x3 and x4 * Optimize AM spectrum zoom x4 processing * Fix AM spectrum zoom formatting * Fix AM spectrum zoom header formatting
This commit is contained in:
@@ -37,6 +37,28 @@ using namespace tonekey;
|
||||
|
||||
namespace ui {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t zoom_filter_offset(const OptionsField::value_t zoom_option) {
|
||||
return zoom_option == (int)AMSpectrumZoomOption::X1 ? 0 : 6;
|
||||
}
|
||||
|
||||
constexpr AMConfigureMessage::Zoom_waterfall zoom_spectrum_factor(
|
||||
const OptionsField::value_t zoom_option) {
|
||||
switch ((AMSpectrumZoomOption)zoom_option) {
|
||||
case AMSpectrumZoomOption::X2:
|
||||
return AMConfigureMessage::Zoom_waterfall::ZOOM_x_2;
|
||||
case AMSpectrumZoomOption::X3:
|
||||
return AMConfigureMessage::Zoom_waterfall::ZOOM_x_3;
|
||||
case AMSpectrumZoomOption::X4:
|
||||
return AMConfigureMessage::Zoom_waterfall::ZOOM_x_4;
|
||||
default:
|
||||
return AMConfigureMessage::Zoom_waterfall::ZOOM_x_1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* AMOptionsView *********************************************************/
|
||||
|
||||
AMOptionsView::AMOptionsView(
|
||||
@@ -52,8 +74,10 @@ AMOptionsView::AMOptionsView(
|
||||
&zoom_config,
|
||||
});
|
||||
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) { // n , has two option values. when GUI =zoom+1 => (0), when GUI=zoom+2 (6)
|
||||
receiver_model.set_am_configuration(view->get_previous_AM_mode_option() + n); // n (0 or 6)
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_am_configuration(
|
||||
view->get_previous_AM_mode_option() + zoom_filter_offset(n),
|
||||
zoom_spectrum_factor(n));
|
||||
view->set_zoom_factor(AM_MODULATION, n);
|
||||
view->set_previous_zoom_option(n);
|
||||
};
|
||||
@@ -61,11 +85,13 @@ AMOptionsView::AMOptionsView(
|
||||
// restore zoom selection
|
||||
zoom_config.set_by_value(view->get_zoom_factor(AM_MODULATION));
|
||||
|
||||
freqman_set_bandwidth_option(AM_MODULATION, options_config); // freqman.cpp to the options_config, only allowing 5 modes freqman_bandwidths[AM] {"DSB 9k", 0}, {"DSB 6k", 1}, {"USB+3k", 2}, {"LSB-3k", 3}, {"CW", 4},
|
||||
options_config.set_by_value(receiver_model.am_configuration() - view->get_previous_zoom_option()); // restore AM GUI option mode , AM FIR index filters (0..11) values , <baseband::AMConfig, 12> am_configs has 12 fir index elements.
|
||||
freqman_set_bandwidth_option(AM_MODULATION, options_config); // freqman.cpp to the options_config, only allowing 5 modes freqman_bandwidths[AM] {"DSB 9k", 0}, {"DSB 6k", 1}, {"USB+3k", 2}, {"LSB-3k", 3}, {"CW", 4},
|
||||
options_config.set_by_value(receiver_model.am_configuration() - zoom_filter_offset(view->get_previous_zoom_option()));
|
||||
options_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_am_configuration(n + view->get_previous_zoom_option()); // we select proper FIR AM filter (0..11), = 0..4 GUI AM modes + offset +6 (if zoom+2)
|
||||
view->set_previous_AM_mode_option(n); // (0..4) allowing 5 AM modes (DSB9K, DSB6K, USB,LSB, CW)
|
||||
receiver_model.set_am_configuration(
|
||||
n + zoom_filter_offset(view->get_previous_zoom_option()),
|
||||
zoom_spectrum_factor(view->get_previous_zoom_option()));
|
||||
view->set_previous_AM_mode_option(n);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,7 +180,9 @@ AMFMAptOptionsView::AMFMAptOptionsView(
|
||||
options_config.set_by_value(receiver_model.amfm_configuration());
|
||||
|
||||
zoom_config.on_change = [this, view](size_t, OptionsField::value_t n) {
|
||||
receiver_model.set_amfm_configuration(5 + n);
|
||||
receiver_model.set_amfm_configuration(
|
||||
5 + zoom_filter_offset(n),
|
||||
zoom_spectrum_factor(n));
|
||||
view->set_zoom_factor(AMFM_MODULATION, n);
|
||||
};
|
||||
|
||||
@@ -645,10 +673,19 @@ void AnalogAudioView::set_frequency_absolute(rf::Frequency frequency) {
|
||||
}
|
||||
|
||||
int32_t AnalogAudioView::sliding_limit() const {
|
||||
const bool zoom_x2 =
|
||||
receiver_model.modulation() == ReceiverModel::Mode::AMAudio &&
|
||||
previous_zoom != 0;
|
||||
return zoom_x2 ? sliding_limit_zoom_x2 : sliding_limit_zoom_x1;
|
||||
if (receiver_model.modulation() != ReceiverModel::Mode::AMAudio)
|
||||
return sliding_limit_zoom_x1;
|
||||
|
||||
switch (previous_zoom) {
|
||||
case (int)AMSpectrumZoomOption::X2:
|
||||
return sliding_limit_zoom_x2;
|
||||
case (int)AMSpectrumZoomOption::X3:
|
||||
return sliding_limit_zoom_x3;
|
||||
case (int)AMSpectrumZoomOption::X4:
|
||||
return sliding_limit_zoom_x4;
|
||||
default:
|
||||
return sliding_limit_zoom_x1;
|
||||
}
|
||||
}
|
||||
|
||||
void AnalogAudioView::reset_sliding_frequency(ReceiverModel::Mode modulation) {
|
||||
|
||||
@@ -69,6 +69,13 @@ class PralineOptionsView : public View {
|
||||
};
|
||||
#endif
|
||||
|
||||
enum class AMSpectrumZoomOption : int32_t {
|
||||
X1 = 0,
|
||||
X2 = 6,
|
||||
X3 = 12,
|
||||
X4 = 18,
|
||||
};
|
||||
|
||||
class AMOptionsView : public View {
|
||||
public:
|
||||
AMOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
|
||||
@@ -89,9 +96,10 @@ class AMOptionsView : public View {
|
||||
OptionsField zoom_config{
|
||||
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
|
||||
7,
|
||||
{{"ZOOM x1", 0},
|
||||
{"ZOOM x2", 6}} // offset index AM modes array FIR filters.
|
||||
};
|
||||
{{"ZOOM x1", (int)AMSpectrumZoomOption::X1},
|
||||
{"ZOOM x2", (int)AMSpectrumZoomOption::X2},
|
||||
{"ZOOM x3", (int)AMSpectrumZoomOption::X3},
|
||||
{"ZOOM x4", (int)AMSpectrumZoomOption::X4}}};
|
||||
};
|
||||
|
||||
class AMFMAptOptionsView : public View {
|
||||
@@ -114,9 +122,10 @@ class AMFMAptOptionsView : public View {
|
||||
OptionsField zoom_config{
|
||||
{UI_POS_X_RIGHT(7), UI_POS_Y(0)},
|
||||
7,
|
||||
{{"ZOOM x1", 0},
|
||||
{"ZOOM x2", 6}} // offset index array filters.
|
||||
};
|
||||
{{"ZOOM x1", (int)AMSpectrumZoomOption::X1},
|
||||
{"ZOOM x2", (int)AMSpectrumZoomOption::X2},
|
||||
{"ZOOM x3", (int)AMSpectrumZoomOption::X3},
|
||||
{"ZOOM x4", (int)AMSpectrumZoomOption::X4}}};
|
||||
};
|
||||
|
||||
class NBFMOptionsView : public View {
|
||||
@@ -265,12 +274,14 @@ class AnalogAudioView : public View {
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{};
|
||||
uint8_t iq_phase_calibration_value{15}; // initial default RX IQ phase calibration value , used for both max2837 & max2839
|
||||
uint8_t zoom_factor_am{0}; // initial zoom factor in AM mode
|
||||
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)
|
||||
uint8_t zoom_factor_am{0};
|
||||
uint8_t zoom_factor_amfm{0};
|
||||
uint8_t previous_AM_mode_option{0};
|
||||
uint8_t previous_zoom{0};
|
||||
static constexpr int32_t sliding_limit_zoom_x1 = 50000;
|
||||
static constexpr int32_t sliding_limit_zoom_x2 = 30000;
|
||||
static constexpr int32_t sliding_limit_zoom_x3 = 15000;
|
||||
static constexpr int32_t sliding_limit_zoom_x4 = 6250;
|
||||
rf::Frequency sliding_center_frequency{0};
|
||||
bool sliding_enabled{false};
|
||||
|
||||
|
||||
@@ -71,6 +71,10 @@ static void send_message(const Message* const message) {
|
||||
}
|
||||
|
||||
void AMConfig::apply() const {
|
||||
apply((AMConfigureMessage::Zoom_waterfall)spectrum_decimation_factor);
|
||||
}
|
||||
|
||||
void AMConfig::apply(const AMConfigureMessage::Zoom_waterfall spectrum_zoom) const {
|
||||
const AMConfigureMessage message{
|
||||
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, AMFM-WFAX)
|
||||
decim_1, // var decim_1 FIR taps filter , variable values , to handle two spectrum decim factor 1 and 2 (zoom) and more APT LPF filtered .
|
||||
@@ -78,7 +82,7 @@ void AMConfig::apply() const {
|
||||
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,AMFM-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)
|
||||
spectrum_decimation_factor}; // var parameter , waterfall no zoom : 1 ,for zoom x 2 : 2
|
||||
(size_t)spectrum_zoom};
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
}
|
||||
|
||||
@@ -41,9 +41,10 @@ struct AMConfig {
|
||||
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.
|
||||
const size_t spectrum_decimation_factor; // used to handle LCD AM waterfall zoom x1 / zoom x2.
|
||||
const size_t spectrum_decimation_factor;
|
||||
|
||||
void apply() const;
|
||||
void apply(AMConfigureMessage::Zoom_waterfall spectrum_zoom) const;
|
||||
};
|
||||
|
||||
struct NBFMConfig {
|
||||
|
||||
@@ -57,6 +57,12 @@ static constexpr std::array<baseband::AMConfig, 12> am_configs{{
|
||||
{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)
|
||||
}};
|
||||
|
||||
AMConfigureMessage::Zoom_waterfall spectrum_zoom_for_am_config(const size_t index) {
|
||||
return index < am_configs.size()
|
||||
? (AMConfigureMessage::Zoom_waterfall)am_configs[index].spectrum_decimation_factor
|
||||
: AMConfigureMessage::Zoom_waterfall::ZOOM_x_1;
|
||||
}
|
||||
|
||||
static constexpr std::array<baseband::NBFMConfig, 4> nbfm_configs{{
|
||||
{taps_4k25_decim_0, taps_4k25_decim_1, taps_4k25_channel, 2500},
|
||||
{taps_11k0_decim_0, taps_11k0_decim_1, taps_11k0_channel, 2500},
|
||||
@@ -166,6 +172,17 @@ uint8_t ReceiverModel::am_configuration() const {
|
||||
void ReceiverModel::set_am_configuration(uint8_t n) {
|
||||
if (n < am_configs.size()) {
|
||||
settings_.am_config_index = n;
|
||||
am_spectrum_zoom_ = spectrum_zoom_for_am_config(n);
|
||||
update_modulation();
|
||||
}
|
||||
}
|
||||
|
||||
void ReceiverModel::set_am_configuration(
|
||||
uint8_t n,
|
||||
AMConfigureMessage::Zoom_waterfall spectrum_zoom) {
|
||||
if (n < am_configs.size()) {
|
||||
settings_.am_config_index = n;
|
||||
am_spectrum_zoom_ = spectrum_zoom;
|
||||
update_modulation();
|
||||
}
|
||||
}
|
||||
@@ -177,6 +194,17 @@ uint8_t ReceiverModel::amfm_configuration() const {
|
||||
void ReceiverModel::set_amfm_configuration(uint8_t n) {
|
||||
if (n < am_configs.size()) {
|
||||
settings_.amfm_config_index = n;
|
||||
am_spectrum_zoom_ = spectrum_zoom_for_am_config(n);
|
||||
update_modulation();
|
||||
}
|
||||
}
|
||||
|
||||
void ReceiverModel::set_amfm_configuration(
|
||||
uint8_t n,
|
||||
AMConfigureMessage::Zoom_waterfall spectrum_zoom) {
|
||||
if (n < am_configs.size()) {
|
||||
settings_.amfm_config_index = n;
|
||||
am_spectrum_zoom_ = spectrum_zoom;
|
||||
update_modulation();
|
||||
}
|
||||
}
|
||||
@@ -288,6 +316,7 @@ void ReceiverModel::enable() {
|
||||
|
||||
void ReceiverModel::disable() {
|
||||
enabled_ = false;
|
||||
am_spectrum_zoom_ = spectrum_zoom_for_am_config(settings_.am_config_index);
|
||||
|
||||
// TODO: Responsibility for enabling/disabling the radio is muddy.
|
||||
// Some happens in ReceiverModel, some inside radio namespace.
|
||||
@@ -296,6 +325,7 @@ void ReceiverModel::disable() {
|
||||
|
||||
void ReceiverModel::initialize() {
|
||||
settings_ = settings_t{};
|
||||
am_spectrum_zoom_ = spectrum_zoom_for_am_config(settings_.am_config_index);
|
||||
}
|
||||
|
||||
void ReceiverModel::set_configuration_without_update(
|
||||
@@ -309,6 +339,7 @@ void ReceiverModel::set_configuration_without_update(
|
||||
settings_.mode = new_mode;
|
||||
settings_.frequency_step = new_frequency_step;
|
||||
settings_.am_config_index = new_am_config_index;
|
||||
am_spectrum_zoom_ = spectrum_zoom_for_am_config(new_am_config_index);
|
||||
settings_.nbfm_config_index = new_nbfm_config_index;
|
||||
settings_.wfm_config_index = new_wfm_config_index;
|
||||
settings_.wfmam_config_index = new_wfmam_config_index;
|
||||
@@ -455,11 +486,11 @@ void ReceiverModel::update_modulation() {
|
||||
}
|
||||
|
||||
void ReceiverModel::update_am_configuration() {
|
||||
am_configs[am_configuration()].apply();
|
||||
am_configs[am_configuration()].apply(am_spectrum_zoom_);
|
||||
}
|
||||
|
||||
void ReceiverModel::update_amfm_configuration() {
|
||||
am_configs[amfm_configuration()].apply(); // update with different index for Wefax.
|
||||
am_configs[amfm_configuration()].apply(am_spectrum_zoom_); // update with different index for Wefax.
|
||||
}
|
||||
|
||||
void ReceiverModel::update_nbfm_configuration() {
|
||||
|
||||
@@ -91,9 +91,11 @@ class ReceiverModel {
|
||||
|
||||
uint8_t am_configuration() const;
|
||||
void set_am_configuration(uint8_t n);
|
||||
void set_am_configuration(uint8_t n, AMConfigureMessage::Zoom_waterfall spectrum_zoom);
|
||||
|
||||
uint8_t amfm_configuration() const;
|
||||
void set_amfm_configuration(uint8_t n);
|
||||
void set_amfm_configuration(uint8_t n, AMConfigureMessage::Zoom_waterfall spectrum_zoom);
|
||||
|
||||
uint8_t nbfm_configuration() const;
|
||||
void set_nbfm_configuration(uint8_t n);
|
||||
@@ -140,6 +142,7 @@ class ReceiverModel {
|
||||
settings_t& settings() { return settings_; }
|
||||
|
||||
private:
|
||||
AMConfigureMessage::Zoom_waterfall am_spectrum_zoom_{AMConfigureMessage::Zoom_waterfall::ZOOM_x_1};
|
||||
settings_t settings_{};
|
||||
bool enabled_ = false;
|
||||
rf::Frequency hidden_offset = 0; // when we need to hide the offset from user, we set this. like when WeFax needs -300Hz.
|
||||
|
||||
@@ -337,6 +337,59 @@ buffer_c16_t FIRC16xR16x16Decim2::execute(
|
||||
src.sampling_rate / decimation_factor};
|
||||
}
|
||||
|
||||
// FIRC16xR16x32Decim4 ////////////////////////////////////////////////////
|
||||
|
||||
void FIRC16xR16x32Decim4::configure(
|
||||
const std::array<tap_t, taps_count>& taps,
|
||||
const int32_t scale) {
|
||||
std::copy(taps.cbegin(), taps.cend(), taps_.begin());
|
||||
output_scale = scale;
|
||||
z_.fill({});
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC16xR16x32Decim4::execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__builtin_assume_aligned(dst.p, 4));
|
||||
|
||||
const auto k = output_scale;
|
||||
const size_t count = src.count / decimation_factor;
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const vec2_s16* const in = static_cast<const vec2_s16*>(__builtin_assume_aligned(&src.p[i * decimation_factor], 4));
|
||||
|
||||
complex32_t accum;
|
||||
|
||||
accum = mac_shift(z, t, 0, accum);
|
||||
accum = mac_shift(z, t, 1, accum);
|
||||
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 0, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 1, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 2, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 3, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 4, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 5, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 6, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 7, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 8, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 9, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 10, accum);
|
||||
accum = mac_shift_and_store(z, t, decimation_factor, 11, accum);
|
||||
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 0, taps_count, accum);
|
||||
accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 1, taps_count, accum);
|
||||
|
||||
d[i] = scale_round_and_pack(accum, k);
|
||||
}
|
||||
|
||||
return {
|
||||
dst.p,
|
||||
count,
|
||||
src.sampling_rate / decimation_factor};
|
||||
}
|
||||
|
||||
// FIRC16xR16x63HalfbandDecim2 ////////////////////////////////////////////
|
||||
|
||||
void FIRC16xR16x63HalfbandDecim2::configure(
|
||||
|
||||
@@ -170,6 +170,28 @@ class FIRC16xR16x16Decim2 {
|
||||
int32_t output_scale = 0;
|
||||
};
|
||||
|
||||
class FIRC16xR16x32Decim4 {
|
||||
public:
|
||||
static constexpr size_t taps_count = 32;
|
||||
static constexpr size_t decimation_factor = 4;
|
||||
|
||||
using sample_t = complex16_t;
|
||||
using tap_t = int16_t;
|
||||
|
||||
void configure(
|
||||
const std::array<tap_t, taps_count>& taps,
|
||||
const int32_t scale = c16_to_c32_sat_scalar);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_{};
|
||||
std::array<tap_t, taps_count> taps_{};
|
||||
int32_t output_scale = 0;
|
||||
};
|
||||
|
||||
class FIRC16xR16x63HalfbandDecim2 {
|
||||
public:
|
||||
static constexpr size_t taps_count = 63;
|
||||
|
||||
@@ -12,8 +12,22 @@
|
||||
#include "dsp_fir_taps.hpp"
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include "ch.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool valid_decimation(
|
||||
const size_t decimation_factor,
|
||||
const size_t maximum_decimation) {
|
||||
return decimation_factor >= 2 &&
|
||||
decimation_factor <= maximum_decimation &&
|
||||
(decimation_factor & (decimation_factor - 1)) == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void FilteredSpectrumCollector::on_message(const Message* const message) {
|
||||
if (message->id == Message::ID::UpdateSpectrum) {
|
||||
update();
|
||||
@@ -21,11 +35,17 @@ void FilteredSpectrumCollector::on_message(const Message* const message) {
|
||||
SpectrumCollector::on_message(message);
|
||||
}
|
||||
|
||||
void FilteredSpectrumCollector::start_capture(
|
||||
bool FilteredSpectrumCollector::start_capture(
|
||||
const size_t decimation_factor) {
|
||||
if (!valid_decimation(decimation_factor, maximum_decimation)) {
|
||||
capture_ready_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
capture_decimation_ = decimation_factor;
|
||||
capture_count_ = 0;
|
||||
capture_ready_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilteredSpectrumCollector::feed(
|
||||
@@ -38,7 +58,7 @@ bool FilteredSpectrumCollector::feed(
|
||||
filter_high_frequency,
|
||||
filter_transition);
|
||||
|
||||
const size_t required_samples = 256 * capture_decimation_;
|
||||
const size_t required_samples = fft_samples * capture_decimation_;
|
||||
const size_t copy_count = std::min(
|
||||
channel.count, required_samples - capture_count_);
|
||||
std::copy_n(
|
||||
@@ -70,7 +90,7 @@ void FilteredSpectrumCollector::update() {
|
||||
decim_0_.configure(taps_audio_spectrum_halfband.taps);
|
||||
const buffer_c16_t capture{
|
||||
capture_.data(),
|
||||
256 * capture_decimation_,
|
||||
fft_samples * capture_decimation_,
|
||||
sampling_rate_ * capture_decimation_};
|
||||
const buffer_c16_t stage_0{
|
||||
stage_0_.data(),
|
||||
@@ -88,3 +108,125 @@ void FilteredSpectrumCollector::update() {
|
||||
}
|
||||
capture_ready_ = false;
|
||||
}
|
||||
|
||||
void AMFilteredSpectrumCollector::on_message(const Message* const message) {
|
||||
if (message->id == Message::ID::UpdateSpectrum) {
|
||||
update();
|
||||
}
|
||||
SpectrumCollector::on_message(message);
|
||||
}
|
||||
|
||||
bool AMFilteredSpectrumCollector::start_capture(
|
||||
const size_t decimation_factor) {
|
||||
if (!valid_decimation(decimation_factor, maximum_decimation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chSysLock();
|
||||
if (capture_state_ != CaptureState::Idle) {
|
||||
chSysUnlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
capture_decimation_ = decimation_factor;
|
||||
capture_count_ = 0;
|
||||
capture_state_ = CaptureState::Capturing;
|
||||
chSysUnlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AMFilteredSpectrumCollector::feed(
|
||||
const buffer_c16_t& channel,
|
||||
const int32_t filter_low_frequency,
|
||||
const int32_t filter_high_frequency,
|
||||
const int32_t filter_transition) {
|
||||
chSysLock();
|
||||
if (capture_state_ != CaptureState::Capturing) {
|
||||
chSysUnlock();
|
||||
return false;
|
||||
}
|
||||
chSysUnlock();
|
||||
|
||||
set_filter(
|
||||
filter_low_frequency,
|
||||
filter_high_frequency,
|
||||
filter_transition);
|
||||
|
||||
const size_t required_samples = fft_samples * capture_decimation_;
|
||||
const size_t copy_count = std::min(
|
||||
channel.count, required_samples - capture_count_);
|
||||
std::copy_n(
|
||||
channel.p,
|
||||
copy_count,
|
||||
capture_.begin() + capture_count_);
|
||||
capture_count_ += copy_count;
|
||||
|
||||
if (capture_count_ == required_samples) {
|
||||
sampling_rate_ = channel.sampling_rate / capture_decimation_;
|
||||
const bool streaming = is_streaming();
|
||||
|
||||
chSysLock();
|
||||
if (streaming) {
|
||||
capture_state_ = CaptureState::Pending;
|
||||
} else {
|
||||
capture_state_ = CaptureState::Idle;
|
||||
}
|
||||
chSysUnlock();
|
||||
|
||||
if (streaming) {
|
||||
EventDispatcher::events_flag(EVT_MASK_SPECTRUM);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AMFilteredSpectrumCollector::update() {
|
||||
chSysLock();
|
||||
if (capture_state_ != CaptureState::Pending) {
|
||||
chSysUnlock();
|
||||
return;
|
||||
}
|
||||
capture_state_ = CaptureState::Processing;
|
||||
const size_t capture_decimation = capture_decimation_;
|
||||
const uint32_t sampling_rate = sampling_rate_;
|
||||
chSysUnlock();
|
||||
|
||||
size_t filtered_count = fft_samples * capture_decimation;
|
||||
uint32_t filtered_sampling_rate = sampling_rate * capture_decimation;
|
||||
size_t remaining_decimation = capture_decimation;
|
||||
|
||||
if (remaining_decimation == maximum_decimation) {
|
||||
decimator_4_.configure(taps_audio_spectrum_decim_4.taps);
|
||||
const buffer_c16_t input{
|
||||
capture_.data(),
|
||||
filtered_count,
|
||||
filtered_sampling_rate};
|
||||
const buffer_c16_t output{
|
||||
capture_.data(),
|
||||
filtered_count / decimator_4_.decimation_factor};
|
||||
const auto filtered = decimator_4_.execute(input, output);
|
||||
filtered_count = filtered.count;
|
||||
filtered_sampling_rate = filtered.sampling_rate;
|
||||
remaining_decimation /= decimator_4_.decimation_factor;
|
||||
}
|
||||
|
||||
for (; remaining_decimation > 1; remaining_decimation /= 2) {
|
||||
decimator_.configure(taps_audio_spectrum_halfband.taps);
|
||||
const buffer_c16_t input{
|
||||
capture_.data(),
|
||||
filtered_count,
|
||||
filtered_sampling_rate};
|
||||
const buffer_c16_t output{
|
||||
capture_.data(),
|
||||
filtered_count / 2};
|
||||
const auto filtered = decimator_.execute(input, output);
|
||||
filtered_count = filtered.count;
|
||||
filtered_sampling_rate = filtered.sampling_rate;
|
||||
}
|
||||
|
||||
post_message({capture_.data(), filtered_count, filtered_sampling_rate});
|
||||
chSysLock();
|
||||
capture_state_ = CaptureState::Idle;
|
||||
chSysUnlock();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class FilteredSpectrumCollector : public SpectrumCollector {
|
||||
public:
|
||||
void on_message(const Message* const message);
|
||||
|
||||
void start_capture(const size_t decimation_factor);
|
||||
bool start_capture(const size_t decimation_factor);
|
||||
bool feed(
|
||||
const buffer_c16_t& channel,
|
||||
const int32_t filter_low_frequency,
|
||||
@@ -27,6 +27,9 @@ class FilteredSpectrumCollector : public SpectrumCollector {
|
||||
const int32_t filter_transition);
|
||||
|
||||
private:
|
||||
static constexpr size_t fft_samples = 256;
|
||||
static constexpr size_t maximum_decimation = 4;
|
||||
|
||||
std::array<complex16_t, 1024> capture_{};
|
||||
std::array<complex16_t, 512> stage_0_{};
|
||||
std::array<complex16_t, 256> stage_1_{};
|
||||
@@ -40,4 +43,37 @@ class FilteredSpectrumCollector : public SpectrumCollector {
|
||||
void update();
|
||||
};
|
||||
|
||||
class AMFilteredSpectrumCollector : public SpectrumCollector {
|
||||
public:
|
||||
void on_message(const Message* const message);
|
||||
|
||||
bool start_capture(const size_t decimation_factor);
|
||||
bool feed(
|
||||
const buffer_c16_t& channel,
|
||||
const int32_t filter_low_frequency,
|
||||
const int32_t filter_high_frequency,
|
||||
const int32_t filter_transition);
|
||||
|
||||
private:
|
||||
enum class CaptureState : uint8_t {
|
||||
Idle,
|
||||
Capturing,
|
||||
Pending,
|
||||
Processing,
|
||||
};
|
||||
|
||||
static constexpr size_t fft_samples = 256;
|
||||
static constexpr size_t maximum_decimation = 16;
|
||||
|
||||
std::array<complex16_t, fft_samples * maximum_decimation> capture_{};
|
||||
dsp::decimate::FIRC16xR16x32Decim4 decimator_4_{};
|
||||
dsp::decimate::FIRC16xR16x63HalfbandDecim2 decimator_{};
|
||||
size_t capture_count_{0};
|
||||
size_t capture_decimation_{1};
|
||||
uint32_t sampling_rate_{0};
|
||||
CaptureState capture_state_{CaptureState::Idle};
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif /*__FILTERED_SPECTRUM_COLLECTOR_H__*/
|
||||
|
||||
@@ -51,8 +51,8 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
|
||||
if (!spectrum_capture_active &&
|
||||
spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.start_capture(spectrum_zoom_x2 ? 4 : 2);
|
||||
spectrum_capture_active = true;
|
||||
spectrum_capture_active =
|
||||
channel_spectrum.start_capture(spectrum_decimation_factor);
|
||||
}
|
||||
|
||||
if (spectrum_capture_active &&
|
||||
@@ -146,7 +146,7 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
|
||||
channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs;
|
||||
|
||||
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}
|
||||
spectrum_zoom_x2 = message.channel_spectrum_decimation_factor == 2;
|
||||
spectrum_decimation_factor = 2 * message.channel_spectrum_decimation_factor;
|
||||
channel_spectrum.set_decimation_factor(1);
|
||||
spectrum_interval_samples =
|
||||
decim_0_output_fs / spectrum_rate_hz;
|
||||
|
||||
@@ -70,7 +70,7 @@ class NarrowbandAMAudio : public BasebandProcessor {
|
||||
size_t spectrum_interval_samples{0};
|
||||
size_t spectrum_samples{0};
|
||||
bool spectrum_capture_active{false};
|
||||
bool spectrum_zoom_x2{false};
|
||||
size_t spectrum_decimation_factor{2};
|
||||
|
||||
// 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}
|
||||
@@ -80,7 +80,7 @@ class NarrowbandAMAudio : public BasebandProcessor {
|
||||
FeedForwardCompressor audio_compressor{};
|
||||
AudioOutput audio_output{};
|
||||
|
||||
FilteredSpectrumCollector channel_spectrum{};
|
||||
AMFilteredSpectrumCollector channel_spectrum{};
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
#ifdef PRALINE
|
||||
|
||||
@@ -57,8 +57,7 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
|
||||
if (!spectrum_capture_active &&
|
||||
spectrum_samples >= spectrum_interval_samples) {
|
||||
spectrum_samples -= spectrum_interval_samples;
|
||||
channel_spectrum.start_capture(2);
|
||||
spectrum_capture_active = true;
|
||||
spectrum_capture_active = channel_spectrum.start_capture(2);
|
||||
}
|
||||
|
||||
if (spectrum_capture_active &&
|
||||
|
||||
@@ -77,6 +77,8 @@ constexpr fir_taps_real<16> taps_audio_wide_halfband_0{
|
||||
* contiguous 256-sample FFT frame is being collected:
|
||||
* 384kHz -> 192kHz (Zoom x1)
|
||||
* 192kHz -> 96kHz (additional stage for Zoom x2)
|
||||
* 96kHz -> 48kHz (additional stage for Zoom x3)
|
||||
* 96kHz -> 48kHz -> 24kHz (after the direct /4 stage for Zoom x4)
|
||||
*/
|
||||
constexpr fir_taps_real<63> taps_audio_spectrum_halfband{
|
||||
.low_frequency_normalized = -0.23f,
|
||||
@@ -149,6 +151,49 @@ constexpr fir_taps_real<63> taps_audio_spectrum_halfband{
|
||||
}},
|
||||
};
|
||||
|
||||
/* Direct 384kHz -> 96kHz spectrum decimator for AM Zoom x4.
|
||||
* Equiripple low-pass: passband edge 25.92kHz, stopband edge 70.08kHz.
|
||||
*/
|
||||
constexpr fir_taps_real<32> taps_audio_spectrum_decim_4{
|
||||
.low_frequency_normalized = -25920.0f / 384000.0f,
|
||||
.high_frequency_normalized = 25920.0f / 384000.0f,
|
||||
.transition_normalized = 44160.0f / 384000.0f,
|
||||
.taps = {{
|
||||
-26,
|
||||
-62,
|
||||
-79,
|
||||
-24,
|
||||
126,
|
||||
312,
|
||||
377,
|
||||
154,
|
||||
-389,
|
||||
-1020,
|
||||
-1263,
|
||||
-616,
|
||||
1137,
|
||||
3685,
|
||||
6234,
|
||||
7829,
|
||||
7829,
|
||||
6234,
|
||||
3685,
|
||||
1137,
|
||||
-616,
|
||||
-1263,
|
||||
-1020,
|
||||
-389,
|
||||
154,
|
||||
377,
|
||||
312,
|
||||
126,
|
||||
-24,
|
||||
-79,
|
||||
-62,
|
||||
-26,
|
||||
}},
|
||||
};
|
||||
|
||||
// NBFM 16K0F3E emission type /////////////////////////////////////////////
|
||||
|
||||
// IFIR image-reject filter: fs=3072000, pass=8000, stop=344000, decim=8, fout=384000
|
||||
|
||||
@@ -728,6 +728,8 @@ class AMConfigureMessage : public Message {
|
||||
enum class Zoom_waterfall : size_t {
|
||||
ZOOM_x_1 = 1,
|
||||
ZOOM_x_2 = 2,
|
||||
ZOOM_x_3 = 4,
|
||||
ZOOM_x_4 = 8,
|
||||
};
|
||||
|
||||
constexpr AMConfigureMessage(
|
||||
|
||||
Reference in New Issue
Block a user