kopia lustrzana https://github.com/mobilinkd/m17-cxx-demod
Remove unused include files
rodzic
9e3f1fc550
commit
8e624b0fad
|
@ -1,41 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.h"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename FloatType>
|
||||
struct CarrierDetect
|
||||
{
|
||||
using result_t = std::tuple<bool, FloatType>;
|
||||
|
||||
BaseIirFilter<FloatType, 3> filter_;
|
||||
FloatType lock_;
|
||||
FloatType unlock_;
|
||||
bool locked_ = false;
|
||||
|
||||
CarrierDetect(std::array<FloatType, 3> const& b, std::array<FloatType, 3> const& a, FloatType lock_level, FloatType unlock_level)
|
||||
: filter_(b, a), lock_(lock_level), unlock_(unlock_level)
|
||||
{
|
||||
}
|
||||
|
||||
result_t operator()(FloatType value)
|
||||
{
|
||||
auto filtered = filter_(std::abs(value));
|
||||
if (locked_ && (filtered > unlock_)) locked_ = false;
|
||||
else if (!locked_ && (filtered < lock_)) locked_ = true;
|
||||
|
||||
return std::make_tuple(locked_, filtered);
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,96 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename T, size_t N = 10>
|
||||
struct DeviationError
|
||||
{
|
||||
using float_type = T;
|
||||
using array_t = std::array<float_type, N>;
|
||||
|
||||
array_t minima_{0};
|
||||
array_t maxima_{0};
|
||||
size_t min_index_ = 0;
|
||||
size_t max_index_ = 0;
|
||||
bool min_rolled_ = false;
|
||||
bool max_rolled_ = false;
|
||||
size_t min_count_ = 0;
|
||||
size_t max_count_ = 0;
|
||||
float_type min_estimate_ = 0.0;
|
||||
float_type max_estimate_ = 0.0;
|
||||
|
||||
const float_type ZERO = 0.0;
|
||||
|
||||
DeviationError()
|
||||
{
|
||||
minima_.fill(0.0);
|
||||
maxima_.fill(0.0);
|
||||
}
|
||||
|
||||
float_type operator()(float_type sample)
|
||||
{
|
||||
if (sample > ZERO)
|
||||
{
|
||||
if (sample > max_estimate_ * 0.67 or max_count_ == 5)
|
||||
{
|
||||
max_count_ = 0;
|
||||
maxima_[max_index_++] = sample;
|
||||
if (max_index_ == N)
|
||||
{
|
||||
max_rolled_ = true;
|
||||
max_index_ = 0;
|
||||
}
|
||||
if (max_rolled_)
|
||||
{
|
||||
max_estimate_ = std::accumulate(std::begin(maxima_), std::end(maxima_), ZERO) / N;
|
||||
}
|
||||
else
|
||||
{
|
||||
max_estimate_ = std::accumulate(std::begin(maxima_), std::begin(maxima_) + max_index_, ZERO) / max_index_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++max_count_;
|
||||
}
|
||||
}
|
||||
else if (sample < 0)
|
||||
{
|
||||
if (sample < min_estimate_ * 0.67 or min_count_ == 5)
|
||||
{
|
||||
min_count_ = 0;
|
||||
minima_[min_index_++] = sample;
|
||||
if (min_index_ == N)
|
||||
{
|
||||
min_rolled_ = true;
|
||||
min_index_ = 0;
|
||||
}
|
||||
if (min_rolled_)
|
||||
{
|
||||
min_estimate_ = std::accumulate(std::begin(minima_), std::end(minima_), ZERO) / N;
|
||||
}
|
||||
else
|
||||
{
|
||||
min_estimate_ = std::accumulate(std::begin(minima_), std::begin(minima_) + min_index_, ZERO) / min_index_;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++min_count_;
|
||||
}
|
||||
}
|
||||
|
||||
auto deviation = max_estimate_ - min_estimate_;
|
||||
auto deviation_error = std::min(6.0 / deviation, 100.0);
|
||||
return deviation_error;
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.h"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename FloatType, size_t N = 32>
|
||||
struct FrequencyError
|
||||
{
|
||||
using float_type = FloatType;
|
||||
using array_t = std::array<FloatType, N>;
|
||||
using filter_type = BaseIirFilter<FloatType, 3>;
|
||||
|
||||
static constexpr std::array<FloatType, 3> evm_b{0.02008337, 0.04016673, 0.02008337};
|
||||
static constexpr std::array<FloatType, 3> evm_a{1.0, -1.56101808, 0.64135154};
|
||||
|
||||
array_t samples_{0};
|
||||
size_t index_ = 0;
|
||||
float_type accum_ = 0.0;
|
||||
filter_type filter_{makeIirFilter(evm_b, evm_a)};
|
||||
|
||||
|
||||
const float_type ZERO = 0.0;
|
||||
|
||||
FrequencyError()
|
||||
{
|
||||
samples_.fill(0.0);
|
||||
}
|
||||
|
||||
auto operator()(float_type sample)
|
||||
{
|
||||
FloatType evm = 0;
|
||||
bool use = true;
|
||||
|
||||
if (sample > 2)
|
||||
{
|
||||
evm = sample - 3;
|
||||
}
|
||||
else if (sample >= -2)
|
||||
{
|
||||
use = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
evm = sample + 3;
|
||||
}
|
||||
|
||||
if (use)
|
||||
{
|
||||
accum_ = accum_ - samples_[index_] + evm;
|
||||
samples_[index_++] = evm;
|
||||
if (index_ == N) index_ = 0;
|
||||
}
|
||||
|
||||
return filter_(accum_ / N);
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,137 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FirFilter.h"
|
||||
#include "PhaseEstimator.h"
|
||||
#include "DeviationError.h"
|
||||
#include "FrequencyError.h"
|
||||
#include "SymbolEvm.h"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
inline const auto rrc_taps = std::array<double, 79>(
|
||||
-0.009265784007800534, -0.006136551625729697, -0.001125978562075172, 0.004891777252042491,
|
||||
0.01071805138282269, 0.01505751553351295, 0.01679337935001369, 0.015256245142156299,
|
||||
0.01042830577908502, 0.003031522725559901, -0.0055333532968188165, -0.013403099825723372,
|
||||
-0.018598682349642525, -0.01944761739590459, -0.015005271935951746, -0.0053887880354343935,
|
||||
0.008056525910253532, 0.022816244158307273, 0.035513467692208076, 0.04244131815783876,
|
||||
0.04025481153629372, 0.02671818654865632, 0.0013810216516704976, -0.03394615682795165,
|
||||
-0.07502635967975885, -0.11540977897637611, -0.14703962203941534, -0.16119995609538576,
|
||||
-0.14969512896336504, -0.10610329539459686, -0.026921412469634916, 0.08757875030779196,
|
||||
0.23293327870303457, 0.4006012210123992, 0.5786324696325503, 0.7528286479934068,
|
||||
0.908262741447522, 1.0309661131633199, 1.1095611856548013, 1.1366197723675815,
|
||||
1.1095611856548013, 1.0309661131633199, 0.908262741447522, 0.7528286479934068,
|
||||
0.5786324696325503, 0.4006012210123992, 0.23293327870303457, 0.08757875030779196,
|
||||
-0.026921412469634916, -0.10610329539459686, -0.14969512896336504, -0.16119995609538576,
|
||||
-0.14703962203941534, -0.11540977897637611, -0.07502635967975885, -0.03394615682795165,
|
||||
0.0013810216516704976, 0.02671818654865632, 0.04025481153629372, 0.04244131815783876,
|
||||
0.035513467692208076, 0.022816244158307273, 0.008056525910253532, -0.0053887880354343935,
|
||||
-0.015005271935951746, -0.01944761739590459, -0.018598682349642525, -0.013403099825723372,
|
||||
-0.0055333532968188165, 0.003031522725559901, 0.01042830577908502, 0.015256245142156299,
|
||||
0.01679337935001369, 0.01505751553351295, 0.01071805138282269, 0.004891777252042491,
|
||||
-0.001125978562075172, -0.006136551625729697, -0.009265784007800534
|
||||
);
|
||||
|
||||
inline const auto evm_b = std::array<double, 3>(0.02008337, 0.04016673, 0.02008337);
|
||||
inline const auto evm_a = std::array<double, 3>(1.0, -1.56101808, 0.64135154);
|
||||
} // detail
|
||||
|
||||
struct Fsk4Demod
|
||||
{
|
||||
using demod_result_t = std::tuple<double, double, int, double>;
|
||||
using result_t = std::optional<std::tuple<double, double, int, double, double, double, double>>;
|
||||
|
||||
BaseFirFilter<double, std::tuple_size<decltype(detail::rrc_taps)>::value> rrc = makeFirFilter(detail::rrc_taps);
|
||||
PhaseEstimator<double> phase = PhaseEstimator<double>(48000, 4800);
|
||||
DeviationError<double> deviation;
|
||||
FrequencyError<double, 32> frequency;
|
||||
SymbolEvm<double, std::tuple_size<decltype(detail::evm_b)>::value> symbol_evm = makeSymbolEvm(makeIirFilter(detail::evm_b, detail::evm_a));
|
||||
|
||||
double sample_rate = 48000;
|
||||
double symbol_rate = 4800;
|
||||
double unlock_gain = 0.02;
|
||||
double lock_gain = 0.001;
|
||||
std::array<double, 3> samples{0};
|
||||
double t = 0;
|
||||
double dt = symbol_rate / sample_rate;
|
||||
double ideal_dt = dt;
|
||||
bool sample_now = false;
|
||||
double estimated_deviation = 1.0;
|
||||
double estimated_frequency_offset = 0.0;
|
||||
double evm_average = 0.0;
|
||||
|
||||
Fsk4Demod(double sample_rate, double symbol_rate, double unlock_gain = 0.02, double lock_gain = 0.001)
|
||||
: sample_rate(sample_rate)
|
||||
, symbol_rate(symbol_rate)
|
||||
, unlock_gain(unlock_gain * symbol_rate / sample_rate)
|
||||
, lock_gain(lock_gain * symbol_rate / sample_rate)
|
||||
, dt(symbol_rate / sample_rate)
|
||||
, ideal_dt(dt)
|
||||
{
|
||||
samples.fill(0.0);
|
||||
}
|
||||
|
||||
demod_result_t demod(bool lock)
|
||||
{
|
||||
estimated_deviation = deviation(samples[1]);
|
||||
for (auto& sample : samples) sample *= estimated_deviation;
|
||||
|
||||
estimated_frequency_offset = frequency(samples[1]);
|
||||
for (auto& sample : samples) sample -= estimated_frequency_offset;
|
||||
|
||||
auto phase_estimate = phase(samples);
|
||||
if (samples[1] < 0) phase_estimate *= -1;
|
||||
|
||||
dt = ideal_dt - (phase_estimate * (lock ? lock_gain : unlock_gain));
|
||||
t += dt;
|
||||
|
||||
auto [symbol, evm] = symbol_evm(samples[1]);
|
||||
evm_average = symbol_evm.evm();
|
||||
samples[0] = samples[2];
|
||||
|
||||
return std::make_tuple(samples[1], phase_estimate, symbol, evm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the sample. If a symbol is ready, return a tuple
|
||||
* containing the sample used, the estimated phase, the decoded
|
||||
* symbol, the EVM, the deviation error and the frequency error
|
||||
* (sample, phase, symbol, evm, ed, ef), otherwise None.
|
||||
*/
|
||||
result_t operator()(double sample, bool lock)
|
||||
{
|
||||
auto filtered_sample = rrc(sample);
|
||||
|
||||
if (sample_now)
|
||||
{
|
||||
samples[2] = filtered_sample;
|
||||
sample_now = false;
|
||||
auto [prev_sample, phase_estimate, symbol, evm] = demod(lock);
|
||||
return std::make_tuple(prev_sample, phase_estimate, symbol, evm, estimated_deviation, estimated_frequency_offset, evm_average);
|
||||
}
|
||||
|
||||
t += dt;
|
||||
if (t < 1.0)
|
||||
{
|
||||
samples[0] = filtered_sample;
|
||||
}
|
||||
else
|
||||
{
|
||||
t -= 1.0;
|
||||
samples[1] = filtered_sample;
|
||||
sample_now = true;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
struct OPVSynchronizer
|
||||
{
|
||||
uint16_t expected_;
|
||||
int allowable_errors_;
|
||||
uint16_t buffer_ = 0;
|
||||
|
||||
OPVSynchronizer(uint16_t word = 0x3243, int bit_errors = 1)
|
||||
: expected_(word), allowable_errors_(bit_errors)
|
||||
{}
|
||||
|
||||
bool operator()(int bits)
|
||||
{
|
||||
// Add one symbol (2 bits) of data to the synchronizer.
|
||||
// Returns true when a sync word has been detected.
|
||||
|
||||
buffer_ = ((buffer_ << 2) | bits) & 0xFFFF;
|
||||
auto tmp = buffer_ ^ expected_;
|
||||
return std::popcount(tmp) <= allowable_errors_;
|
||||
}
|
||||
|
||||
void reset() { buffer_ = 0; }
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
/**
|
||||
* Estimate the phase of a sample by estimating the
|
||||
* tangent of the sample point. This is done by computing
|
||||
* the magnitude difference of the previous and following
|
||||
* samples. We do not correct for 0-crossing errors because
|
||||
* these errors have not affected the performance of clock
|
||||
* recovery.
|
||||
*/
|
||||
template <typename FloatType>
|
||||
struct PhaseEstimator
|
||||
{
|
||||
using float_type = FloatType;
|
||||
using samples_t = std::array<FloatType, 3>; // 3 samples in length
|
||||
|
||||
float_type dx_;
|
||||
|
||||
PhaseEstimator(FloatType sample_rate, FloatType symbol_rate)
|
||||
: dx_(2.0 * symbol_rate / sample_rate)
|
||||
{}
|
||||
|
||||
/**
|
||||
* This performs a rolling estimate of the phase.
|
||||
*
|
||||
* @param samples are three samples centered around the current sample point
|
||||
* (t-1, t, t+1).
|
||||
*/
|
||||
float_type operator()(const samples_t& samples)
|
||||
{
|
||||
assert(dx_ > 0.0);
|
||||
|
||||
auto ratio = ((samples.at(2) - samples.at(0)) / 3.0) / dx_;
|
||||
// Clamp +/-5.
|
||||
ratio = std::min(FloatType(5.0), ratio);
|
||||
ratio = std::max(FloatType(-5.0), ratio);
|
||||
|
||||
return ratio;
|
||||
}
|
||||
};
|
||||
|
||||
} // mobilinkd
|
|
@ -1,83 +0,0 @@
|
|||
// Copyright 2020 Mobilinkd LLC.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IirFilter.h"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace mobilinkd
|
||||
{
|
||||
|
||||
template <typename FloatType, size_t N>
|
||||
struct SymbolEvm
|
||||
{
|
||||
using filter_type = BaseIirFilter<FloatType, N>;
|
||||
using symbol_t = int;
|
||||
using result_type = std::tuple<symbol_t, FloatType>;
|
||||
|
||||
filter_type filter_;
|
||||
std::optional<FloatType> erasure_limit_;
|
||||
FloatType evm_ = 0.0;
|
||||
|
||||
SymbolEvm(filter_type&& filter, std::optional<FloatType> erasure_limit = std::nullopt)
|
||||
: filter_(std::forward<filter_type>(filter))
|
||||
, erasure_limit_(erasure_limit)
|
||||
{}
|
||||
|
||||
FloatType evm() const { return evm_; }
|
||||
|
||||
/**
|
||||
* Decode a normalized sample into a symbol. Symbols
|
||||
* are decoded into +3, +1, -1, -3. If an erasure limit
|
||||
* is set, symbols outside this limit are 'erased' and
|
||||
* returned as 0.
|
||||
*/
|
||||
result_type operator()(FloatType sample)
|
||||
{
|
||||
symbol_t symbol;
|
||||
FloatType evm;
|
||||
|
||||
sample = std::min(3.0, std::max(-3.0, sample));
|
||||
|
||||
if (sample > 2)
|
||||
{
|
||||
symbol = 3;
|
||||
evm = (sample - 3) * 0.333333;
|
||||
}
|
||||
else if (sample > 0)
|
||||
{
|
||||
symbol = 1;
|
||||
evm = sample - 1;
|
||||
}
|
||||
else if (sample >= -2)
|
||||
{
|
||||
symbol = -1;
|
||||
evm = sample + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
symbol = -3;
|
||||
evm = (sample + 3) * 0.333333;
|
||||
}
|
||||
|
||||
if (erasure_limit_ and (abs(evm) > *erasure_limit_)) symbol = 0;
|
||||
|
||||
evm_ = filter_(evm);
|
||||
|
||||
return std::make_tuple(symbol, evm);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FloatType, size_t N>
|
||||
SymbolEvm<FloatType, N> makeSymbolEvm(
|
||||
BaseIirFilter<FloatType, N>&& filter, std::optional<FloatType> erasure_limit = std::nullopt)
|
||||
{
|
||||
return std::move(SymbolEvm<FloatType, N>(std::move(filter), erasure_limit));
|
||||
}
|
||||
|
||||
} // mobilinkd
|
Ładowanie…
Reference in New Issue