1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 09:18:54 -05:00

M17 demod: removed FloatType template parameter

This commit is contained in:
f4exb 2022-06-09 20:12:35 +02:00
parent f326860f64
commit 424d072f0c
20 changed files with 652 additions and 731 deletions

View File

@ -13,22 +13,21 @@
namespace mobilinkd namespace mobilinkd
{ {
template <typename FloatType>
struct CarrierDetect struct CarrierDetect
{ {
using result_t = std::tuple<bool, FloatType>; using result_t = std::tuple<bool, float>;
BaseIirFilter<FloatType, 3> filter_; BaseIirFilter<3> filter_;
FloatType lock_; float lock_;
FloatType unlock_; float unlock_;
bool locked_ = false; bool locked_ = false;
CarrierDetect(std::array<FloatType, 3> const& b, std::array<FloatType, 3> const& a, FloatType lock_level, FloatType unlock_level) CarrierDetect(std::array<float, 3> const& b, std::array<float, 3> const& a, float lock_level, float unlock_level)
: filter_(b, a), lock_(lock_level), unlock_(unlock_level) : filter_(b, a), lock_(lock_level), unlock_(unlock_level)
{ {
} }
result_t operator()(FloatType value) result_t operator()(float value)
{ {
auto filtered = filter_(std::abs(value)); auto filtered = filter_(std::abs(value));
if (locked_ && (filtered > unlock_)) locked_ = false; if (locked_ && (filtered > unlock_)) locked_ = false;

View File

@ -13,16 +13,16 @@ namespace mobilinkd
/** /**
* Calculate the phase estimates for each sample position. * Calculate the phase estimates for each sample position.
* *
* This performs a running calculation of the phase of each bit position. * This performs a running calculation of the phase of each bit position.
* It is very noisy for individual samples, but quite accurate when * It is very noisy for individual samples, but quite accurate when
* averaged over an entire M17 frame. * averaged over an entire M17 frame.
* *
* It is designed to be used to calculate the best bit position for each * It is designed to be used to calculate the best bit position for each
* frame of data. Samples are collected and averaged. When update() is * frame of data. Samples are collected and averaged. When update() is
* called, the best sample index and clock are estimated, and the counters * called, the best sample index and clock are estimated, and the counters
* reset for the next frame. * reset for the next frame.
* *
* It starts counting bit 0 as the first bit received after a reset. * It starts counting bit 0 as the first bit received after a reset.
* *
* This is very efficient as it only uses addition and subtraction for * This is very efficient as it only uses addition and subtraction for
@ -37,24 +37,24 @@ namespace mobilinkd
* @inv sample_index_ is in the interval [0, SAMPLES_PER_SYMBOL). * @inv sample_index_ is in the interval [0, SAMPLES_PER_SYMBOL).
* @inv clock_ is in the interval [0.9995, 1.0005] * @inv clock_ is in the interval [0.9995, 1.0005]
*/ */
template <typename FloatType, size_t SampleRate, size_t SymbolRate> template <size_t SampleRate, size_t SymbolRate>
class ClockRecovery class ClockRecovery
{ {
static constexpr size_t SAMPLES_PER_SYMBOL = SampleRate / SymbolRate; static constexpr size_t SAMPLES_PER_SYMBOL = SampleRate / SymbolRate;
static constexpr int8_t MAX_OFFSET = SAMPLES_PER_SYMBOL / 2; static constexpr int8_t MAX_OFFSET = SAMPLES_PER_SYMBOL / 2;
static constexpr FloatType dx = 1.0 / SAMPLES_PER_SYMBOL; static constexpr float dx = 1.0 / SAMPLES_PER_SYMBOL;
static constexpr FloatType MAX_CLOCK = 1.0005; static constexpr float MAX_CLOCK = 1.0005;
static constexpr FloatType MIN_CLOCK = 0.9995; static constexpr float MIN_CLOCK = 0.9995;
std::array<FloatType, SAMPLES_PER_SYMBOL> estimates_; std::array<float, SAMPLES_PER_SYMBOL> estimates_;
size_t sample_count_ = 0; size_t sample_count_ = 0;
uint16_t frame_count_ = 0; uint16_t frame_count_ = 0;
uint8_t sample_index_ = 0; uint8_t sample_index_ = 0;
uint8_t prev_sample_index_ = 0; uint8_t prev_sample_index_ = 0;
uint8_t index_ = 0; uint8_t index_ = 0;
FloatType offset_ = 0.0; float offset_ = 0.0;
FloatType clock_ = 1.0; float clock_ = 1.0;
FloatType prev_sample_ = 0.0; float prev_sample_ = 0.0;
/** /**
* Find the sample index. * Find the sample index.
@ -78,7 +78,7 @@ class ClockRecovery
bool is_positive = false; bool is_positive = false;
for (size_t i = 0; i != SAMPLES_PER_SYMBOL; ++i) for (size_t i = 0; i != SAMPLES_PER_SYMBOL; ++i)
{ {
FloatType phase = estimates_[i]; float phase = estimates_[i];
if (!is_positive && phase > 0) if (!is_positive && phase > 0)
{ {
@ -90,7 +90,7 @@ class ClockRecovery
break; break;
} }
} }
sample_index_ = index == 0 ? SAMPLES_PER_SYMBOL - 1 : index - 1; sample_index_ = index == 0 ? SAMPLES_PER_SYMBOL - 1 : index - 1;
} }
@ -99,7 +99,7 @@ class ClockRecovery
* *
* This should never be greater than one. * This should never be greater than one.
*/ */
FloatType calc_offset_() float calc_offset_()
{ {
int8_t offset = sample_index_ - prev_sample_index_; int8_t offset = sample_index_ - prev_sample_index_;
@ -139,14 +139,14 @@ public:
{ {
estimates_.fill(0); estimates_.fill(0);
} }
/** /**
* Update clock recovery with the given sample. This will advance the * Update clock recovery with the given sample. This will advance the
* current sample index by 1. * current sample index by 1.
*/ */
void operator()(FloatType sample) void operator()(float sample)
{ {
FloatType dy = (sample - prev_sample_); float dy = (sample - prev_sample_);
if (sample + prev_sample_ < 0) if (sample + prev_sample_ < 0)
{ {
@ -155,7 +155,7 @@ public:
} }
prev_sample_ = sample; prev_sample_ = sample;
estimates_[index_] += dy; estimates_[index_] += dy;
index_ += 1; index_ += 1;
if (index_ == SAMPLES_PER_SYMBOL) if (index_ == SAMPLES_PER_SYMBOL)
@ -189,18 +189,18 @@ public:
/** /**
* Return the estimated sample clock increment based on the last update. * Return the estimated sample clock increment based on the last update.
* *
* The value is only valid after samples have been collected and update() * The value is only valid after samples have been collected and update()
* has been called. * has been called.
*/ */
FloatType clock_estimate() const float clock_estimate() const
{ {
return clock_; return clock_;
} }
/** /**
* Return the estimated "best sample index" based on the last update. * Return the estimated "best sample index" based on the last update.
* *
* The value is only valid after samples have been collected and update() * The value is only valid after samples have been collected and update()
* has been called. * has been called.
*/ */
@ -208,20 +208,20 @@ public:
{ {
return sample_index_; return sample_index_;
} }
/** /**
* Update the sample index and clock estimates, and reset the state for * Update the sample index and clock estimates, and reset the state for
* the next frame of data. * the next frame of data.
* *
* @pre index_ = 0 * @pre index_ = 0
* @pre sample_count_ > 0 * @pre sample_count_ > 0
* *
* After this is called, sample_index() and clock_estimate() will have * After this is called, sample_index() and clock_estimate() will have
* valid, updated results. * valid, updated results.
* *
* The more samples between calls to update, the more accurate the * The more samples between calls to update, the more accurate the
* estimates will be. * estimates will be.
* *
* @return true if the preconditions are met and the update has been * @return true if the preconditions are met and the update has been
* performed, otherwise false. * performed, otherwise false.
*/ */
@ -231,7 +231,7 @@ public:
update_sample_index_(); update_sample_index_();
update_clock_(); update_clock_();
frame_count_ = std::min(0x1000, 1 + frame_count_); frame_count_ = std::min(0x1000, 1 + frame_count_);
sample_count_ = 0; sample_count_ = 0;
estimates_.fill(0); estimates_.fill(0);

View File

@ -3,16 +3,7 @@
namespace mobilinkd { namespace mobilinkd {
// IIR with Nyquist of 1/240. // IIR with Nyquist of 1/240.
template<> const std::array<float,3> Correlator::b = {4.24433681e-05, 8.48867363e-05, 4.24433681e-05};
const std::array<double,3> Correlator<double>::b = {4.24433681e-05, 8.48867363e-05, 4.24433681e-05}; const std::array<float,3> Correlator::a = {1.0, -1.98148851, 0.98165828};
template<>
const std::array<double,3> Correlator<double>::a = {1.0, -1.98148851, 0.98165828};
template<>
const std::array<float,3> Correlator<float>::b = {4.24433681e-05, 8.48867363e-05, 4.24433681e-05};
template<>
const std::array<float,3> Correlator<float>::a = {1.0, -1.98148851, 0.98165828};
} // namespace mobilinkd } // namespace mobilinkd

View File

@ -15,32 +15,31 @@
namespace mobilinkd { namespace mobilinkd {
template <typename FloatType>
struct Correlator struct Correlator
{ {
static constexpr size_t SYMBOLS = 8; static constexpr size_t SYMBOLS = 8;
static constexpr size_t SAMPLES_PER_SYMBOL = 10; static constexpr size_t SAMPLES_PER_SYMBOL = 10;
using value_type = FloatType; using value_type = float;
using buffer_t = std::array<FloatType, SYMBOLS * SAMPLES_PER_SYMBOL>; using buffer_t = std::array<float, SYMBOLS * SAMPLES_PER_SYMBOL>;
using sync_t = std::array<int8_t, SYMBOLS>; using sync_t = std::array<int8_t, SYMBOLS>;
using sample_filter_t = BaseIirFilter<FloatType, 3>; using sample_filter_t = BaseIirFilter<3>;
buffer_t buffer_; buffer_t buffer_;
FloatType limit_ = 0.; float limit_ = 0.;
size_t symbol_pos_ = 0; size_t symbol_pos_ = 0;
size_t buffer_pos_ = 0; size_t buffer_pos_ = 0;
size_t prev_buffer_pos_ = 0; size_t prev_buffer_pos_ = 0;
int code = -1; int code = -1;
// IIR with Nyquist of 1/240. // IIR with Nyquist of 1/240.
static const std::array<FloatType,3> b; static const std::array<float,3> b;
static const std::array<FloatType,3> a; static const std::array<float,3> a;
sample_filter_t sample_filter{b, a}; sample_filter_t sample_filter{b, a};
std::array<int, SYMBOLS> tmp; std::array<int, SYMBOLS> tmp;
void sample(FloatType value) void sample(float value)
{ {
limit_ = sample_filter(std::abs(value)); limit_ = sample_filter(std::abs(value));
buffer_[buffer_pos_] = value; buffer_[buffer_pos_] = value;
@ -48,9 +47,9 @@ struct Correlator
if (++buffer_pos_ == buffer_.size()) buffer_pos_ = 0; if (++buffer_pos_ == buffer_.size()) buffer_pos_ = 0;
} }
FloatType correlate(sync_t sync) float correlate(sync_t sync)
{ {
FloatType result = 0.; float result = 0.;
size_t pos = prev_buffer_pos_ + SAMPLES_PER_SYMBOL; size_t pos = prev_buffer_pos_ + SAMPLES_PER_SYMBOL;
for (size_t i = 0; i != sync.size(); ++i) for (size_t i = 0; i != sync.size(); ++i)
@ -63,7 +62,7 @@ struct Correlator
return result; return result;
} }
FloatType limit() const {return limit_;} float limit() const {return limit_;}
size_t index() const {return prev_buffer_pos_ % SAMPLES_PER_SYMBOL;} size_t index() const {return prev_buffer_pos_ % SAMPLES_PER_SYMBOL;}
/** /**
@ -78,10 +77,10 @@ struct Correlator
* second holds true for the sync words used for M17. The third will * second holds true for the sync words used for M17. The third will
* hold true if passed the timing index from a triggered sync word. * hold true if passed the timing index from a triggered sync word.
*/ */
std::tuple<FloatType, FloatType> outer_symbol_levels(size_t sample_index) std::tuple<float, float> outer_symbol_levels(size_t sample_index)
{ {
FloatType min_sum = 0; float min_sum = 0;
FloatType max_sum = 0; float max_sum = 0;
size_t min_count = 0; size_t min_count = 0;
size_t max_count = 0; size_t max_count = 0;
size_t index = 0; size_t index = 0;

View File

@ -25,23 +25,23 @@ namespace mobilinkd {
* *
* Note: the input to this DCD must be unfiltered (raw) baseband input. * Note: the input to this DCD must be unfiltered (raw) baseband input.
*/ */
template <typename FloatType, size_t SampleRate, size_t Accuracy = 1000> template <size_t SampleRate, size_t Accuracy = 1000>
struct DataCarrierDetect struct DataCarrierDetect
{ {
using ComplexType = std::complex<FloatType>; using ComplexType = std::complex<float>;
using NDFT = NSlidingDFT<FloatType, SampleRate, SampleRate / Accuracy, 2>; using NDFT = NSlidingDFT<SampleRate, SampleRate / Accuracy, 2>;
NDFT dft_; NDFT dft_;
FloatType ltrigger_; float ltrigger_;
FloatType htrigger_; float htrigger_;
FloatType level_1 = 0.0; float level_1 = 0.0;
FloatType level_2 = 0.0; float level_2 = 0.0;
FloatType level_ = 0.0; float level_ = 0.0;
bool triggered_ = false; bool triggered_ = false;
DataCarrierDetect( DataCarrierDetect(
size_t freq1, size_t freq2, size_t freq1, size_t freq2,
FloatType ltrigger = 2.0, FloatType htrigger = 5.0) float ltrigger = 2.0, float htrigger = 5.0)
: dft_({freq1, freq2}), ltrigger_(ltrigger), htrigger_(htrigger) : dft_({freq1, freq2}), ltrigger_(ltrigger), htrigger_(htrigger)
{ {
} }
@ -50,7 +50,7 @@ struct DataCarrierDetect
* Accept unfiltered baseband input and output a decision on whether * Accept unfiltered baseband input and output a decision on whether
* a carrier has been detected after every @tparam BlockSize inputs. * a carrier has been detected after every @tparam BlockSize inputs.
*/ */
void operator()(FloatType sample) void operator()(float sample)
{ {
auto result = dft_(sample); auto result = dft_(sample);
level_1 += std::norm(result[0]); level_1 += std::norm(result[0]);
@ -69,7 +69,7 @@ struct DataCarrierDetect
} }
FloatType level() const { return level_; } float level() const { return level_; }
bool dcd() const { return triggered_; } bool dcd() const { return triggered_; }
}; };

View File

@ -9,11 +9,10 @@
namespace mobilinkd namespace mobilinkd
{ {
template <typename T, size_t N = 10> template <size_t N = 10>
struct DeviationError struct DeviationError
{ {
using float_type = T; using array_t = std::array<float, N>;
using array_t = std::array<float_type, N>;
array_t minima_{0}; array_t minima_{0};
array_t maxima_{0}; array_t maxima_{0};
@ -23,18 +22,18 @@ struct DeviationError
bool max_rolled_ = false; bool max_rolled_ = false;
size_t min_count_ = 0; size_t min_count_ = 0;
size_t max_count_ = 0; size_t max_count_ = 0;
float_type min_estimate_ = 0.0; float min_estimate_ = 0.0;
float_type max_estimate_ = 0.0; float max_estimate_ = 0.0;
const float_type ZERO = 0.0; const float ZERO = 0.0;
DeviationError() DeviationError()
{ {
minima_.fill(0.0); minima_.fill(0.0);
maxima_.fill(0.0); maxima_.fill(0.0);
} }
float_type operator()(float_type sample) float operator()(float sample)
{ {
if (sample > ZERO) if (sample > ZERO)
{ {

View File

@ -10,27 +10,27 @@
namespace mobilinkd namespace mobilinkd
{ {
template <typename FloatType, size_t N> template <size_t N>
struct BaseFirFilter : FilterBase<FloatType> struct BaseFirFilter : FilterBase<float>
{ {
using array_t = std::array<FloatType, N>; using array_t = std::array<float, N>;
const array_t& taps_; const array_t& taps_;
array_t history_; array_t history_;
size_t pos_ = 0; size_t pos_ = 0;
BaseFirFilter(const array_t& taps) BaseFirFilter(const array_t& taps)
: taps_(taps) : taps_(taps)
{ {
history_.fill(0.0); history_.fill(0.0);
} }
FloatType operator()(FloatType input) override float operator()(float input) override
{ {
history_[pos_++] = input; history_[pos_++] = input;
if (pos_ == N) pos_ = 0; if (pos_ == N) pos_ = 0;
FloatType result = 0.0; float result = 0.0;
size_t index = pos_; size_t index = pos_;
for (size_t i = 0; i != N; ++i) for (size_t i = 0; i != N; ++i)
@ -49,10 +49,10 @@ struct BaseFirFilter : FilterBase<FloatType>
} }
}; };
template <typename FloatType, size_t N> template <size_t N>
BaseFirFilter<FloatType, N> makeFirFilter(const std::array<FloatType, N>& taps) BaseFirFilter<N> makeFirFilter(const std::array<float, N>& taps)
{ {
return std::move(BaseFirFilter<FloatType, N>(taps)); return std::move(BaseFirFilter<N>(taps));
} }

View File

@ -2,16 +2,7 @@
namespace mobilinkd { namespace mobilinkd {
template<> const std::array<float, 3> FreqDevEstimator::dc_b = { 0.09763107, 0.19526215, 0.09763107 };
const std::array<double, 3> FreqDevEstimator<double>::dc_b = { 0.09763107, 0.19526215, 0.09763107 }; const std::array<float, 3> FreqDevEstimator::dc_a = { 1. , -0.94280904, 0.33333333 };
template<>
const std::array<double, 3> FreqDevEstimator<double>::dc_a = { 1. , -0.94280904, 0.33333333 };
template<>
const std::array<float, 3> FreqDevEstimator<float>::dc_b = { 0.09763107, 0.19526215, 0.09763107 };
template<>
const std::array<float, 3> FreqDevEstimator<float>::dc_a = { 1. , -0.94280904, 0.33333333 };
} // namespace mobilinkd } // namespace mobilinkd

View File

@ -24,29 +24,28 @@ namespace mobilinkd {
* Estimates are expected to be updated at each sync word. But they can * Estimates are expected to be updated at each sync word. But they can
* be updated more frequently, such as during the preamble. * be updated more frequently, such as during the preamble.
*/ */
template <typename FloatType>
class FreqDevEstimator class FreqDevEstimator
{ {
using sample_filter_t = BaseIirFilter<FloatType, 3>; using sample_filter_t = BaseIirFilter<3>;
// IIR with Nyquist of 1/4. // IIR with Nyquist of 1/4.
static const std::array<FloatType, 3> dc_b; static const std::array<float, 3> dc_b;
static const std::array<FloatType, 3> dc_a; static const std::array<float, 3> dc_a;
static constexpr FloatType MAX_DC_ERROR = 0.2; static constexpr float MAX_DC_ERROR = 0.2;
FloatType min_est_ = 0.0; float min_est_ = 0.0;
FloatType max_est_ = 0.0; float max_est_ = 0.0;
FloatType min_cutoff_ = 0.0; float min_cutoff_ = 0.0;
FloatType max_cutoff_ = 0.0; float max_cutoff_ = 0.0;
FloatType min_var_ = 0.0; float min_var_ = 0.0;
FloatType max_var_ = 0.0; float max_var_ = 0.0;
size_t min_count_ = 0; size_t min_count_ = 0;
size_t max_count_ = 0; size_t max_count_ = 0;
FloatType deviation_ = 0.0; float deviation_ = 0.0;
FloatType offset_ = 0.0; float offset_ = 0.0;
FloatType error_ = 0.0; float error_ = 0.0;
FloatType idev_ = 1.0; float idev_ = 1.0;
sample_filter_t dc_filter_{dc_b, dc_a}; sample_filter_t dc_filter_{dc_b, dc_a};
public: public:
@ -63,7 +62,7 @@ public:
max_cutoff_ = 0.0; max_cutoff_ = 0.0;
} }
void sample(FloatType sample) void sample(float sample)
{ {
if (sample < 1.5 * min_est_) if (sample < 1.5 * min_est_)
{ {
@ -76,7 +75,7 @@ public:
{ {
min_count_ += 1; min_count_ += 1;
min_est_ += sample; min_est_ += sample;
FloatType var = (min_est_ / min_count_) - sample; float var = (min_est_ / min_count_) - sample;
min_var_ += var * var; min_var_ += var * var;
} }
else if (sample > 1.5 * max_est_) else if (sample > 1.5 * max_est_)
@ -90,7 +89,7 @@ public:
{ {
max_count_ += 1; max_count_ += 1;
max_est_ += sample; max_est_ += sample;
FloatType var = (max_est_ / max_count_) - sample; float var = (max_est_ / max_count_) - sample;
max_var_ += var * var; max_var_ += var * var;
} }
} }
@ -104,8 +103,8 @@ public:
void update() void update()
{ {
if (max_count_ < 2 || min_count_ < 2) return; if (max_count_ < 2 || min_count_ < 2) return;
FloatType max_ = max_est_ / max_count_; float max_ = max_est_ / max_count_;
FloatType min_ = min_est_ / min_count_; float min_ = min_est_ / min_count_;
deviation_ = (max_ - min_) / 6.0; deviation_ = (max_ - min_) / 6.0;
offset_ = dc_filter_(std::max(std::min(max_ + min_, deviation_ * MAX_DC_ERROR), deviation_ * -MAX_DC_ERROR)); offset_ = dc_filter_(std::max(std::min(max_ + min_, deviation_ * MAX_DC_ERROR), deviation_ * -MAX_DC_ERROR));
error_ = (std::sqrt(max_var_ / (max_count_ - 1)) + std::sqrt(min_var_ / (min_count_ - 1))) * 0.5; error_ = (std::sqrt(max_var_ / (max_count_ - 1)) + std::sqrt(min_var_ / (min_count_ - 1))) * 0.5;
@ -120,10 +119,10 @@ public:
min_var_ = 0.0; min_var_ = 0.0;
} }
FloatType deviation() const { return deviation_; } float deviation() const { return deviation_; }
FloatType offset() const { return offset_; } float offset() const { return offset_; }
FloatType error() const { return error_; } float error() const { return error_; }
FloatType idev() const { return idev_; } float idev() const { return idev_; }
}; };
} // mobilinkd } // mobilinkd

View File

@ -11,15 +11,15 @@
namespace mobilinkd namespace mobilinkd
{ {
template <typename FloatType, size_t N = 32> template <size_t N = 32>
struct FrequencyError struct FrequencyError
{ {
using float_type = FloatType; using float_type = float;
using array_t = std::array<FloatType, N>; using array_t = std::array<float, N>;
using filter_type = BaseIirFilter<FloatType, 3>; using filter_type = BaseIirFilter<3>;
static constexpr std::array<FloatType, 3> evm_b{0.02008337, 0.04016673, 0.02008337}; static constexpr std::array<float, 3> evm_b{0.02008337, 0.04016673, 0.02008337};
static constexpr std::array<FloatType, 3> evm_a{1.0, -1.56101808, 0.64135154}; static constexpr std::array<float, 3> evm_a{1.0, -1.56101808, 0.64135154};
array_t samples_{0}; array_t samples_{0};
size_t index_ = 0; size_t index_ = 0;
@ -33,10 +33,10 @@ struct FrequencyError
{ {
samples_.fill(0.0); samples_.fill(0.0);
} }
auto operator()(float_type sample) auto operator()(float_type sample)
{ {
FloatType evm = 0; float evm = 0;
bool use = true; bool use = true;
if (sample > 2) if (sample > 2)

View File

@ -16,7 +16,7 @@ namespace mobilinkd
namespace detail namespace detail
{ {
static const auto rrc_taps = std::array<double, 79>{ static const auto rrc_taps = std::array<float, 79>{
-0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491, -0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491,
0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299, 0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299,
0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372, 0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372,
@ -39,8 +39,8 @@ static const auto rrc_taps = std::array<double, 79>{
-0.001125978562075172, -0.006136551625729697, -0.009265784007800534 -0.001125978562075172, -0.006136551625729697, -0.009265784007800534
}; };
static const auto evm_b = std::array<double, 3>{0.02008337, 0.04016673, 0.02008337}; static const auto evm_b = std::array<float, 3>{0.02008337, 0.04016673, 0.02008337};
static const auto evm_a = std::array<double, 3>{1.0, -1.56101808, 0.64135154}; static const auto evm_a = std::array<float, 3>{1.0, -1.56101808, 0.64135154};
} // detail } // detail
struct Fsk4Demod struct Fsk4Demod
@ -48,17 +48,17 @@ struct Fsk4Demod
using demod_result_t = std::tuple<double, double, int, double>; using demod_result_t = std::tuple<double, double, int, double>;
using result_t = std::tuple<double, double, int, double, double, double, double>; using result_t = std::tuple<double, double, int, double, double, double, double>;
BaseFirFilter<double, std::tuple_size<decltype(detail::rrc_taps)>::value> rrc = makeFirFilter(detail::rrc_taps); BaseFirFilter<std::tuple_size<decltype(detail::rrc_taps)>::value> rrc = makeFirFilter(detail::rrc_taps);
PhaseEstimator<double> phase = PhaseEstimator<double>(48000, 4800); PhaseEstimator phase = PhaseEstimator(48000, 4800);
DeviationError<double> deviation; DeviationError<10> deviation;
FrequencyError<double, 32> frequency; FrequencyError<32> frequency;
SymbolEvm<double, std::tuple_size<decltype(detail::evm_b)>::value> symbol_evm = makeSymbolEvm(makeIirFilter(detail::evm_b, detail::evm_a)); SymbolEvm<std::tuple_size<decltype(detail::evm_b)>::value> symbol_evm = makeSymbolEvm(makeIirFilter(detail::evm_b, detail::evm_a));
double sample_rate = 48000; double sample_rate = 48000;
double symbol_rate = 4800; double symbol_rate = 4800;
double unlock_gain = 0.02; double unlock_gain = 0.02;
double lock_gain = 0.001; double lock_gain = 0.001;
std::array<double, 3> samples{0}; std::array<float, 3> samples{0};
double t = 0; double t = 0;
double dt = symbol_rate / sample_rate; double dt = symbol_rate / sample_rate;
double ideal_dt = dt; double ideal_dt = dt;

View File

@ -10,43 +10,43 @@
namespace mobilinkd namespace mobilinkd
{ {
template <typename FloatType, size_t N> template <size_t N>
struct BaseIirFilter : FilterBase<FloatType> struct BaseIirFilter : FilterBase<float>
{ {
const std::array<FloatType, N>& numerator_; const std::array<float, N>& numerator_;
const std::array<FloatType, N> denominator_; const std::array<float, N> denominator_;
std::array<FloatType, N> history_{0}; std::array<float, N> history_{0};
BaseIirFilter(const std::array<FloatType, N>& b, const std::array<FloatType, N>& a) BaseIirFilter(const std::array<float, N>& b, const std::array<float, N>& a)
: numerator_(b), denominator_(a) : numerator_(b), denominator_(a)
{ {
history_.fill(0.0); history_.fill(0.0);
} }
FloatType operator()(FloatType input) { float operator()(float input) {
for (size_t i = N - 1; i != 0; i--) history_[i] = history_[i - 1]; for (size_t i = N - 1; i != 0; i--) history_[i] = history_[i - 1];
history_[0] = input; history_[0] = input;
for (size_t i = 1; i != N; i++) { for (size_t i = 1; i != N; i++) {
history_[0] -= denominator_[i] * history_[i]; history_[0] -= denominator_[i] * history_[i];
} }
FloatType result = 0; float result = 0;
for (size_t i = 0; i != N; i++) { for (size_t i = 0; i != N; i++) {
result += numerator_[i] * history_[i]; result += numerator_[i] * history_[i];
} }
return result; return result;
} }
}; };
template <typename FloatType, size_t N> template <size_t N>
BaseIirFilter<FloatType, N> makeIirFilter( BaseIirFilter<N> makeIirFilter(
const std::array<FloatType, N>& b, const std::array<FloatType, N>& a) const std::array<float, N>& b, const std::array<float, N>& a)
{ {
return std::move(BaseIirFilter<FloatType, N>(b, a)); return std::move(BaseIirFilter<N>(b, a));
} }
} // mobilinkd } // mobilinkd

View File

@ -2,8 +2,7 @@
namespace mobilinkd { namespace mobilinkd {
template <> const std::array<float, 150> M17Demodulator::rrc_taps = std::array<float, 150>{
const std::array<double, 150> M17Demodulator<double>::rrc_taps = std::array<double, 150>{
0.0029364388513841593, 0.0031468394550958484, 0.002699564567597445, 0.001661182944400927, 0.0029364388513841593, 0.0031468394550958484, 0.002699564567597445, 0.001661182944400927,
0.00023319405581230247, -0.0012851320781224025, -0.0025577136087664687, -0.0032843366522956313, 0.00023319405581230247, -0.0012851320781224025, -0.0025577136087664687, -0.0032843366522956313,
-0.0032697038088887226, -0.0024733964729590865, -0.0010285696910973807, 0.0007766690889758685, -0.0032697038088887226, -0.0024733964729590865, -0.0010285696910973807, 0.0007766690889758685,
@ -44,46 +43,453 @@ const std::array<double, 150> M17Demodulator<double>::rrc_taps = std::array<doub
0.0029364388513841593, 0.0 0.0029364388513841593, 0.0
}; };
template <> void M17Demodulator::update_values(uint8_t index)
const std::array<float, 150> M17Demodulator<float>::rrc_taps = std::array<float, 150>{ {
0.0029364388513841593, 0.0031468394550958484, 0.002699564567597445, 0.001661182944400927, correlator.apply([this,index](float t){dev.sample(t);}, index);
0.00023319405581230247, -0.0012851320781224025, -0.0025577136087664687, -0.0032843366522956313, dev.update();
-0.0032697038088887226, -0.0024733964729590865, -0.0010285696910973807, 0.0007766690889758685, sync_sample_index = index;
0.002553421969211845, 0.0038920145144327816, 0.004451886520053017, 0.00404219185231544, }
0.002674727068399207, 0.0005756567993179152, -0.0018493784971116507, -0.004092346891623224,
-0.005648131453822014, -0.006126925416243605, -0.005349511529163396, -0.003403189203405097, void M17Demodulator::dcd_on()
-0.0006430502751187517, 0.002365929161655135, 0.004957956568090113, 0.006506845894531803, {
0.006569574194782443, 0.0050017573119839134, 0.002017321931508163, -0.0018256054303579805, // Data carrier newly detected.
-0.00571615173291049, -0.008746639552588416, -0.010105075751866371, -0.009265784007800534, dcd_ = true;
-0.006136551625729697, -0.001125978562075172, 0.004891777252042491, 0.01071805138282269, sync_count = 0;
0.01505751553351295, 0.01679337935001369, 0.015256245142156299, 0.01042830577908502, missing_sync_count = 0;
0.003031522725559901, -0.0055333532968188165, -0.013403099825723372, -0.018598682349642525,
-0.01944761739590459, -0.015005271935951746, -0.0053887880354343935, 0.008056525910253532, dev.reset();
0.022816244158307273, 0.035513467692208076, 0.04244131815783876, 0.04025481153629372, framer.reset();
0.02671818654865632, 0.0013810216516704976, -0.03394615682795165, -0.07502635967975885, decoder.reset();
-0.11540977897637611, -0.14703962203941534, -0.16119995609538576, -0.14969512896336504, }
-0.10610329539459686, -0.026921412469634916, 0.08757875030779196, 0.23293327870303457,
0.4006012210123992, 0.5786324696325503, 0.7528286479934068, 0.908262741447522, void M17Demodulator::dcd_off()
1.0309661131633199, 1.1095611856548013, 1.1366197723675815, 1.1095611856548013, {
1.0309661131633199, 0.908262741447522, 0.7528286479934068, 0.5786324696325503, // Just lost data carrier.
0.4006012210123992, 0.23293327870303457, 0.08757875030779196, -0.026921412469634916, dcd_ = false;
-0.10610329539459686, -0.14969512896336504, -0.16119995609538576, -0.14703962203941534, demodState = DemodState::UNLOCKED;
-0.11540977897637611, -0.07502635967975885, -0.03394615682795165, 0.0013810216516704976, decoder.reset();
0.02671818654865632, 0.04025481153629372, 0.04244131815783876, 0.035513467692208076,
0.022816244158307273, 0.008056525910253532, -0.0053887880354343935, -0.015005271935951746, if (diagnostic_callback)
-0.01944761739590459, -0.018598682349642525, -0.013403099825723372, -0.0055333532968188165, {
0.003031522725559901, 0.01042830577908502, 0.015256245142156299, 0.01679337935001369, diagnostic_callback(int(dcd_), dev.error(), dev.deviation(), dev.offset(), (int) demodState,
0.01505751553351295, 0.01071805138282269, 0.004891777252042491, -0.001125978562075172, clock_recovery.clock_estimate(), sample_index, sync_sample_index, clock_recovery.sample_index(), -1);
-0.006136551625729697, -0.009265784007800534, -0.010105075751866371, -0.008746639552588416, }
-0.00571615173291049, -0.0018256054303579805, 0.002017321931508163, 0.0050017573119839134, }
0.006569574194782443, 0.006506845894531803, 0.004957956568090113, 0.002365929161655135,
-0.0006430502751187517, -0.003403189203405097, -0.005349511529163396, -0.006126925416243605, void M17Demodulator::initialize(const float input)
-0.005648131453822014, -0.004092346891623224, -0.0018493784971116507, 0.0005756567993179152, {
0.002674727068399207, 0.00404219185231544, 0.004451886520053017, 0.0038920145144327816, auto filtered_sample = demod_filter(input);
0.002553421969211845, 0.0007766690889758685, -0.0010285696910973807, -0.0024733964729590865, correlator.sample(filtered_sample);
-0.0032697038088887226, -0.0032843366522956313, -0.0025577136087664687, -0.0012851320781224025, }
0.00023319405581230247, 0.001661182944400927, 0.002699564567597445, 0.0031468394550958484,
0.0029364388513841593, 0.0 void M17Demodulator::update_dcd()
}; {
if (!dcd_ && dcd.dcd())
{
// fputs("\nAOS\n", stderr);
dcd_on();
need_clock_reset_ = true;
}
else if (dcd_ && !dcd.dcd())
{
// fputs("\nLOS\n", stderr);
dcd_off();
}
}
void M17Demodulator::do_unlocked()
{
// We expect to find the preamble immediately after DCD.
if (missing_sync_count < 1920)
{
missing_sync_count += 1;
auto sync_index = preamble_sync(correlator);
auto sync_updated = preamble_sync.updated();
if (sync_updated)
{
sync_count = 0;
missing_sync_count = 0;
need_clock_reset_ = true;
dev.reset();
update_values(sync_index);
sample_index = sync_index;
demodState = DemodState::LSF_SYNC;
}
return;
}
auto sync_index = lsf_sync(correlator);
auto sync_updated = lsf_sync.updated();
if (sync_updated)
{
sync_count = 0;
missing_sync_count = 0;
need_clock_reset_ = true;
dev.reset();
update_values(sync_index);
sample_index = sync_index;
demodState = DemodState::FRAME;
if (sync_updated < 0)
{
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
}
else
{
sync_word_type = M17FrameDecoder::SyncWordType::LSF;
}
return;
}
sync_index = packet_sync(correlator);
sync_updated = packet_sync.updated();
if (sync_updated < 0)
{
sync_count = 0;
missing_sync_count = 0;
need_clock_reset_ = true;
dev.reset();
update_values(sync_index);
sample_index = sync_index;
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
}
}
/**
* Check for LSF sync word. We only enter the DemodState::LSF_SYNC state
* if a preamble sync has been detected, which also means that sample_index
* has been initialized to a sane value for the baseband.
*/
void M17Demodulator::do_lsf_sync()
{
float sync_triggered = 0.;
float bert_triggered = 0.;
if (correlator.index() == sample_index)
{
sync_triggered = preamble_sync.triggered(correlator);
if (sync_triggered > 0.1)
{
return;
}
sync_triggered = lsf_sync.triggered(correlator);
bert_triggered = packet_sync.triggered(correlator);
if (bert_triggered < 0)
{
missing_sync_count = 0;
need_clock_update_ = true;
update_values(sample_index);
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
}
else if (std::abs(sync_triggered) > 0.1)
{
missing_sync_count = 0;
need_clock_update_ = true;
update_values(sample_index);
if (sync_triggered > 0)
{
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::LSF;
}
else
{
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
}
}
else if (++missing_sync_count > 192)
{
demodState = DemodState::UNLOCKED;
decoder.reset();
missing_sync_count = 0;
}
else
{
update_values(sample_index);
}
}
}
/**
* Check for a stream sync word (LSF sync word that is maximally negative).
* We can enter DemodState::STREAM_SYNC from either a valid LSF decode for
* an audio stream, or from a stream frame decode.
*
*/
void M17Demodulator::do_stream_sync()
{
uint8_t sync_index = lsf_sync(correlator);
int8_t sync_updated = lsf_sync.updated();
sync_count += 1;
if (sync_updated < 0) // Stream sync word
{
missing_sync_count = 0;
if (sync_count > 70)
{
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
demodState = DemodState::FRAME;
}
return;
}
else if (sync_count > 87)
{
update_values(sync_index);
missing_sync_count += 1;
if (missing_sync_count < MAX_MISSING_SYNC)
{
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
demodState = DemodState::FRAME;
}
else
{
// fputs("\n!SYNC\n", stderr);
demodState = DemodState::LSF_SYNC;
}
}
}
/**
* Check for a packet sync word. DemodState::PACKET_SYNC can only be
* entered from a valid LSF frame decode with the data/packet type bit set.
*/
void M17Demodulator::do_packet_sync()
{
auto sync_index = packet_sync(correlator);
auto sync_updated = packet_sync.updated();
sync_count += 1;
if (sync_count > 70 && sync_updated)
{
missing_sync_count = 0;
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
demodState = DemodState::FRAME;
}
else if (sync_count > 87)
{
missing_sync_count += 1;
if (missing_sync_count < MAX_MISSING_SYNC)
{
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
demodState = DemodState::FRAME;
}
else
{
demodState = DemodState::UNLOCKED;
decoder.reset();
}
}
}
/**
* Check for a bert sync word.
*/
void M17Demodulator::do_bert_sync()
{
auto sync_index = packet_sync(correlator);
auto sync_updated = packet_sync.updated();
sync_count += 1;
if (sync_count > 70 && sync_updated < 0)
{
missing_sync_count = 0;
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
demodState = DemodState::FRAME;
}
else if (sync_count > 87)
{
missing_sync_count += 1;
if (missing_sync_count < MAX_MISSING_SYNC)
{
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
demodState = DemodState::FRAME;
}
else
{
demodState = DemodState::UNLOCKED;
decoder.reset();
}
}
}
void M17Demodulator::do_frame(float filtered_sample)
{
if (correlator.index() != sample_index) return;
static uint8_t cost_count = 0;
auto sample = filtered_sample - dev.offset();
sample *= dev.idev();
sample *= polarity;
auto n = llr<4>(sample);
int8_t* tmp;
auto len = framer(n, &tmp);
if (len != 0)
{
need_clock_update_ = true;
M17FrameDecoder::input_buffer_t buffer;
std::copy(tmp, tmp + len, buffer.begin());
auto valid = decoder(sync_word_type, buffer, viterbi_cost);
cost_count = viterbi_cost > 90 ? cost_count + 1 : 0;
cost_count = viterbi_cost > 100 ? cost_count + 1 : cost_count;
cost_count = viterbi_cost > 110 ? cost_count + 1 : cost_count;
if (cost_count > 75)
{
cost_count = 0;
demodState = DemodState::UNLOCKED;
decoder.reset();
// fputs("\nCOST\n", stderr);
return;
}
switch (decoder.state())
{
case M17FrameDecoder::State::STREAM:
demodState = DemodState::STREAM_SYNC;
break;
case M17FrameDecoder::State::LSF:
// If state == LSF, we need to recover LSF from LICH.
demodState = DemodState::STREAM_SYNC;
break;
case M17FrameDecoder::State::BERT:
demodState = DemodState::BERT_SYNC;
break;
default:
demodState = DemodState::PACKET_SYNC;
break;
}
sync_count = 0;
switch (valid)
{
case M17FrameDecoder::DecodeResult::FAIL:
break;
case M17FrameDecoder::DecodeResult::EOS:
demodState = DemodState::LSF_SYNC;
break;
case M17FrameDecoder::DecodeResult::OK:
break;
case M17FrameDecoder::DecodeResult::INCOMPLETE:
break;
case M17FrameDecoder::DecodeResult::PACKET_INCOMPLETE:
break;
}
}
}
void M17Demodulator::operator()(const float input)
{
static int16_t initializing = 1920;
count_++;
dcd(input);
// We need to pump a few ms of data through on startup to initialize
// the demodulator.
if (initializing) [[unlikely]]
{
--initializing;
initialize(input);
count_ = 0;
return;
}
if (!dcd_)
{
if (count_ % (BLOCK_SIZE * 2) == 0)
{
update_dcd();
dcd.update();
if (diagnostic_callback)
{
diagnostic_callback(int(dcd_), dev.error(), dev.deviation(), dev.offset(), (int) demodState,
clock_recovery.clock_estimate(), sample_index, sync_sample_index, clock_recovery.sample_index(), viterbi_cost);
}
count_ = 0;
}
return;
}
auto filtered_sample = demod_filter(input);
correlator.sample(filtered_sample);
if (correlator.index() == 0)
{
if (need_clock_reset_)
{
clock_recovery.reset();
need_clock_reset_ = false;
}
else if (need_clock_update_) // must avoid update immediately after reset.
{
clock_recovery.update();
uint8_t clock_index = clock_recovery.sample_index();
uint8_t clock_diff = std::abs(sample_index - clock_index);
uint8_t sync_diff = std::abs(sample_index - sync_sample_index);
bool clock_diff_ok = clock_diff <= 1 || clock_diff == 9;
bool sync_diff_ok = sync_diff <= 1 || sync_diff == 9;
if (clock_diff_ok) sample_index = clock_index;
else if (sync_diff_ok) sample_index = sync_sample_index;
// else unchanged.
need_clock_update_ = false;
}
}
clock_recovery(filtered_sample);
if (demodState != DemodState::UNLOCKED && correlator.index() == sample_index)
{
dev.sample(filtered_sample);
}
switch (demodState)
{
case DemodState::UNLOCKED:
// In this state, the sample_index is unknown. We need to find
// a sync word to find the proper sample_index. We only leave
// this state if we believe that we have a valid sample_index.
do_unlocked();
break;
case DemodState::LSF_SYNC:
do_lsf_sync();
break;
case DemodState::STREAM_SYNC:
do_stream_sync();
break;
case DemodState::PACKET_SYNC:
do_packet_sync();
break;
case DemodState::BERT_SYNC:
do_bert_sync();
break;
case DemodState::FRAME:
do_frame(filtered_sample);
break;
}
if (count_ % (BLOCK_SIZE * 5) == 0)
{
update_dcd();
count_ = 0;
if (diagnostic_callback)
{
diagnostic_callback(int(dcd_), dev.error(), dev.deviation(), dev.offset(), (int) demodState,
clock_recovery.clock_estimate(), sample_index, sync_sample_index, clock_recovery.sample_index(), viterbi_cost);
}
dcd.update();
}
}
} // mobilinkd } // mobilinkd

View File

@ -26,7 +26,6 @@ namespace detail
} // detail } // detail
template <typename FloatType>
struct M17Demodulator struct M17Demodulator
{ {
static const uint16_t SAMPLE_RATE = 48000; static const uint16_t SAMPLE_RATE = 48000;
@ -34,15 +33,15 @@ struct M17Demodulator
static const uint16_t SAMPLES_PER_SYMBOL = SAMPLE_RATE / SYMBOL_RATE; static const uint16_t SAMPLES_PER_SYMBOL = SAMPLE_RATE / SYMBOL_RATE;
static const uint16_t BLOCK_SIZE = 192; static const uint16_t BLOCK_SIZE = 192;
static constexpr FloatType sample_rate = SAMPLE_RATE; static constexpr float sample_rate = SAMPLE_RATE;
static constexpr FloatType symbol_rate = SYMBOL_RATE; static constexpr float symbol_rate = SYMBOL_RATE;
static const uint8_t MAX_MISSING_SYNC = 8; static const uint8_t MAX_MISSING_SYNC = 8;
using collelator_t = Correlator<FloatType>; using collelator_t = Correlator;
using sync_word_t = SyncWord<collelator_t>; using sync_word_t = SyncWord<collelator_t>;
using callback_t = M17FrameDecoder::callback_t; using callback_t = M17FrameDecoder::callback_t;
using diagnostic_callback_t = std::function<void(bool, FloatType, FloatType, FloatType, int, FloatType, int, int, int, int)>; using diagnostic_callback_t = std::function<void(bool, float, float, float, int, float, int, int, int, int)>;
enum class DemodState { enum class DemodState {
UNLOCKED, UNLOCKED,
@ -53,16 +52,16 @@ struct M17Demodulator
FRAME FRAME
}; };
DataCarrierDetect<FloatType, SAMPLE_RATE, 500> dcd{2500, 4000, 1.0, 4.0}; DataCarrierDetect<SAMPLE_RATE, 500> dcd{2500, 4000, 1.0, 4.0};
ClockRecovery<FloatType, SAMPLE_RATE, SYMBOL_RATE> clock_recovery; ClockRecovery<SAMPLE_RATE, SYMBOL_RATE> clock_recovery;
collelator_t correlator; collelator_t correlator;
sync_word_t preamble_sync{{+3,-3,+3,-3,+3,-3,+3,-3}, 29.f}; sync_word_t preamble_sync{{+3,-3,+3,-3,+3,-3,+3,-3}, 29.f};
sync_word_t lsf_sync{{+3,+3,+3,+3,-3,-3,+3,-3}, 32.f, -31.f}; // LSF or STREAM (inverted) sync_word_t lsf_sync{{+3,+3,+3,+3,-3,-3,+3,-3}, 32.f, -31.f}; // LSF or STREAM (inverted)
sync_word_t packet_sync{{3,-3,3,3,-3,-3,-3,-3}, 31.f, -31.f}; // PACKET or BERT (inverted) sync_word_t packet_sync{{3,-3,3,3,-3,-3,-3,-3}, 31.f, -31.f}; // PACKET or BERT (inverted)
FreqDevEstimator<FloatType> dev; FreqDevEstimator dev;
FloatType idev; float idev;
size_t count_ = 0; size_t count_ = 0;
int8_t polarity = 1; int8_t polarity = 1;
@ -91,14 +90,14 @@ struct M17Demodulator
void dcd_on(); void dcd_on();
void dcd_off(); void dcd_off();
void initialize(const FloatType input); void initialize(const float input);
void update_dcd(); void update_dcd();
void do_unlocked(); void do_unlocked();
void do_lsf_sync(); void do_lsf_sync();
void do_packet_sync(); void do_packet_sync();
void do_stream_sync(); void do_stream_sync();
void do_bert_sync(); void do_bert_sync();
void do_frame(FloatType filtered_sample); void do_frame(float filtered_sample);
bool locked() const bool locked() const
{ {
@ -118,472 +117,11 @@ struct M17Demodulator
void update_values(uint8_t index); void update_values(uint8_t index);
void operator()(const FloatType input); void operator()(const float input);
private: private:
static const std::array<FloatType, 150> rrc_taps; static const std::array<float, 150> rrc_taps;
BaseFirFilter<FloatType, rrc_taps.size()> demod_filter{rrc_taps}; BaseFirFilter<rrc_taps.size()> demod_filter{rrc_taps};
}; };
template <typename FloatType>
void M17Demodulator<FloatType>::update_values(uint8_t index)
{
correlator.apply([this,index](FloatType t){dev.sample(t);}, index);
dev.update();
sync_sample_index = index;
}
template <typename FloatType>
void M17Demodulator<FloatType>::dcd_on()
{
// Data carrier newly detected.
dcd_ = true;
sync_count = 0;
missing_sync_count = 0;
dev.reset();
framer.reset();
decoder.reset();
}
template <typename FloatType>
void M17Demodulator<FloatType>::dcd_off()
{
// Just lost data carrier.
dcd_ = false;
demodState = DemodState::UNLOCKED;
decoder.reset();
if (diagnostic_callback)
{
diagnostic_callback(int(dcd_), dev.error(), dev.deviation(), dev.offset(), (int) demodState,
clock_recovery.clock_estimate(), sample_index, sync_sample_index, clock_recovery.sample_index(), -1);
}
}
template <typename FloatType>
void M17Demodulator<FloatType>::initialize(const FloatType input)
{
auto filtered_sample = demod_filter(input);
correlator.sample(filtered_sample);
}
template <typename FloatType>
void M17Demodulator<FloatType>::update_dcd()
{
if (!dcd_ && dcd.dcd())
{
// fputs("\nAOS\n", stderr);
dcd_on();
need_clock_reset_ = true;
}
else if (dcd_ && !dcd.dcd())
{
// fputs("\nLOS\n", stderr);
dcd_off();
}
}
template <typename FloatType>
void M17Demodulator<FloatType>::do_unlocked()
{
// We expect to find the preamble immediately after DCD.
if (missing_sync_count < 1920)
{
missing_sync_count += 1;
auto sync_index = preamble_sync(correlator);
auto sync_updated = preamble_sync.updated();
if (sync_updated)
{
sync_count = 0;
missing_sync_count = 0;
need_clock_reset_ = true;
dev.reset();
update_values(sync_index);
sample_index = sync_index;
demodState = DemodState::LSF_SYNC;
}
return;
}
auto sync_index = lsf_sync(correlator);
auto sync_updated = lsf_sync.updated();
if (sync_updated)
{
sync_count = 0;
missing_sync_count = 0;
need_clock_reset_ = true;
dev.reset();
update_values(sync_index);
sample_index = sync_index;
demodState = DemodState::FRAME;
if (sync_updated < 0)
{
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
}
else
{
sync_word_type = M17FrameDecoder::SyncWordType::LSF;
}
return;
}
sync_index = packet_sync(correlator);
sync_updated = packet_sync.updated();
if (sync_updated < 0)
{
sync_count = 0;
missing_sync_count = 0;
need_clock_reset_ = true;
dev.reset();
update_values(sync_index);
sample_index = sync_index;
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
}
}
/**
* Check for LSF sync word. We only enter the DemodState::LSF_SYNC state
* if a preamble sync has been detected, which also means that sample_index
* has been initialized to a sane value for the baseband.
*/
template <typename FloatType>
void M17Demodulator<FloatType>::do_lsf_sync()
{
FloatType sync_triggered = 0.;
FloatType bert_triggered = 0.;
if (correlator.index() == sample_index)
{
sync_triggered = preamble_sync.triggered(correlator);
if (sync_triggered > 0.1)
{
return;
}
sync_triggered = lsf_sync.triggered(correlator);
bert_triggered = packet_sync.triggered(correlator);
if (bert_triggered < 0)
{
missing_sync_count = 0;
need_clock_update_ = true;
update_values(sample_index);
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
}
else if (std::abs(sync_triggered) > 0.1)
{
missing_sync_count = 0;
need_clock_update_ = true;
update_values(sample_index);
if (sync_triggered > 0)
{
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::LSF;
}
else
{
demodState = DemodState::FRAME;
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
}
}
else if (++missing_sync_count > 192)
{
demodState = DemodState::UNLOCKED;
decoder.reset();
missing_sync_count = 0;
}
else
{
update_values(sample_index);
}
}
}
/**
* Check for a stream sync word (LSF sync word that is maximally negative).
* We can enter DemodState::STREAM_SYNC from either a valid LSF decode for
* an audio stream, or from a stream frame decode.
*
*/
template <typename FloatType>
void M17Demodulator<FloatType>::do_stream_sync()
{
uint8_t sync_index = lsf_sync(correlator);
int8_t sync_updated = lsf_sync.updated();
sync_count += 1;
if (sync_updated < 0) // Stream sync word
{
missing_sync_count = 0;
if (sync_count > 70)
{
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
demodState = DemodState::FRAME;
}
return;
}
else if (sync_count > 87)
{
update_values(sync_index);
missing_sync_count += 1;
if (missing_sync_count < MAX_MISSING_SYNC)
{
sync_word_type = M17FrameDecoder::SyncWordType::STREAM;
demodState = DemodState::FRAME;
}
else
{
// fputs("\n!SYNC\n", stderr);
demodState = DemodState::LSF_SYNC;
}
}
}
/**
* Check for a packet sync word. DemodState::PACKET_SYNC can only be
* entered from a valid LSF frame decode with the data/packet type bit set.
*/
template <typename FloatType>
void M17Demodulator<FloatType>::do_packet_sync()
{
auto sync_index = packet_sync(correlator);
auto sync_updated = packet_sync.updated();
sync_count += 1;
if (sync_count > 70 && sync_updated)
{
missing_sync_count = 0;
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
demodState = DemodState::FRAME;
}
else if (sync_count > 87)
{
missing_sync_count += 1;
if (missing_sync_count < MAX_MISSING_SYNC)
{
sync_word_type = M17FrameDecoder::SyncWordType::PACKET;
demodState = DemodState::FRAME;
}
else
{
demodState = DemodState::UNLOCKED;
decoder.reset();
}
}
}
/**
* Check for a bert sync word.
*/
template <typename FloatType>
void M17Demodulator<FloatType>::do_bert_sync()
{
auto sync_index = packet_sync(correlator);
auto sync_updated = packet_sync.updated();
sync_count += 1;
if (sync_count > 70 && sync_updated < 0)
{
missing_sync_count = 0;
update_values(sync_index);
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
demodState = DemodState::FRAME;
}
else if (sync_count > 87)
{
missing_sync_count += 1;
if (missing_sync_count < MAX_MISSING_SYNC)
{
sync_word_type = M17FrameDecoder::SyncWordType::BERT;
demodState = DemodState::FRAME;
}
else
{
demodState = DemodState::UNLOCKED;
decoder.reset();
}
}
}
template <typename FloatType>
void M17Demodulator<FloatType>::do_frame(FloatType filtered_sample)
{
if (correlator.index() != sample_index) return;
static uint8_t cost_count = 0;
auto sample = filtered_sample - dev.offset();
sample *= dev.idev();
sample *= polarity;
auto n = llr<FloatType, 4>(sample);
int8_t* tmp;
auto len = framer(n, &tmp);
if (len != 0)
{
need_clock_update_ = true;
M17FrameDecoder::input_buffer_t buffer;
std::copy(tmp, tmp + len, buffer.begin());
auto valid = decoder(sync_word_type, buffer, viterbi_cost);
cost_count = viterbi_cost > 90 ? cost_count + 1 : 0;
cost_count = viterbi_cost > 100 ? cost_count + 1 : cost_count;
cost_count = viterbi_cost > 110 ? cost_count + 1 : cost_count;
if (cost_count > 75)
{
cost_count = 0;
demodState = DemodState::UNLOCKED;
decoder.reset();
// fputs("\nCOST\n", stderr);
return;
}
switch (decoder.state())
{
case M17FrameDecoder::State::STREAM:
demodState = DemodState::STREAM_SYNC;
break;
case M17FrameDecoder::State::LSF:
// If state == LSF, we need to recover LSF from LICH.
demodState = DemodState::STREAM_SYNC;
break;
case M17FrameDecoder::State::BERT:
demodState = DemodState::BERT_SYNC;
break;
default:
demodState = DemodState::PACKET_SYNC;
break;
}
sync_count = 0;
switch (valid)
{
case M17FrameDecoder::DecodeResult::FAIL:
break;
case M17FrameDecoder::DecodeResult::EOS:
demodState = DemodState::LSF_SYNC;
break;
case M17FrameDecoder::DecodeResult::OK:
break;
case M17FrameDecoder::DecodeResult::INCOMPLETE:
break;
case M17FrameDecoder::DecodeResult::PACKET_INCOMPLETE:
break;
}
}
}
template <typename FloatType>
void M17Demodulator<FloatType>::operator()(const FloatType input)
{
static int16_t initializing = 1920;
count_++;
dcd(input);
// We need to pump a few ms of data through on startup to initialize
// the demodulator.
if (initializing) [[unlikely]]
{
--initializing;
initialize(input);
count_ = 0;
return;
}
if (!dcd_)
{
if (count_ % (BLOCK_SIZE * 2) == 0)
{
update_dcd();
dcd.update();
if (diagnostic_callback)
{
diagnostic_callback(int(dcd_), dev.error(), dev.deviation(), dev.offset(), (int) demodState,
clock_recovery.clock_estimate(), sample_index, sync_sample_index, clock_recovery.sample_index(), viterbi_cost);
}
count_ = 0;
}
return;
}
auto filtered_sample = demod_filter(input);
correlator.sample(filtered_sample);
if (correlator.index() == 0)
{
if (need_clock_reset_)
{
clock_recovery.reset();
need_clock_reset_ = false;
}
else if (need_clock_update_) // must avoid update immediately after reset.
{
clock_recovery.update();
uint8_t clock_index = clock_recovery.sample_index();
uint8_t clock_diff = std::abs(sample_index - clock_index);
uint8_t sync_diff = std::abs(sample_index - sync_sample_index);
bool clock_diff_ok = clock_diff <= 1 || clock_diff == 9;
bool sync_diff_ok = sync_diff <= 1 || sync_diff == 9;
if (clock_diff_ok) sample_index = clock_index;
else if (sync_diff_ok) sample_index = sync_sample_index;
// else unchanged.
need_clock_update_ = false;
}
}
clock_recovery(filtered_sample);
if (demodState != DemodState::UNLOCKED && correlator.index() == sample_index)
{
dev.sample(filtered_sample);
}
switch (demodState)
{
case DemodState::UNLOCKED:
// In this state, the sample_index is unknown. We need to find
// a sync word to find the proper sample_index. We only leave
// this state if we believe that we have a valid sample_index.
do_unlocked();
break;
case DemodState::LSF_SYNC:
do_lsf_sync();
break;
case DemodState::STREAM_SYNC:
do_stream_sync();
break;
case DemodState::PACKET_SYNC:
do_packet_sync();
break;
case DemodState::BERT_SYNC:
do_bert_sync();
break;
case DemodState::FRAME:
do_frame(filtered_sample);
break;
}
if (count_ % (BLOCK_SIZE * 5) == 0)
{
update_dcd();
count_ = 0;
if (diagnostic_callback)
{
diagnostic_callback(int(dcd_), dev.error(), dev.deviation(), dev.offset(), (int) demodState,
clock_recovery.clock_estimate(), sample_index, sync_sample_index, clock_recovery.sample_index(), viterbi_cost);
}
dcd.update();
}
}
} // mobilinkd } // mobilinkd

View File

@ -595,7 +595,7 @@ public:
static baseband_t symbols_to_baseband(const symbols_t& symbols) static baseband_t symbols_to_baseband(const symbols_t& symbols)
{ {
// Generated using scikit-commpy // Generated using scikit-commpy
static const auto rrc_taps = std::array<double, 79>{ static const auto rrc_taps = std::array<float, 79>{
-0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491, -0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491,
0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299, 0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299,
0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372, 0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372,
@ -617,7 +617,7 @@ public:
0.01679337935001369, 0.01505751553351295, 0.01071805138282269, 0.004891777252042491, 0.01679337935001369, 0.01505751553351295, 0.01071805138282269, 0.004891777252042491,
-0.001125978562075172, -0.006136551625729697, -0.009265784007800534 -0.001125978562075172, -0.006136551625729697, -0.009265784007800534
}; };
static BaseFirFilter<double, std::tuple_size<decltype(rrc_taps)>::value> rrc = makeFirFilter(rrc_taps); static BaseFirFilter<std::tuple_size<decltype(rrc_taps)>::value> rrc = makeFirFilter(rrc_taps);
std::array<int16_t, 1920> baseband; std::array<int16_t, 1920> baseband;
baseband.fill(0); baseband.fill(0);

View File

@ -17,33 +17,32 @@ namespace mobilinkd
* these errors have not affected the performance of clock * these errors have not affected the performance of clock
* recovery. * recovery.
*/ */
template <typename FloatType>
struct PhaseEstimator struct PhaseEstimator
{ {
using float_type = FloatType; using float_type = float;
using samples_t = std::array<FloatType, 3>; // 3 samples in length using samples_t = std::array<float, 3>; // 3 samples in length
float_type dx_; float_type dx_;
PhaseEstimator(FloatType sample_rate, FloatType symbol_rate) PhaseEstimator(float sample_rate, float symbol_rate)
: dx_(2.0 * symbol_rate / sample_rate) : dx_(2.0 * symbol_rate / sample_rate)
{} {}
/** /**
* This performs a rolling estimate of the phase. * This performs a rolling estimate of the phase.
* *
* @param samples are three samples centered around the current sample point * @param samples are three samples centered around the current sample point
* (t-1, t, t+1). * (t-1, t, t+1).
*/ */
float_type operator()(const samples_t& samples) float_type operator()(const samples_t& samples)
{ {
assert(dx_ > 0.0); assert(dx_ > 0.0);
auto ratio = ((samples.at(2) - samples.at(0)) / 3.0) / dx_; float ratio = ((samples.at(2) - samples.at(0)) / 3.0) / dx_;
// Clamp +/-5. // Clamp +/-5.
ratio = std::min(FloatType(5.0), ratio); ratio = std::min(float(5.0), ratio);
ratio = std::max(FloatType(-5.0), ratio); ratio = std::max(float(-5.0), ratio);
return ratio; return ratio;
} }
}; };

View File

@ -17,18 +17,18 @@ namespace mobilinkd
* Eric Jacobsen, 2015-04-23 * Eric Jacobsen, 2015-04-23
* https://www.dsprelated.com/showarticle/776.php * https://www.dsprelated.com/showarticle/776.php
*/ */
template <typename FloatType, size_t SampleRate, size_t Frequency, size_t Accuracy = 1000> template <size_t SampleRate, size_t Frequency, size_t Accuracy = 1000>
class SlidingDFT class SlidingDFT
{ {
using ComplexType = std::complex<FloatType>; using ComplexType = std::complex<float>;
static constexpr size_t N = SampleRate / Accuracy; static constexpr size_t N = SampleRate / Accuracy;
static constexpr FloatType pi2 = M_PI * 2.0; static constexpr float pi2 = M_PI * 2.0;
static constexpr FloatType kth = FloatType(Frequency) / FloatType(SampleRate); static constexpr float kth = float(Frequency) / float(SampleRate);
// We'd like this to be static constexpr, but std::exp is not a constexpr. // We'd like this to be static constexpr, but std::exp is not a constexpr.
const ComplexType coeff_; const ComplexType coeff_;
std::array<FloatType, N> samples_; std::array<float, N> samples_;
ComplexType result_{0,0}; ComplexType result_{0,0};
size_t index_ = 0; size_t index_ = 0;
size_t prev_index_ = N - 1; size_t prev_index_ = N - 1;
@ -40,15 +40,15 @@ public:
coeff_ = std::exp(-ComplexType{0, 1} * pi2 * kth); coeff_ = std::exp(-ComplexType{0, 1} * pi2 * kth);
} }
ComplexType operator()(FloatType sample) ComplexType operator()(float sample)
{ {
auto index = index_; auto index = index_;
index_ += 1; index_ += 1;
if (index_ == N) index_ = 0; if (index_ == N) index_ = 0;
FloatType delta = sample - samples_[index]; float delta = sample - samples_[index];
ComplexType result = (result_ + delta) * coeff_; ComplexType result = (result_ + delta) * coeff_;
result_ = result * FloatType(0.999999999999999); result_ = result * float(0.999999999999999);
samples_[index] = sample; samples_[index] = sample;
prev_index_ = index; prev_index_ = index;
return result; return result;
@ -62,21 +62,21 @@ public:
* Eric Jacobsen, 2015-04-23 * Eric Jacobsen, 2015-04-23
* https://www.dsprelated.com/showarticle/776.php * https://www.dsprelated.com/showarticle/776.php
* *
* @tparam FloatType is the floating point type to use. * @tparam float is the floating point type to use.
* @tparam SampleRate is the sample rate of the incoming data. * @tparam SampleRate is the sample rate of the incoming data.
* @tparam N is the length of the DFT. Frequency resolution is SampleRate / N. * @tparam N is the length of the DFT. Frequency resolution is SampleRate / N.
* @tparam K is the number of frequencies whose DFT will be calculated. * @tparam K is the number of frequencies whose DFT will be calculated.
*/ */
template <typename FloatType, size_t SampleRate, size_t N, size_t K> template <size_t SampleRate, size_t N, size_t K>
class NSlidingDFT class NSlidingDFT
{ {
using ComplexType = std::complex<FloatType>; using ComplexType = std::complex<float>;
static constexpr FloatType pi2 = M_PI * 2.0; static constexpr float pi2 = M_PI * 2.0;
// We'd like this to be static constexpr, but std::exp is not a constexpr. // We'd like this to be static constexpr, but std::exp is not a constexpr.
const std::array<ComplexType, K> coeff_; const std::array<ComplexType, K> coeff_;
std::array<FloatType, N> samples_; std::array<float, N> samples_;
std::array<ComplexType, K> result_{0,0}; std::array<ComplexType, K> result_{0,0};
size_t index_ = 0; size_t index_ = 0;
size_t prev_index_ = N - 1; size_t prev_index_ = N - 1;
@ -88,7 +88,7 @@ class NSlidingDFT
std::array<ComplexType, K> result; std::array<ComplexType, K> result;
for (size_t i = 0; i != K; ++i) for (size_t i = 0; i != K; ++i)
{ {
FloatType k = FloatType(frequencies[i]) / FloatType(SampleRate); float k = float(frequencies[i]) / float(SampleRate);
result[i] = std::exp(-j * pi2 * k); result[i] = std::exp(-j * pi2 * k);
} }
return result; return result;
@ -115,13 +115,13 @@ public:
* constructor. The result is only valid after at least N samples * constructor. The result is only valid after at least N samples
* have been cycled in. * have been cycled in.
*/ */
result_type operator()(FloatType sample) result_type operator()(float sample)
{ {
auto index = index_; auto index = index_;
index_ += 1; index_ += 1;
if (index_ == N) index_ = 0; if (index_ == N) index_ = 0;
FloatType delta = sample - samples_[index]; float delta = sample - samples_[index];
for (size_t i = 0; i != K; ++i) for (size_t i = 0; i != K; ++i)
{ {

View File

@ -13,23 +13,23 @@
namespace mobilinkd namespace mobilinkd
{ {
template <typename FloatType, size_t N> template <size_t N>
struct SymbolEvm struct SymbolEvm
{ {
using filter_type = BaseIirFilter<FloatType, N>; using filter_type = BaseIirFilter<float, N>;
using symbol_t = int; using symbol_t = int;
using result_type = std::tuple<symbol_t, FloatType>; using result_type = std::tuple<symbol_t, float>;
filter_type filter_; filter_type filter_;
FloatType erasure_limit_; float erasure_limit_;
FloatType evm_ = 0.0; float evm_ = 0.0;
SymbolEvm(filter_type&& filter, FloatType erasure_limit = 0.0) : SymbolEvm(filter_type&& filter, float erasure_limit = 0.0) :
filter_(std::forward<filter_type>(filter)), filter_(std::forward<filter_type>(filter)),
erasure_limit_(erasure_limit) erasure_limit_(erasure_limit)
{} {}
FloatType evm() const { return evm_; } float evm() const { return evm_; }
/** /**
* Decode a normalized sample into a symbol. Symbols * Decode a normalized sample into a symbol. Symbols
@ -37,10 +37,10 @@ struct SymbolEvm
* is set, symbols outside this limit are 'erased' and * is set, symbols outside this limit are 'erased' and
* returned as 0. * returned as 0.
*/ */
result_type operator()(FloatType sample) result_type operator()(float sample)
{ {
symbol_t symbol; symbol_t symbol;
FloatType evm; float evm;
sample = std::min(3.0, std::max(-3.0, sample)); sample = std::min(3.0, std::max(-3.0, sample));
@ -73,13 +73,13 @@ struct SymbolEvm
} }
}; };
template <typename FloatType, size_t N> template <size_t N>
SymbolEvm<FloatType, N> makeSymbolEvm( SymbolEvm<N> makeSymbolEvm(
BaseIirFilter<FloatType, N>&& filter, BaseIirFilter<N>&& filter,
FloatType erasure_limit = 0.0f float erasure_limit = 0.0f
) )
{ {
return std::move(SymbolEvm<FloatType, N>(std::move(filter), erasure_limit)); return std::move(SymbolEvm<float, N>(std::move(filter), erasure_limit));
} }
} // mobilinkd } // mobilinkd

View File

@ -59,19 +59,19 @@ constexpr size_t llr_size()
return llr_limit<N>() * 6 + 1; return llr_limit<N>() * 6 + 1;
} }
template<typename FloatType, size_t LLR> template<size_t LLR>
constexpr std::array<std::tuple<FloatType, std::tuple<int8_t, int8_t>>, llr_size<LLR>()> make_llr_map() constexpr std::array<std::tuple<float, std::tuple<int8_t, int8_t>>, llr_size<LLR>()> make_llr_map()
{ {
constexpr size_t size = llr_size<LLR>(); constexpr size_t size = llr_size<LLR>();
std::array<std::tuple<FloatType, std::tuple<int8_t, int8_t>>, size> result; std::array<std::tuple<float, std::tuple<int8_t, int8_t>>, size> result;
constexpr int8_t limit = llr_limit<LLR>(); constexpr int8_t limit = llr_limit<LLR>();
constexpr FloatType inc = 1.0 / FloatType(limit); constexpr float inc = 1.0 / float(limit);
int8_t i = limit; int8_t i = limit;
int8_t j = limit; int8_t j = limit;
// Output must be ordered by k, ascending. // Output must be ordered by k, ascending.
FloatType k = -3.0 + inc; float k = -3.0 + inc;
for (size_t index = 0; index != size; ++index) for (size_t index = 0; index != size; ++index)
{ {
auto& a = result[index]; auto& a = result[index];
@ -124,17 +124,17 @@ inline int from_4fsk(int symbol)
} }
} }
template <typename FloatType, size_t LLR> template <size_t LLR>
auto llr(FloatType sample) auto llr(float sample)
{ {
static auto symbol_map = detail::make_llr_map<FloatType, LLR>(); static auto symbol_map = detail::make_llr_map<LLR>();
static constexpr FloatType MAX_VALUE = 3.0; static constexpr float MAX_VALUE = 3.0;
static constexpr FloatType MIN_VALUE = -3.0; static constexpr float MIN_VALUE = -3.0;
FloatType s = std::min(MAX_VALUE, std::max(MIN_VALUE, sample)); float s = std::min(MAX_VALUE, std::max(MIN_VALUE, sample));
auto it = std::lower_bound(symbol_map.begin(), symbol_map.end(), s, auto it = std::lower_bound(symbol_map.begin(), symbol_map.end(), s,
[](std::tuple<FloatType, std::tuple<int8_t, int8_t>> const& e, FloatType s){ [](std::tuple<float, std::tuple<int8_t, int8_t>> const& e, float s){
return std::get<0>(e) < s; return std::get<0>(e) < s;
}); });

View File

@ -96,7 +96,7 @@ private:
bool m_noiseBlanker; bool m_noiseBlanker;
struct CODEC2 *m_codec2; struct CODEC2 *m_codec2;
static M17DemodProcessor *m_this; static M17DemodProcessor *m_this;
mobilinkd::M17Demodulator<float> m_demod; mobilinkd::M17Demodulator m_demod;
AudioFifo *m_audioFifo; AudioFifo *m_audioFifo;
bool m_audioMute; bool m_audioMute;
AudioVector m_audioBuffer; AudioVector m_audioBuffer;