mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Decode status widget (#1431)
* Initial cleanup of pocsag beta, using DSP filters * Better filter params * Better filter * Add signal diagnostics widgets * POCSAG procs sends stats messages * Only draw 32 bits * Add AudioNormalizer filter
This commit is contained in:
parent
2435ee780f
commit
4819a2f4e2
@ -105,6 +105,8 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
||||
&field_volume,
|
||||
&image_status,
|
||||
&text_packet_count,
|
||||
&widget_bits,
|
||||
&widget_frames,
|
||||
&button_ignore_last,
|
||||
&button_config,
|
||||
&console});
|
||||
@ -271,7 +273,31 @@ void POCSAGAppView::on_packet(const POCSAGPacketMessage* message) {
|
||||
image_status.set_foreground(get_status_color(pocsag_state));
|
||||
}
|
||||
|
||||
void POCSAGAppView::on_stats(const POCSAGStatsMessage*) {
|
||||
void POCSAGAppView::on_stats(const POCSAGStatsMessage* stats) {
|
||||
widget_bits.set_bits(stats->current_bits);
|
||||
widget_frames.set_frames(stats->current_frames);
|
||||
widget_frames.set_sync(stats->has_sync);
|
||||
}
|
||||
|
||||
void BitsIndicator::paint(Painter&) {
|
||||
auto p = screen_pos();
|
||||
for (size_t i = 0; i < sizeof(bits_) * 8; ++i) {
|
||||
auto is_set = ((bits_ >> i) & 0x1) == 1;
|
||||
|
||||
int x = p.x() + (i / height);
|
||||
int y = p.y() + (i % height);
|
||||
display.draw_pixel({x, y}, is_set ? Color::white() : Color::black());
|
||||
}
|
||||
}
|
||||
|
||||
void FrameIndicator::paint(Painter& painter) {
|
||||
auto p = screen_pos();
|
||||
painter.draw_rectangle({p, {2, height}}, has_sync_ ? Color::green() : Color::grey());
|
||||
|
||||
for (size_t i = 0; i < height; ++i) {
|
||||
auto p2 = p + Point{2, 16 - (int)i};
|
||||
painter.draw_hline(p2, 2, i < frame_count_ ? Color::white() : Color::black());
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
@ -52,6 +52,50 @@ class POCSAGLogger {
|
||||
|
||||
namespace ui {
|
||||
|
||||
class BitsIndicator : public Widget {
|
||||
public:
|
||||
BitsIndicator(Point position)
|
||||
: Widget{{position, {2, height}}} {}
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void set_bits(uint32_t bits) {
|
||||
if (bits != bits_) {
|
||||
bits_ = bits;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint8_t height = 16;
|
||||
uint32_t bits_ = 0;
|
||||
};
|
||||
|
||||
class FrameIndicator : public Widget {
|
||||
public:
|
||||
FrameIndicator(Point position)
|
||||
: Widget{{position, {4, height}}} {}
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void set_frames(uint8_t frame_count) {
|
||||
if (frame_count != frame_count_) {
|
||||
frame_count_ = frame_count;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
void set_sync(bool has_sync) {
|
||||
if (has_sync != has_sync_) {
|
||||
has_sync_ = has_sync;
|
||||
set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint8_t height = 16;
|
||||
uint8_t frame_count_ = 0;
|
||||
bool has_sync_ = false;
|
||||
};
|
||||
|
||||
struct POCSAGSettings {
|
||||
bool enable_small_font = false;
|
||||
bool enable_logging = false;
|
||||
@ -187,7 +231,8 @@ class POCSAGAppView : public View {
|
||||
2,
|
||||
{0, 99},
|
||||
1,
|
||||
' '};
|
||||
' ',
|
||||
true /*wrap*/};
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
@ -201,6 +246,12 @@ class POCSAGAppView : public View {
|
||||
{3 * 8, 1 * 16 + 2, 5 * 8, 16},
|
||||
"0"};
|
||||
|
||||
BitsIndicator widget_bits{
|
||||
{9 * 7 + 6, 1 * 16 + 2}};
|
||||
|
||||
FrameIndicator widget_frames{
|
||||
{9 * 8, 1 * 16 + 2}};
|
||||
|
||||
Button button_ignore_last{
|
||||
{10 * 8, 1 * 16, 12 * 8, 20},
|
||||
"Ignore Last"};
|
||||
|
@ -449,7 +449,7 @@ DeclareTargets(PPOC pocsag)
|
||||
set(MODE_CPPSRC
|
||||
proc_pocsag2.cpp
|
||||
)
|
||||
DeclareTargets(PPOC pocsag2)
|
||||
DeclareTargets(PPO2 pocsag2)
|
||||
|
||||
### RDS
|
||||
|
||||
|
@ -56,6 +56,12 @@ void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
|
||||
processDemodulatedSamples(audio.p, 16);
|
||||
extractFrames();
|
||||
|
||||
samples_processed += buffer.count;
|
||||
if (samples_processed >= stat_update_threshold) {
|
||||
send_stats();
|
||||
samples_processed = 0;
|
||||
}
|
||||
|
||||
// Clear the output before sending to audio chip.
|
||||
// Only clear the audio buffer when there hasn't been any audio for a while.
|
||||
if (squelch_.enabled() && squelch_history == 0) {
|
||||
@ -137,6 +143,11 @@ void POCSAGProcessor::configure() {
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void POCSAGProcessor::send_stats() const {
|
||||
POCSAGStatsMessage message(m_fifo.codeword, m_numCode, m_gotSync);
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Frame extractraction methods
|
||||
// -----------------------------
|
||||
|
@ -137,6 +137,9 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
static constexpr uint8_t stat_update_interval = 10;
|
||||
static constexpr uint32_t stat_update_threshold =
|
||||
baseband_fs / stat_update_interval;
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
@ -158,7 +161,10 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
bool configured = false;
|
||||
pocsag::POCSAGPacket packet{};
|
||||
|
||||
uint32_t samples_processed = 0;
|
||||
|
||||
void configure();
|
||||
void send_stats() const;
|
||||
|
||||
// ----------------------------------------
|
||||
// Frame extractraction methods and members
|
||||
|
@ -25,7 +25,6 @@
|
||||
|
||||
#include "proc_pocsag2.hpp"
|
||||
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "event_m4.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@ -33,50 +32,106 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void POCSAGProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
// buffer has 2048 samples
|
||||
// decim0 out: 2048/8 = 256 samples
|
||||
// decim1 out: 256/8 = 32 samples
|
||||
// channel out: 32/2 = 16 samples
|
||||
|
||||
// Get 24kHz audio
|
||||
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
|
||||
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
|
||||
const auto channel_out = channel_filter.execute(decim_1_out, dst_buffer);
|
||||
auto audio = demod.execute(channel_out, audio_buffer);
|
||||
|
||||
// If squelching, check for audio before smoothing because smoothing
|
||||
// causes the squelch noise detection to fail. Likely because squelch
|
||||
// looks for HF noise and smoothing is basically a lowpass filter.
|
||||
// NB: Squelch in this processor is only for the the audio output.
|
||||
// Squelching will likely drop data "noise" and break processing.
|
||||
if (squelch_.enabled()) {
|
||||
bool has_audio = squelch_.execute(audio);
|
||||
squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0);
|
||||
// Check if there's any signal in the audio buffer.
|
||||
bool has_audio = squelch.execute(audio);
|
||||
squelch_history = (squelch_history << 1) | (has_audio ? 1 : 0);
|
||||
|
||||
// Has there been any signal?
|
||||
if (squelch_history == 0) {
|
||||
// No signal for a while, flush and reset.
|
||||
if (!has_been_reset) {
|
||||
OnDataFrame(m_numCode, getRate());
|
||||
resetVals();
|
||||
send_stats();
|
||||
}
|
||||
|
||||
// Clear the audio stream before sending.
|
||||
for (size_t i = 0; i < audio.count; ++i)
|
||||
audio.p[i] = 0.0;
|
||||
|
||||
audio_output.write(audio);
|
||||
return;
|
||||
}
|
||||
|
||||
smooth.Process(audio.p, audio.count);
|
||||
// Filter out high-frequency noise. TODO: compensate gain?
|
||||
lpf.execute_in_place(audio);
|
||||
normalizer.execute_in_place(audio);
|
||||
audio_output.write(audio);
|
||||
|
||||
processDemodulatedSamples(audio.p, 16);
|
||||
extractFrames();
|
||||
|
||||
// Clear the output before sending to audio chip.
|
||||
if (squelch_.enabled() && squelch_history == 0) {
|
||||
for (size_t i = 0; i < audio.count; ++i) {
|
||||
audio.p[i] = 0.0;
|
||||
}
|
||||
samples_processed += buffer.count;
|
||||
if (samples_processed >= stat_update_threshold) {
|
||||
send_stats();
|
||||
samples_processed = 0;
|
||||
}
|
||||
|
||||
audio_output.write(audio);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::on_message(const Message* const message) {
|
||||
switch (message->id) {
|
||||
case Message::ID::POCSAGConfigure:
|
||||
configure();
|
||||
break;
|
||||
|
||||
case Message::ID::NBFMConfigure: {
|
||||
auto config = reinterpret_cast<const NBFMConfigureMessage*>(message);
|
||||
squelch.set_threshold(config->squelch_level / 99.0);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void POCSAGProcessor::configure() {
|
||||
constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor;
|
||||
constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor;
|
||||
const size_t channel_filter_output_fs = decim_1_output_fs / 2;
|
||||
const size_t demod_input_fs = channel_filter_output_fs;
|
||||
|
||||
decim_0.configure(taps_11k0_decim_0.taps);
|
||||
decim_1.configure(taps_11k0_decim_1.taps);
|
||||
channel_filter.configure(taps_11k0_channel.taps, 2);
|
||||
demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz.
|
||||
|
||||
// Don't process the audio stream.
|
||||
audio_output.configure(false);
|
||||
|
||||
// Set up the frame extraction, limits of baud.
|
||||
setFrameExtractParams(demod_input_fs, 4000, 300, 32);
|
||||
|
||||
// Set ready to process data.
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void POCSAGProcessor::send_stats() const {
|
||||
POCSAGStatsMessage message(m_fifo.codeword, m_numCode, m_gotSync);
|
||||
shared_memory.application_queue.push(message);
|
||||
}
|
||||
|
||||
int POCSAGProcessor::OnDataWord(uint32_t word, int pos) {
|
||||
packet.set(pos, word);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::OnDataFrame(int len, int baud) {
|
||||
if (len > 0) {
|
||||
packet.set_bitrate(baud);
|
||||
@ -88,57 +143,6 @@ int POCSAGProcessor::OnDataFrame(int len, int baud) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void POCSAGProcessor::on_message(const Message* const message) {
|
||||
switch (message->id) {
|
||||
case Message::ID::POCSAGConfigure:
|
||||
configure();
|
||||
break;
|
||||
|
||||
case Message::ID::NBFMConfigure: {
|
||||
auto config = reinterpret_cast<const NBFMConfigureMessage*>(message);
|
||||
squelch_.set_threshold(config->squelch_level / 100.0);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void POCSAGProcessor::configure() {
|
||||
constexpr size_t decim_0_input_fs = baseband_fs;
|
||||
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
|
||||
|
||||
constexpr size_t decim_1_input_fs = decim_0_output_fs;
|
||||
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
|
||||
|
||||
constexpr size_t channel_filter_input_fs = decim_1_output_fs;
|
||||
const size_t channel_filter_output_fs = channel_filter_input_fs / 2;
|
||||
|
||||
const size_t demod_input_fs = channel_filter_output_fs;
|
||||
|
||||
decim_0.configure(taps_11k0_decim_0.taps);
|
||||
decim_1.configure(taps_11k0_decim_1.taps);
|
||||
channel_filter.configure(taps_11k0_channel.taps, 2);
|
||||
demod.configure(demod_input_fs, 4'500); // FSK +/- 4k5Hz.
|
||||
|
||||
// Smoothing should be roughly sample rate over max baud
|
||||
// 24k / 3.2k = 7.5
|
||||
smooth.SetSize(8);
|
||||
|
||||
// Don't have audio process the stream.
|
||||
audio_output.configure(false);
|
||||
|
||||
// Set up the frame extraction, limits of baud
|
||||
setFrameExtractParams(demod_input_fs, 4000, 300, 32);
|
||||
|
||||
// Mark the class as ready to accept data
|
||||
configured = true;
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Frame extractraction methods
|
||||
// -----------------------------
|
||||
#define BAUD_STABLE (104)
|
||||
#define MAX_CONSEC_SAME (32)
|
||||
#define MAX_WITHOUT_SINGLE (64)
|
||||
@ -149,9 +153,6 @@ void POCSAGProcessor::configure() {
|
||||
|
||||
#define M_IDLE (0x7a89c197)
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
inline int bitsDiff(unsigned long left, unsigned long right) {
|
||||
unsigned long xord = left ^ right;
|
||||
int count = 0;
|
||||
@ -162,9 +163,6 @@ inline int bitsDiff(unsigned long left, unsigned long right) {
|
||||
return (count);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::initFrameExtraction() {
|
||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||
m_lastStableSymbolLen_1024 = m_minSymSamples_1024;
|
||||
@ -177,12 +175,10 @@ void POCSAGProcessor::initFrameExtraction() {
|
||||
resetVals();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::resetVals() {
|
||||
if (has_been_reset) return;
|
||||
|
||||
// Reset the parameters
|
||||
// --------------------
|
||||
m_goodTransitions = 0;
|
||||
m_badTransitions = 0;
|
||||
m_averageSymbolLen_1024 = m_maxSymSamples_1024;
|
||||
@ -190,7 +186,6 @@ void POCSAGProcessor::resetVals() {
|
||||
m_valMid = 0;
|
||||
|
||||
// And reset the counts
|
||||
// --------------------
|
||||
m_lastTransPos_1024 = 0;
|
||||
m_lastBitPos_1024 = 0;
|
||||
m_lastSample = 0;
|
||||
@ -200,13 +195,14 @@ void POCSAGProcessor::resetVals() {
|
||||
|
||||
// Extraction
|
||||
m_fifo.numBits = 0;
|
||||
m_fifo.codeword = 0;
|
||||
m_gotSync = false;
|
||||
m_numCode = 0;
|
||||
|
||||
has_been_reset = true;
|
||||
samples_processed = 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud, long a_minBaud, long maxRunOfSameValue) {
|
||||
m_samplesPerSec = a_samplesPerSec;
|
||||
m_minSymSamples_1024 = (uint32_t)(1024.0f * (float)a_samplesPerSec / (float)a_maxBaud);
|
||||
@ -223,14 +219,13 @@ void POCSAGProcessor::setFrameExtractParams(long a_samplesPerSec, long a_maxBaud
|
||||
initFrameExtraction();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSamples) {
|
||||
bool transition = false;
|
||||
uint32_t samplePos_1024 = 0;
|
||||
uint32_t len_1024 = 0;
|
||||
|
||||
has_been_reset = false;
|
||||
|
||||
// Loop through the block of data
|
||||
// ------------------------------
|
||||
for (int pos = 0; pos < noOfSamples; ++pos) {
|
||||
@ -399,9 +394,6 @@ int POCSAGProcessor::processDemodulatedSamples(float* sampleBuff, int noOfSample
|
||||
return getNoOfBits();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
void POCSAGProcessor::storeBit() {
|
||||
if (++m_bitsStart >= BIT_BUF_SIZE) {
|
||||
m_bitsStart = 0;
|
||||
@ -429,9 +421,6 @@ void POCSAGProcessor::storeBit() {
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::extractFrames() {
|
||||
int msgCnt = 0;
|
||||
// While there is unread data in the bits buffer
|
||||
@ -471,7 +460,7 @@ int POCSAGProcessor::extractFrames() {
|
||||
// If at the end of a 16 word block
|
||||
// --------------------------------
|
||||
if (m_numCode >= 15) {
|
||||
msgCnt += OnDataFrame(m_numCode + 1, (m_samplesPerSec << 10) / m_lastStableSymbolLen_1024);
|
||||
msgCnt += OnDataFrame(m_numCode + 1, getRate());
|
||||
m_gotSync = false;
|
||||
m_numCode = -1;
|
||||
}
|
||||
@ -482,9 +471,6 @@ int POCSAGProcessor::extractFrames() {
|
||||
return msgCnt;
|
||||
} // extractFrames
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
short POCSAGProcessor::getBit() {
|
||||
if (m_bitsEnd != m_bitsStart) {
|
||||
if (++m_bitsEnd >= BIT_BUF_SIZE) {
|
||||
@ -496,9 +482,6 @@ short POCSAGProcessor::getBit() {
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int POCSAGProcessor::getNoOfBits() {
|
||||
int bits = m_bitsEnd - m_bitsStart;
|
||||
if (bits < 0) {
|
||||
@ -507,16 +490,10 @@ int POCSAGProcessor::getNoOfBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
uint32_t POCSAGProcessor::getRate() {
|
||||
return ((m_samplesPerSec << 10) + 512) / m_lastStableSymbolLen_1024;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
//
|
||||
// ====================================================================
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<POCSAGProcessor>()};
|
||||
event_dispatcher.run();
|
||||
|
@ -26,107 +26,87 @@
|
||||
#ifndef __PROC_POCSAG2_H__
|
||||
#define __PROC_POCSAG2_H__
|
||||
|
||||
#include "audio_output.hpp"
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include "dsp_decimate.hpp"
|
||||
#include "dsp_demodulate.hpp"
|
||||
|
||||
#include "pocsag_packet.hpp"
|
||||
|
||||
#include "pocsag.hpp"
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "message.hpp"
|
||||
#include "audio_output.hpp"
|
||||
#include "pocsag.hpp"
|
||||
#include "pocsag_packet.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <bitset>
|
||||
using namespace std;
|
||||
|
||||
// Class used to smooth demodulated waveform prior to decoding
|
||||
// -----------------------------------------------------------
|
||||
template <class ValType, class CalcType>
|
||||
class SmoothVals {
|
||||
protected:
|
||||
ValType* m_lastVals; // Previous N values
|
||||
int m_size; // The size N
|
||||
CalcType m_sumVal; // Running sum of lastVals
|
||||
int m_pos; // Current position in last vals ring buffer
|
||||
int m_count; //
|
||||
|
||||
/* Takes audio stream and automatically normalizes it to +/-1.0f */
|
||||
class AudioNormalizer {
|
||||
public:
|
||||
SmoothVals()
|
||||
: m_lastVals(NULL), m_size(1), m_sumVal(0), m_pos(0), m_count(0) {
|
||||
m_lastVals = new ValType[m_size];
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// --------------------------------------------------
|
||||
virtual ~SmoothVals() {
|
||||
delete[] m_lastVals;
|
||||
}
|
||||
|
||||
SmoothVals(const SmoothVals<float, float>&) {
|
||||
}
|
||||
|
||||
SmoothVals& operator=(const SmoothVals<float, float>&) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Set size of smoothing
|
||||
// --------------------------------------------------
|
||||
void SetSize(int size) {
|
||||
m_size = std::max(size, 1);
|
||||
m_pos = 0;
|
||||
delete[] m_lastVals;
|
||||
m_lastVals = new ValType[m_size];
|
||||
m_sumVal = 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// Get size of smoothing
|
||||
// --------------------------------------------------
|
||||
int Size() { return m_size; }
|
||||
|
||||
// --------------------------------------------------
|
||||
// In place processing
|
||||
// --------------------------------------------------
|
||||
void Process(ValType* valBuff, int numVals) {
|
||||
ValType tmpVal;
|
||||
|
||||
if (m_count > (1024 * 10)) {
|
||||
// Recalculate the sum value occasionaly, stops accumulated errors when using float
|
||||
m_count = 0;
|
||||
m_sumVal = 0;
|
||||
for (int i = 0; i < m_size; ++i) {
|
||||
m_sumVal += (CalcType)m_lastVals[i];
|
||||
}
|
||||
void execute_in_place(const buffer_f32_t& audio) {
|
||||
// Decay min/max every second (@24kHz).
|
||||
if (counter_ >= 24'000) {
|
||||
// 90% decay factor seems to work well.
|
||||
// This keeps large transients from wrecking the filter.
|
||||
max_ *= 0.9f;
|
||||
min_ *= 0.9f;
|
||||
counter_ = 0;
|
||||
calculate_thresholds();
|
||||
}
|
||||
|
||||
// Use a rolling smoothed value while processing the buffer
|
||||
for (int buffPos = 0; buffPos < numVals; ++buffPos) {
|
||||
m_pos++;
|
||||
if (m_pos >= m_size) {
|
||||
m_pos = 0;
|
||||
counter_ += audio.count;
|
||||
|
||||
for (size_t i = 0; i < audio.count; ++i) {
|
||||
auto& val = audio.p[i];
|
||||
|
||||
if (val > max_) {
|
||||
max_ = val;
|
||||
calculate_thresholds();
|
||||
}
|
||||
if (val < min_) {
|
||||
min_ = val;
|
||||
calculate_thresholds();
|
||||
}
|
||||
|
||||
m_sumVal -= (CalcType)m_lastVals[m_pos]; // Subtract the oldest value
|
||||
m_lastVals[m_pos] = valBuff[buffPos]; // Store the new value
|
||||
m_sumVal += (CalcType)m_lastVals[m_pos]; // Add on the new value
|
||||
|
||||
tmpVal = (ValType)(m_sumVal / m_size); // Scale by number of values smoothed
|
||||
valBuff[buffPos] = tmpVal;
|
||||
if (val >= t_hi_)
|
||||
val = 1.0f;
|
||||
else if (val <= t_lo_)
|
||||
val = -1.0f;
|
||||
else
|
||||
val = 0.0;
|
||||
}
|
||||
|
||||
m_count += numVals;
|
||||
}
|
||||
|
||||
private:
|
||||
void calculate_thresholds() {
|
||||
auto center = (max_ + min_) / 2.0f;
|
||||
auto range = (max_ - min_) / 2.0f;
|
||||
|
||||
// 10% off center force either +/-1.0f.
|
||||
// Higher == larger dead zone.
|
||||
// Lower == more false positives.
|
||||
auto threshold = range * 0.1;
|
||||
t_hi_ = center + threshold;
|
||||
t_lo_ = center - threshold;
|
||||
}
|
||||
|
||||
uint32_t counter_ = 0;
|
||||
float min_ = 99.0f;
|
||||
float max_ = -99.0f;
|
||||
float t_hi_ = 1.0;
|
||||
float t_lo_ = 1.0;
|
||||
};
|
||||
|
||||
// --------------------------------------------------
|
||||
// Class to process base band data to pocsag frames
|
||||
// --------------------------------------------------
|
||||
// How to detect clock signal across baud rates?
|
||||
// Maybe have a bit extraction state machine that reset
|
||||
// then watches for the clocks, but there are multiple
|
||||
// clock and the last one is the right one.
|
||||
// So keep updating clock until a sync?
|
||||
|
||||
class BitExtractor {};
|
||||
|
||||
class WordExtractor {};
|
||||
|
||||
class POCSAGProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
@ -137,28 +117,54 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
|
||||
private:
|
||||
static constexpr size_t baseband_fs = 3072000;
|
||||
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{
|
||||
dst.data(),
|
||||
dst.size()};
|
||||
std::array<float, 32> audio{};
|
||||
const buffer_f32_t audio_buffer{
|
||||
audio.data(),
|
||||
audio.size()};
|
||||
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
||||
dsp::demodulate::FM demod{};
|
||||
SmoothVals<float, float> smooth = {};
|
||||
|
||||
AudioOutput audio_output{};
|
||||
|
||||
bool configured = false;
|
||||
pocsag::POCSAGPacket packet{};
|
||||
static constexpr uint8_t stat_update_interval = 10;
|
||||
static constexpr uint32_t stat_update_threshold =
|
||||
baseband_fs / stat_update_interval;
|
||||
|
||||
void configure();
|
||||
void send_stats() const;
|
||||
|
||||
// Set once app is ready to receive messages.
|
||||
bool configured = false;
|
||||
|
||||
// Buffer for decimated IQ data.
|
||||
std::array<complex16_t, 512> dst{};
|
||||
const buffer_c16_t dst_buffer{dst.data(), dst.size()};
|
||||
|
||||
// Buffer for demodulated audio.
|
||||
std::array<float, 32> audio{};
|
||||
const buffer_f32_t audio_buffer{audio.data(), audio.size()};
|
||||
|
||||
// Decimate to 48kHz.
|
||||
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
|
||||
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
|
||||
|
||||
// Filter to 24kHz and demodulate.
|
||||
dsp::decimate::FIRAndDecimateComplex channel_filter{};
|
||||
dsp::demodulate::FM demod{};
|
||||
|
||||
// LPF to reduce noise.
|
||||
// scipy.signal.butter(2, 1800, "lowpass", fs=24000, analog=False)
|
||||
IIRBiquadFilter lpf{{{0.04125354f, 0.082507070f, 0.04125354f},
|
||||
{1.00000000f, -1.34896775f, 0.51398189f}}};
|
||||
|
||||
// Squelch to ignore noise.
|
||||
FMSquelch squelch{};
|
||||
uint64_t squelch_history = 0;
|
||||
|
||||
// Attempts to de-noise signal and normalize to +/- 1.0f.
|
||||
AudioNormalizer normalizer{};
|
||||
|
||||
// Handles writing audio stream to hardware.
|
||||
AudioOutput audio_output{};
|
||||
|
||||
// Holds the data sent to the app.
|
||||
pocsag::POCSAGPacket packet{};
|
||||
|
||||
bool has_been_reset = true;
|
||||
uint32_t samples_processed = 0;
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
// ----------------------------------------
|
||||
// Frame extractraction methods and members
|
||||
@ -169,8 +175,6 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
int numBits;
|
||||
};
|
||||
|
||||
#define BIT_BUF_SIZE (64)
|
||||
|
||||
void resetVals();
|
||||
void setFrameExtractParams(long a_samplesPerSec, long a_maxBaud = 8000, long a_minBaud = 200, long maxRunOfSameValue = 32);
|
||||
|
||||
@ -207,7 +211,8 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
uint32_t m_maxSymSamples_1024{0};
|
||||
uint32_t m_maxRunOfSameValue{0};
|
||||
|
||||
bitset<(size_t)BIT_BUF_SIZE> m_bits{0};
|
||||
static constexpr long BIT_BUF_SIZE = 64;
|
||||
std::bitset<64> m_bits{0};
|
||||
long m_bitsStart{0};
|
||||
long m_bitsEnd{0};
|
||||
|
||||
@ -216,8 +221,7 @@ class POCSAGProcessor : public BasebandProcessor {
|
||||
int m_numCode{0};
|
||||
bool m_inverted{false};
|
||||
|
||||
FMSquelch squelch_{};
|
||||
uint64_t squelch_history = 0;
|
||||
//--------------------------------------------------
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
|
||||
|
@ -77,12 +77,6 @@ constexpr iir_biquad_config_t audio_24k_deemph_300_6_config{
|
||||
{0.03780475f, 0.03780475f, 0.00000000f},
|
||||
{1.00000000f, -0.92439049f, 0.00000000f}};
|
||||
|
||||
// scipy.signal.butter(1, 2400 / 12000.0, 'lowpass', analog=False)
|
||||
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
|
||||
constexpr iir_biquad_config_t audio_24k_lpf_2400hz_config{
|
||||
{0.03780475f, 0.03780475f, 0.00000000f},
|
||||
{1.00000000f, -0.92439049f, 0.00000000f}};
|
||||
|
||||
// scipy.signal.butter(1, 300 / 8000.0, 'lowpass', analog=False)
|
||||
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
|
||||
constexpr iir_biquad_config_t audio_16k_deemph_300_6_config{
|
||||
|
@ -344,12 +344,18 @@ class POCSAGPacketMessage : public Message {
|
||||
class POCSAGStatsMessage : public Message {
|
||||
public:
|
||||
constexpr POCSAGStatsMessage(
|
||||
uint16_t baud_rate)
|
||||
uint32_t current_bits,
|
||||
uint8_t current_frames,
|
||||
bool has_sync)
|
||||
: Message{ID::POCSAGStats},
|
||||
baud_rate_{baud_rate} {
|
||||
current_bits{current_bits},
|
||||
current_frames{current_frames},
|
||||
has_sync{has_sync} {
|
||||
}
|
||||
|
||||
uint16_t baud_rate_;
|
||||
uint32_t current_bits = 0;
|
||||
uint8_t current_frames = 0;
|
||||
bool has_sync = false;
|
||||
};
|
||||
|
||||
class ACARSPacketMessage : public Message {
|
||||
|
Loading…
Reference in New Issue
Block a user