mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-14 04:04:34 +00:00
Added an audio FFT view in Wideband FM receive
Tried speeding up fill_rectangle for clearing the waveform widget
This commit is contained in:
parent
b11c3c94b6
commit
b813b32593
@ -143,7 +143,7 @@ set(CPPSRC
|
|||||||
${COMMON}/portapack_persistent_memory.cpp
|
${COMMON}/portapack_persistent_memory.cpp
|
||||||
${COMMON}/portapack_shared_memory.cpp
|
${COMMON}/portapack_shared_memory.cpp
|
||||||
${COMMON}/sonde_packet.cpp
|
${COMMON}/sonde_packet.cpp
|
||||||
${COMMON}/test_packet.cpp
|
# ${COMMON}/test_packet.cpp
|
||||||
${COMMON}/tpms_packet.cpp
|
${COMMON}/tpms_packet.cpp
|
||||||
${COMMON}/ui.cpp
|
${COMMON}/ui.cpp
|
||||||
${COMMON}/ui_focus.cpp
|
${COMMON}/ui_focus.cpp
|
||||||
@ -239,7 +239,7 @@ set(CPPSRC
|
|||||||
apps/ui_sonde.cpp
|
apps/ui_sonde.cpp
|
||||||
apps/ui_soundboard.cpp
|
apps/ui_soundboard.cpp
|
||||||
apps/ui_sstvtx.cpp
|
apps/ui_sstvtx.cpp
|
||||||
apps/ui_test.cpp
|
# apps/ui_test.cpp
|
||||||
apps/ui_tone_search.cpp
|
apps/ui_tone_search.cpp
|
||||||
apps/ui_touch_calibration.cpp
|
apps/ui_touch_calibration.cpp
|
||||||
apps/ui_touchtunes.cpp
|
apps/ui_touchtunes.cpp
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2018 Furrtek
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@ -100,11 +101,9 @@ AnalogAudioView::AnalogAudioView(
|
|||||||
&field_volume,
|
&field_volume,
|
||||||
&text_ctcss,
|
&text_ctcss,
|
||||||
&record_view,
|
&record_view,
|
||||||
&waterfall,
|
&waterfall
|
||||||
});
|
});
|
||||||
|
|
||||||
//exit_on_squelch = eos;
|
|
||||||
|
|
||||||
field_frequency.set_value(receiver_model.tuning_frequency());
|
field_frequency.set_value(receiver_model.tuning_frequency());
|
||||||
field_frequency.set_step(receiver_model.frequency_step());
|
field_frequency.set_step(receiver_model.frequency_step());
|
||||||
field_frequency.on_change = [this](rf::Frequency f) {
|
field_frequency.on_change = [this](rf::Frequency f) {
|
||||||
@ -258,10 +257,21 @@ void AnalogAudioView::on_show_options_modulation() {
|
|||||||
switch(modulation) {
|
switch(modulation) {
|
||||||
case ReceiverModel::Mode::AMAudio:
|
case ReceiverModel::Mode::AMAudio:
|
||||||
widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
|
widget = std::make_unique<AMOptionsView>(options_view_rect, &style_options_group);
|
||||||
|
waterfall.set_fft_widget(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ReceiverModel::Mode::NarrowbandFMAudio:
|
case ReceiverModel::Mode::NarrowbandFMAudio:
|
||||||
widget = std::make_unique<NBFMOptionsView>(nbfm_view_rect, &style_options_group);
|
widget = std::make_unique<NBFMOptionsView>(nbfm_view_rect, &style_options_group);
|
||||||
|
waterfall.set_fft_widget(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ReceiverModel::Mode::WidebandFMAudio:
|
||||||
|
waterfall.set_fft_widget(true);
|
||||||
|
waterfall.on_show();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||||
|
waterfall.set_fft_widget(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -341,6 +351,7 @@ void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
|
|||||||
size_t min_idx { 0 };
|
size_t min_idx { 0 };
|
||||||
size_t c;
|
size_t c;
|
||||||
|
|
||||||
|
// Find nearest match
|
||||||
for (c = 0; c < tone_keys.size(); c++) {
|
for (c = 0; c < tone_keys.size(); c++) {
|
||||||
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
diff = abs(((float)value / 100.0) - tone_keys[c].second);
|
||||||
if (diff < min_diff) {
|
if (diff < min_diff) {
|
||||||
@ -349,6 +360,7 @@ void AnalogAudioView::handle_coded_squelch(const uint32_t value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Arbitrary confidence threshold
|
||||||
if (min_diff < 40)
|
if (min_diff < 40)
|
||||||
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
|
text_ctcss.set("CTCSS " + tone_keys[min_idx].first);
|
||||||
else
|
else
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2018 Furrtek
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
|
@ -27,6 +27,10 @@
|
|||||||
// Check what ends up in the BSS section by looking at the map files !
|
// Check what ends up in the BSS section by looking at the map files !
|
||||||
// Use constexpr where possible or make sure const are in .cpp files, not headers !
|
// Use constexpr where possible or make sure const are in .cpp files, not headers !
|
||||||
|
|
||||||
|
// Note about messages:
|
||||||
|
// There can only be one message handler for one kind of message at once
|
||||||
|
// If an attempt is made to register a second handler, there's a chDbgPanic
|
||||||
|
|
||||||
//TEST: Goertzel tone detect
|
//TEST: Goertzel tone detect
|
||||||
//TEST: Menuview refresh, seems to blink a lot
|
//TEST: Menuview refresh, seems to blink a lot
|
||||||
//TEST: Check AFSK transmit end, skips last bits ?
|
//TEST: Check AFSK transmit end, skips last bits ?
|
||||||
@ -36,7 +40,11 @@
|
|||||||
//BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one
|
//BUG: SCANNER Lock on frequency, if frequency jump, still locked on first one
|
||||||
//BUG: SCANNER Multiple slices
|
//BUG: SCANNER Multiple slices
|
||||||
//GLITCH: The about view scroller sometimes misses lines because of a race condition between the display scrolling and drawing the line
|
//GLITCH: The about view scroller sometimes misses lines because of a race condition between the display scrolling and drawing the line
|
||||||
|
//GLITCH: Start of tx using ReplayThread plays a small bit of previous transmission (content of 1 buffer ?)
|
||||||
|
// See fifo.reset_in() ?
|
||||||
|
|
||||||
|
//TODO: DCS decoder
|
||||||
|
//TODO: Make CTCSS display only when squelch is opened
|
||||||
//TODO: Make play button larger in Replay
|
//TODO: Make play button larger in Replay
|
||||||
//TODO: Put LNA and VGA controls in Soundboard
|
//TODO: Put LNA and VGA controls in Soundboard
|
||||||
//TODO: Add default headphones volume setting in Audio settings
|
//TODO: Add default headphones volume setting in Audio settings
|
||||||
|
@ -255,7 +255,7 @@ WaterfallWidget::WaterfallWidget(const bool cursor) {
|
|||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
&waterfall_view,
|
&waterfall_view,
|
||||||
&frequency_scale,
|
&frequency_scale
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,16 +311,51 @@ bool WaterfallWidget::on_key(const KeyEvent key) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WaterfallWidget::on_audio_spectrum(const AudioSpectrum& spectrum) {
|
||||||
|
if (fft_widget) {
|
||||||
|
for (size_t i = 0; i < spectrum.db.size(); i++)
|
||||||
|
audio_spectrum[i] = ((int16_t)spectrum.db[i] - 127) * 256;
|
||||||
|
fft_widget->set_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaterfallWidget::set_fft_widget(const bool show) {
|
||||||
|
if (fft_widget) {
|
||||||
|
audio_fifo = nullptr;
|
||||||
|
remove_child(fft_widget.get());
|
||||||
|
fft_widget.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
waterfall_view.set_parent_rect(waterfall_reduced_rect);
|
||||||
|
waterfall_view.on_show();
|
||||||
|
fft_widget = std::make_unique<Waveform>(
|
||||||
|
fft_widget_rect,
|
||||||
|
audio_spectrum,
|
||||||
|
128,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
Color::white());
|
||||||
|
add_child(fft_widget.get());
|
||||||
|
} else {
|
||||||
|
waterfall_view.set_parent_rect(waterfall_normal_rect);
|
||||||
|
waterfall_view.on_show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) {
|
||||||
constexpr Dim scale_height = 20;
|
constexpr Dim scale_height = 20;
|
||||||
|
|
||||||
View::set_parent_rect(new_parent_rect);
|
View::set_parent_rect(new_parent_rect);
|
||||||
|
|
||||||
|
waterfall_normal_rect = { 0, scale_height, new_parent_rect.width(), new_parent_rect.height() - scale_height};
|
||||||
|
waterfall_reduced_rect = { 0, scale_height, new_parent_rect.width(), new_parent_rect.height() - scale_height - audio_spectrum_height };
|
||||||
|
|
||||||
frequency_scale.set_parent_rect({ 0, 0, new_parent_rect.width(), scale_height });
|
frequency_scale.set_parent_rect({ 0, 0, new_parent_rect.width(), scale_height });
|
||||||
waterfall_view.set_parent_rect({
|
waterfall_view.set_parent_rect(waterfall_normal_rect);
|
||||||
0, scale_height,
|
waterfall_view.on_show();
|
||||||
new_parent_rect.width(),
|
|
||||||
new_parent_rect.height() - scale_height
|
fft_widget_rect = { 0, new_parent_rect.height() - audio_spectrum_height, new_parent_rect.width(), audio_spectrum_height };
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaterfallWidget::paint(Painter& painter) {
|
void WaterfallWidget::paint(Painter& painter) {
|
||||||
|
@ -98,40 +98,66 @@ public:
|
|||||||
|
|
||||||
void set_parent_rect(const Rect new_parent_rect) override;
|
void set_parent_rect(const Rect new_parent_rect) override;
|
||||||
|
|
||||||
|
void set_fft_widget(const bool show);
|
||||||
|
|
||||||
void paint(Painter& painter) override;
|
void paint(Painter& painter) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void on_tick_second();
|
void on_tick_second();
|
||||||
|
|
||||||
|
static constexpr ui::Dim audio_spectrum_height = 2 * 16;
|
||||||
|
|
||||||
WaterfallView waterfall_view { };
|
WaterfallView waterfall_view { };
|
||||||
FrequencyScale frequency_scale { };
|
FrequencyScale frequency_scale { };
|
||||||
ChannelSpectrumFIFO* fifo { nullptr };
|
|
||||||
|
|
||||||
|
ChannelSpectrumFIFO* channel_fifo { nullptr };
|
||||||
|
AudioSpectrumFIFO* audio_fifo { nullptr };
|
||||||
|
|
||||||
|
std::unique_ptr<Widget> fft_widget { };
|
||||||
bool _blink { false };
|
bool _blink { false };
|
||||||
int sampling_rate { 0 };
|
int sampling_rate { 0 };
|
||||||
int32_t cursor_position { 0 };
|
int32_t cursor_position { 0 };
|
||||||
SignalToken signal_token_tick_second { };
|
SignalToken signal_token_tick_second { };
|
||||||
|
int16_t audio_spectrum[128] { 0 };
|
||||||
|
ui::Rect waterfall_normal_rect { };
|
||||||
|
ui::Rect waterfall_reduced_rect { };
|
||||||
|
ui::Rect fft_widget_rect { };
|
||||||
|
|
||||||
MessageHandlerRegistration message_handler_spectrum_config {
|
MessageHandlerRegistration message_handler_channel_spectrum_config {
|
||||||
Message::ID::ChannelSpectrumConfig,
|
Message::ID::ChannelSpectrumConfig,
|
||||||
[this](const Message* const p) {
|
[this](const Message* const p) {
|
||||||
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
||||||
this->fifo = message.fifo;
|
this->channel_fifo = message.fifo;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
MessageHandlerRegistration message_handler_audio_spectrum_config {
|
||||||
|
Message::ID::AudioSpectrumConfig,
|
||||||
|
[this](const Message* const p) {
|
||||||
|
const auto message = *reinterpret_cast<const AudioSpectrumConfigMessage*>(p);
|
||||||
|
this->audio_fifo = message.fifo;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MessageHandlerRegistration message_handler_frame_sync {
|
MessageHandlerRegistration message_handler_frame_sync {
|
||||||
Message::ID::DisplayFrameSync,
|
Message::ID::DisplayFrameSync,
|
||||||
[this](const Message* const) {
|
[this](const Message* const) {
|
||||||
if( this->fifo ) {
|
if( this->channel_fifo ) {
|
||||||
ChannelSpectrum channel_spectrum;
|
ChannelSpectrum channel_spectrum;
|
||||||
while( fifo->out(channel_spectrum) ) {
|
while( channel_fifo->out(channel_spectrum) ) {
|
||||||
this->on_channel_spectrum(channel_spectrum);
|
this->on_channel_spectrum(channel_spectrum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if( this->audio_fifo ) {
|
||||||
|
AudioSpectrum audio_spectrum;
|
||||||
|
while( audio_fifo->out(audio_spectrum) ) {
|
||||||
|
// Unstack everything until and only use last buffer (should only be one max. ready per frame)
|
||||||
|
}
|
||||||
|
this->on_audio_spectrum(audio_spectrum);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||||
|
void on_audio_spectrum(const AudioSpectrum& spectrum);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace spectrum */
|
} /* namespace spectrum */
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
#include "ui_sonde.hpp"
|
#include "ui_sonde.hpp"
|
||||||
#include "ui_soundboard.hpp"
|
#include "ui_soundboard.hpp"
|
||||||
#include "ui_sstvtx.hpp"
|
#include "ui_sstvtx.hpp"
|
||||||
#include "ui_test.hpp"
|
//#include "ui_test.hpp"
|
||||||
#include "ui_tone_search.hpp"
|
#include "ui_tone_search.hpp"
|
||||||
#include "ui_touchtunes.hpp"
|
#include "ui_touchtunes.hpp"
|
||||||
#include "ui_view_wav.hpp"
|
#include "ui_view_wav.hpp"
|
||||||
|
@ -22,8 +22,9 @@
|
|||||||
|
|
||||||
#include "proc_wfm_audio.hpp"
|
#include "proc_wfm_audio.hpp"
|
||||||
|
|
||||||
|
#include "portapack_shared_memory.hpp"
|
||||||
#include "audio_output.hpp"
|
#include "audio_output.hpp"
|
||||||
|
#include "dsp_fft.hpp"
|
||||||
#include "event_m4.hpp"
|
#include "event_m4.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -64,6 +65,49 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
|
|||||||
* -> 96kHz int16_t[64] */
|
* -> 96kHz int16_t[64] */
|
||||||
auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer);
|
auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer);
|
||||||
|
|
||||||
|
// Input: 96kHz int16_t[64]
|
||||||
|
// audio_spectrum_decimator piles up 256 bytes before doing FFT computation
|
||||||
|
// This should send an AudioSpectrum every sample rate/buffer size/(256/64)/refresh scaler = 3072000/2048/4/8 = ~47 Hz
|
||||||
|
// 0~3: feed continuous audio
|
||||||
|
// 4~31: ignore, wrap at 31
|
||||||
|
if (!(refresh_timer & 0xF8)) {
|
||||||
|
for (size_t i = 0; i < 64; i++) {
|
||||||
|
complex_audio[i] = { (int16_t)(work_audio_buffer.p[i] / 32), (int16_t)0 };
|
||||||
|
}
|
||||||
|
audio_spectrum_decimator.feed(
|
||||||
|
complex_audio_buffer,
|
||||||
|
[this](const buffer_c16_t& data) {
|
||||||
|
this->post_message(data);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Spread the FFT workload in time to avoid making the audio skip
|
||||||
|
// "8" comes from the log2() of the size of audio_spectrum: log2(256) = 8
|
||||||
|
if (fft_stage && (fft_stage <= 8)) {
|
||||||
|
fft_c_preswapped(audio_spectrum, fft_stage - 1, fft_stage);
|
||||||
|
fft_stage++;
|
||||||
|
} else if (fft_stage > 8) {
|
||||||
|
AudioSpectrum spectrum;
|
||||||
|
const size_t spectrum_end = spectrum.db.size();
|
||||||
|
for(size_t i=0; i<spectrum_end; i++) {
|
||||||
|
//const auto corrected_sample = spectrum_window_hamming_3(audio_spectrum, i);
|
||||||
|
const auto corrected_sample = audio_spectrum[i];
|
||||||
|
const auto mag2 = magnitude_squared(corrected_sample * (1.0f / 32768.0f));
|
||||||
|
const float db = mag2_to_dbv_norm(mag2);
|
||||||
|
constexpr float mag_scale = 5.0f;
|
||||||
|
const unsigned int v = (db * mag_scale) + 255.0f;
|
||||||
|
spectrum.db[i] = std::max(0U, std::min(255U, v));
|
||||||
|
}
|
||||||
|
fifo.in(spectrum);
|
||||||
|
fft_stage = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh_timer == 31)
|
||||||
|
refresh_timer = 0;
|
||||||
|
else
|
||||||
|
refresh_timer++;
|
||||||
|
|
||||||
/* 96kHz int16_t[64]
|
/* 96kHz int16_t[64]
|
||||||
* -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1
|
* -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1
|
||||||
* -> 48kHz int16_t[32] */
|
* -> 48kHz int16_t[32] */
|
||||||
@ -74,6 +118,12 @@ void WidebandFMAudio::execute(const buffer_c8_t& buffer) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WidebandFMAudio::post_message(const buffer_c16_t& data) {
|
||||||
|
// This is called when audio_spectrum_decimator is filled up to 256 samples
|
||||||
|
fft_swap(data, audio_spectrum);
|
||||||
|
fft_stage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
void WidebandFMAudio::on_message(const Message* const message) {
|
void WidebandFMAudio::on_message(const Message* const message) {
|
||||||
switch(message->id) {
|
switch(message->id) {
|
||||||
case Message::ID::UpdateSpectrum:
|
case Message::ID::UpdateSpectrum:
|
||||||
@ -117,6 +167,9 @@ void WidebandFMAudio::configure(const WFMConfigureMessage& message) {
|
|||||||
channel_spectrum.set_decimation_factor(1);
|
channel_spectrum.set_decimation_factor(1);
|
||||||
|
|
||||||
configured = true;
|
configured = true;
|
||||||
|
|
||||||
|
AudioSpectrumConfigMessage config_message { &fifo };
|
||||||
|
shared_memory.application_queue.push(config_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WidebandFMAudio::capture_config(const CaptureConfigMessage& message) {
|
void WidebandFMAudio::capture_config(const CaptureConfigMessage& message) {
|
||||||
|
@ -27,8 +27,10 @@
|
|||||||
#include "baseband_thread.hpp"
|
#include "baseband_thread.hpp"
|
||||||
#include "rssi_thread.hpp"
|
#include "rssi_thread.hpp"
|
||||||
|
|
||||||
|
#include "dsp_types.hpp"
|
||||||
#include "dsp_decimate.hpp"
|
#include "dsp_decimate.hpp"
|
||||||
#include "dsp_demodulate.hpp"
|
#include "dsp_demodulate.hpp"
|
||||||
|
#include "block_decimator.hpp"
|
||||||
|
|
||||||
#include "audio_output.hpp"
|
#include "audio_output.hpp"
|
||||||
#include "spectrum_collector.hpp"
|
#include "spectrum_collector.hpp"
|
||||||
@ -56,6 +58,12 @@ private:
|
|||||||
sizeof(dst) / sizeof(int16_t)
|
sizeof(dst) / sizeof(int16_t)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::array<complex16_t, 64> complex_audio { };
|
||||||
|
const buffer_c16_t complex_audio_buffer {
|
||||||
|
complex_audio.data(),
|
||||||
|
complex_audio.size()
|
||||||
|
};
|
||||||
|
|
||||||
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0 { };
|
dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0 { };
|
||||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1 { };
|
dsp::decimate::FIRC16xR16x16Decim2 decim_1 { };
|
||||||
uint32_t channel_filter_pass_f = 0;
|
uint32_t channel_filter_pass_f = 0;
|
||||||
@ -68,6 +76,14 @@ private:
|
|||||||
|
|
||||||
AudioOutput audio_output { };
|
AudioOutput audio_output { };
|
||||||
|
|
||||||
|
// For fs=96kHz FFT streaming
|
||||||
|
BlockDecimator<complex16_t, 256> audio_spectrum_decimator { 1 };
|
||||||
|
AudioSpectrum fifo_data[1 << AudioSpectrumConfigMessage::fifo_k] { };
|
||||||
|
AudioSpectrumFIFO fifo { fifo_data, AudioSpectrumConfigMessage::fifo_k };
|
||||||
|
std::array<std::complex<float>, 256> audio_spectrum { };
|
||||||
|
uint32_t refresh_timer { 0 };
|
||||||
|
uint32_t fft_stage { 0 };
|
||||||
|
|
||||||
SpectrumCollector channel_spectrum { };
|
SpectrumCollector channel_spectrum { };
|
||||||
size_t spectrum_interval_samples = 0;
|
size_t spectrum_interval_samples = 0;
|
||||||
size_t spectrum_samples = 0;
|
size_t spectrum_samples = 0;
|
||||||
@ -75,6 +91,7 @@ private:
|
|||||||
bool configured { false };
|
bool configured { false };
|
||||||
void configure(const WFMConfigureMessage& message);
|
void configure(const WFMConfigureMessage& message);
|
||||||
void capture_config(const CaptureConfigMessage& message);
|
void capture_config(const CaptureConfigMessage& message);
|
||||||
|
void post_message(const buffer_c16_t& data);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif/*__PROC_WFM_AUDIO_H__*/
|
#endif/*__PROC_WFM_AUDIO_H__*/
|
||||||
|
@ -132,7 +132,7 @@ void SpectrumCollector::update() {
|
|||||||
// Called from idle thread (after EVT_MASK_SPECTRUM is flagged)
|
// Called from idle thread (after EVT_MASK_SPECTRUM is flagged)
|
||||||
if( streaming && channel_spectrum_request_update ) {
|
if( streaming && channel_spectrum_request_update ) {
|
||||||
/* Decimated buffer is full. Compute spectrum. */
|
/* Decimated buffer is full. Compute spectrum. */
|
||||||
fft_c_preswapped(channel_spectrum);
|
fft_c_preswapped(channel_spectrum, 0, 8);
|
||||||
|
|
||||||
ChannelSpectrum spectrum;
|
ChannelSpectrum spectrum;
|
||||||
spectrum.sampling_rate = channel_spectrum_sampling_rate;
|
spectrum.sampling_rate = channel_spectrum_sampling_rate;
|
||||||
|
@ -100,25 +100,26 @@ void fft_swap_in_place(std::array<T, N>& data) {
|
|||||||
/* http://www.drdobbs.com/cpp/a-simple-and-efficient-fft-implementatio/199500857?pgno=3 */
|
/* http://www.drdobbs.com/cpp/a-simple-and-efficient-fft-implementatio/199500857?pgno=3 */
|
||||||
|
|
||||||
template<typename T, size_t N>
|
template<typename T, size_t N>
|
||||||
void fft_c_preswapped(std::array<T, N>& data) {
|
void fft_c_preswapped(std::array<T, N>& data, const size_t from, const size_t to) {
|
||||||
static_assert(power_of_two(N), "only defined for N == power of two");
|
static_assert(power_of_two(N), "only defined for N == power of two");
|
||||||
constexpr auto K = log_2(N);
|
constexpr auto K = log_2(N);
|
||||||
|
if ((to > K) || (from > K)) return;
|
||||||
|
|
||||||
constexpr size_t K_max = 8;
|
constexpr size_t K_max = 8;
|
||||||
static_assert(K <= K_max, "No FFT twiddle factors for K > 8");
|
static_assert(K <= K_max, "No FFT twiddle factors for K > 8");
|
||||||
static constexpr std::array<std::complex<float>, K_max> wp_table { {
|
static constexpr std::array<std::complex<float>, K_max> wp_table { {
|
||||||
{ -2.0f, 0.0f },
|
{ -2.0f, 0.0f }, // 2
|
||||||
{ -1.0f, -1.0f },
|
{ -1.0f, -1.0f }, // 4
|
||||||
{ -0.2928932188134524756f, -0.7071067811865475244f },
|
{ -0.2928932188134524756f, -0.7071067811865475244f }, // 8
|
||||||
{ -0.076120467488713243872f, -0.38268343236508977173f },
|
{ -0.076120467488713243872f, -0.38268343236508977173f }, // 16
|
||||||
{ -0.019214719596769550874f, -0.19509032201612826785f },
|
{ -0.019214719596769550874f, -0.19509032201612826785f }, // 32
|
||||||
{ -0.0048152733278031137552f, -0.098017140329560601994f },
|
{ -0.0048152733278031137552f, -0.098017140329560601994f }, // 64
|
||||||
{ -0.0012045437948276072852f, -0.049067674327418014255f },
|
{ -0.0012045437948276072852f, -0.049067674327418014255f }, // 128
|
||||||
{ -0.00030118130379577988423f, -0.024541228522912288032f },
|
{ -0.00030118130379577988423f, -0.024541228522912288032f }, // 256
|
||||||
} };
|
} };
|
||||||
|
|
||||||
/* Provide data to this function, pre-swapped. */
|
/* Provide data to this function, pre-swapped. */
|
||||||
for(size_t k = 0; k < log_2(N); k++) {
|
for(size_t k = from; k < to; k++) {
|
||||||
const size_t mmax = 1 << k;
|
const size_t mmax = 1 << k;
|
||||||
const auto wp = wp_table[k];
|
const auto wp = wp_table[k];
|
||||||
T w { 1.0f, 0.0f };
|
T w { 1.0f, 0.0f };
|
||||||
|
@ -297,6 +297,15 @@ void ILI9341::fill_rectangle(ui::Rect r, const ui::Color c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ILI9341::fill_rectangle_unrolled8(ui::Rect r, const ui::Color c) {
|
||||||
|
const auto r_clipped = r.intersect(screen_rect());
|
||||||
|
if( !r_clipped.is_empty() ) {
|
||||||
|
lcd_start_ram_write(r_clipped);
|
||||||
|
size_t count = r_clipped.width() * r_clipped.height();
|
||||||
|
io.lcd_write_pixels_unrolled8(c, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ILI9341::render_line(const ui::Point p, const uint8_t count, const ui::Color* line_buffer) {
|
void ILI9341::render_line(const ui::Point p, const uint8_t count, const ui::Color* line_buffer) {
|
||||||
lcd_start_ram_write(p, { count, 1 });
|
lcd_start_ram_write(p, { count, 1 });
|
||||||
io.lcd_write_pixels(line_buffer, count);
|
io.lcd_write_pixels(line_buffer, count);
|
||||||
|
@ -49,6 +49,7 @@ public:
|
|||||||
void wake();
|
void wake();
|
||||||
|
|
||||||
void fill_rectangle(ui::Rect r, const ui::Color c);
|
void fill_rectangle(ui::Rect r, const ui::Color c);
|
||||||
|
void fill_rectangle_unrolled8(ui::Rect r, const ui::Color c);
|
||||||
void draw_line(const ui::Point start, const ui::Point end, const ui::Color color);
|
void draw_line(const ui::Point start, const ui::Point end, const ui::Color color);
|
||||||
void fill_circle(
|
void fill_circle(
|
||||||
const ui::Point center,
|
const ui::Point center,
|
||||||
|
@ -106,6 +106,7 @@ public:
|
|||||||
|
|
||||||
AudioLevelReport = 51,
|
AudioLevelReport = 51,
|
||||||
CodedSquelch = 52,
|
CodedSquelch = 52,
|
||||||
|
AudioSpectrumConfig = 53,
|
||||||
MAX
|
MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -273,6 +274,27 @@ public:
|
|||||||
size_t trigger { 0 };
|
size_t trigger { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AudioSpectrum {
|
||||||
|
std::array<uint8_t, 128> db { { 0 } };
|
||||||
|
//uint32_t sampling_rate { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
using AudioSpectrumFIFO = FIFO<AudioSpectrum>;
|
||||||
|
|
||||||
|
class AudioSpectrumConfigMessage : public Message {
|
||||||
|
public:
|
||||||
|
static constexpr size_t fifo_k = 2;
|
||||||
|
|
||||||
|
constexpr AudioSpectrumConfigMessage(
|
||||||
|
AudioSpectrumFIFO* fifo
|
||||||
|
) : Message { ID::AudioSpectrumConfig },
|
||||||
|
fifo { fifo }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioSpectrumFIFO* fifo { nullptr };
|
||||||
|
};
|
||||||
|
|
||||||
struct ChannelSpectrum {
|
struct ChannelSpectrum {
|
||||||
std::array<uint8_t, 256> db { { 0 } };
|
std::array<uint8_t, 256> db { { 0 } };
|
||||||
uint32_t sampling_rate { 0 };
|
uint32_t sampling_rate { 0 };
|
||||||
|
@ -158,6 +158,21 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void lcd_write_pixels_unrolled8(const ui::Color pixel, size_t n) {
|
||||||
|
auto v = pixel.v;
|
||||||
|
n >>= 3;
|
||||||
|
while(n--) {
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
lcd_write_data(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void lcd_write_pixels(const ui::Color* const pixels, size_t n) {
|
void lcd_write_pixels(const ui::Color* const pixels, size_t n) {
|
||||||
for(size_t i=0; i<n; i++) {
|
for(size_t i=0; i<n; i++) {
|
||||||
lcd_write_pixel(pixels[i]);
|
lcd_write_pixel(pixels[i]);
|
||||||
|
@ -98,6 +98,10 @@ void Painter::fill_rectangle(const Rect r, const Color c) {
|
|||||||
display.fill_rectangle(r, c);
|
display.fill_rectangle(r, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Painter::fill_rectangle_unrolled8(const Rect r, const Color c) {
|
||||||
|
display.fill_rectangle_unrolled8(r, c);
|
||||||
|
}
|
||||||
|
|
||||||
void Painter::paint_widget_tree(Widget* const w) {
|
void Painter::paint_widget_tree(Widget* const w) {
|
||||||
if( ui::is_dirty() ) {
|
if( ui::is_dirty() ) {
|
||||||
paint_widget(w);
|
paint_widget(w);
|
||||||
|
@ -56,6 +56,7 @@ public:
|
|||||||
|
|
||||||
void draw_rectangle(const Rect r, const Color c);
|
void draw_rectangle(const Rect r, const Color c);
|
||||||
void fill_rectangle(const Rect r, const Color c);
|
void fill_rectangle(const Rect r, const Color c);
|
||||||
|
void fill_rectangle_unrolled8(const Rect r, const Color c);
|
||||||
|
|
||||||
void paint_widget_tree(Widget* const w);
|
void paint_widget_tree(Widget* const w);
|
||||||
|
|
||||||
|
@ -1493,6 +1493,7 @@ Waveform::Waveform(
|
|||||||
color_ { color }
|
color_ { color }
|
||||||
{
|
{
|
||||||
//set_focusable(false);
|
//set_focusable(false);
|
||||||
|
//previous_data.resize(length_, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Waveform::set_cursor(const uint32_t i, const int16_t position) {
|
void Waveform::set_cursor(const uint32_t i, const int16_t position) {
|
||||||
@ -1522,19 +1523,31 @@ void Waveform::set_length(const uint32_t new_length) {
|
|||||||
void Waveform::paint(Painter& painter) {
|
void Waveform::paint(Painter& painter) {
|
||||||
size_t n;
|
size_t n;
|
||||||
Coord y, y_offset = screen_rect().location().y();
|
Coord y, y_offset = screen_rect().location().y();
|
||||||
Coord prev_x = screen_rect().location().x(), prev_y;
|
Coord prev_x, prev_y;
|
||||||
float x, x_inc;
|
float x, x_inc;
|
||||||
Dim h = screen_rect().size().height();
|
Dim h = screen_rect().size().height();
|
||||||
const float y_scale = (float)(h - 1) / 65536.0;
|
const float y_scale = (float)(h - 1) / 65536.0;
|
||||||
int16_t * data_start = data_ + offset_;
|
int16_t * data_start = data_ + offset_;
|
||||||
|
|
||||||
// Clear
|
|
||||||
painter.fill_rectangle(screen_rect(), Color::black());
|
|
||||||
|
|
||||||
if (!length_) return;
|
if (!length_) return;
|
||||||
|
|
||||||
x_inc = (float)screen_rect().size().width() / length_;
|
x_inc = (float)screen_rect().size().width() / length_;
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
painter.fill_rectangle_unrolled8(screen_rect(), Color::black());
|
||||||
|
/*prev_x = screen_rect().location().x();
|
||||||
|
prev_y = previous_data[0];
|
||||||
|
x = prev_x + x_inc;
|
||||||
|
for (n = 1; n < length_; n++) {
|
||||||
|
y = previous_data[n];
|
||||||
|
display.draw_line( {prev_x, prev_y}, {(Coord)x, y}, Color::black());
|
||||||
|
|
||||||
|
prev_x = x;
|
||||||
|
prev_y = y;
|
||||||
|
x += x_inc;
|
||||||
|
}*/
|
||||||
|
prev_x = screen_rect().location().x();
|
||||||
|
|
||||||
if (digital_) {
|
if (digital_) {
|
||||||
// Digital waveform: each value is an horizontal line
|
// Digital waveform: each value is an horizontal line
|
||||||
x = 0;
|
x = 0;
|
||||||
@ -1557,8 +1570,10 @@ void Waveform::paint(Painter& painter) {
|
|||||||
x = prev_x + x_inc;
|
x = prev_x + x_inc;
|
||||||
h /= 2;
|
h /= 2;
|
||||||
prev_y = y_offset + h - (*(data_start++) * y_scale);
|
prev_y = y_offset + h - (*(data_start++) * y_scale);
|
||||||
|
//previous_data[0] = prev_y;
|
||||||
for (n = 1; n < length_; n++) {
|
for (n = 1; n < length_; n++) {
|
||||||
y = y_offset + h - (*(data_start++) * y_scale);
|
y = y_offset + h - (*(data_start++) * y_scale);
|
||||||
|
//previous_data[n] = y;
|
||||||
display.draw_line( {prev_x, prev_y}, {(Coord)x, y}, color_);
|
display.draw_line( {prev_x, prev_y}, {(Coord)x, y}, color_);
|
||||||
|
|
||||||
prev_x = x;
|
prev_x = x;
|
||||||
|
@ -617,6 +617,7 @@ private:
|
|||||||
const Color cursor_colors[2] = { Color::cyan(), Color::magenta() };
|
const Color cursor_colors[2] = { Color::cyan(), Color::magenta() };
|
||||||
|
|
||||||
int16_t * data_;
|
int16_t * data_;
|
||||||
|
//std::vector<int16_t> previous_data { };
|
||||||
uint32_t length_;
|
uint32_t length_;
|
||||||
uint32_t offset_;
|
uint32_t offset_;
|
||||||
bool digital_ { false };
|
bool digital_ { false };
|
||||||
|
Loading…
Reference in New Issue
Block a user