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:
parent
f326860f64
commit
424d072f0c
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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_; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user