mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-01-22 20:51:26 -05:00
POCSAG clock detection 🐋 improvement(?) (#1442)
* WIP convergence * Tighter code, allow for sample nudges during clock discovery.
This commit is contained in:
parent
4926cf8df5
commit
b28283271b
@ -127,41 +127,26 @@ uint32_t BitQueue::data() const {
|
|||||||
|
|
||||||
void BitExtractor::extract_bits(const buffer_f32_t& audio) {
|
void BitExtractor::extract_bits(const buffer_f32_t& audio) {
|
||||||
// Assumes input has been normalized +/- 1.0f.
|
// Assumes input has been normalized +/- 1.0f.
|
||||||
|
// Positive == 0, Negative == 1.
|
||||||
for (size_t i = 0; i < audio.count; ++i) {
|
for (size_t i = 0; i < audio.count; ++i) {
|
||||||
auto sample = audio.p[i];
|
auto sample = audio.p[i];
|
||||||
samples_until_next_ -= 1;
|
|
||||||
|
|
||||||
if (!current_rate_) {
|
if (current_rate_) {
|
||||||
// Feed the known rate queues for clock detection.
|
if (current_rate_->handle_sample(sample)) {
|
||||||
|
auto value = (current_rate_->bits.data() & 1) == 1;
|
||||||
|
bits_.push(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Feed sample to all known rates for clock detection.
|
||||||
for (auto& rate : known_rates_) {
|
for (auto& rate : known_rates_) {
|
||||||
if (handle_sample(rate, sample) &&
|
if (rate.handle_sample(sample) &&
|
||||||
diff_bit_count(rate.bits.data(), clock_magic_number) <= 2) {
|
diff_bit_count(rate.bits.data(), clock_magic_number) <= 3) {
|
||||||
// Clock detected.
|
// Clock detected, continue with this rate.
|
||||||
// NB: This block should only happen on the second sample of a pulse.
|
rate.is_stable = true;
|
||||||
// samples_until_next_ to start sampling the *next* pulse.
|
|
||||||
current_rate_ = &rate;
|
current_rate_ = &rate;
|
||||||
samples_until_next_ = rate.sample_interval;
|
|
||||||
ready_to_send_ = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have a clock rate and it's time to process the next sample.
|
|
||||||
if (current_rate_ && samples_until_next_ <= 0) {
|
|
||||||
// TODO: It seems like it would be possible to combine this
|
|
||||||
// code with handle_sample. Nearly the same work.
|
|
||||||
|
|
||||||
// Only send on the second sample of a bit.
|
|
||||||
// Sampling twice helps mitigate noisy audio data.
|
|
||||||
if (ready_to_send_) {
|
|
||||||
auto value = (prev_sample_ + sample) / 2;
|
|
||||||
bits_.push(signbit(value)); // NB: negative == '1'
|
|
||||||
}
|
|
||||||
|
|
||||||
ready_to_send_ = !ready_to_send_;
|
|
||||||
prev_sample_ = sample;
|
|
||||||
samples_until_next_ += current_rate_->sample_interval;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,39 +162,60 @@ void BitExtractor::configure(uint32_t sample_rate) {
|
|||||||
|
|
||||||
void BitExtractor::reset() {
|
void BitExtractor::reset() {
|
||||||
current_rate_ = nullptr;
|
current_rate_ = nullptr;
|
||||||
samples_until_next_ = 0.0;
|
|
||||||
prev_sample_ = 0.0;
|
|
||||||
ready_to_send_ = false;
|
|
||||||
|
|
||||||
for (auto& rate : known_rates_) {
|
for (auto& rate : known_rates_)
|
||||||
rate.samples_until_next = 0.0;
|
rate.reset();
|
||||||
rate.last_sample = 0.0;
|
|
||||||
rate.bits.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t BitExtractor::baud_rate() const {
|
uint16_t BitExtractor::baud_rate() const {
|
||||||
return current_rate_ ? current_rate_->baud_rate : 0;
|
return current_rate_ ? current_rate_->baud_rate : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BitExtractor::handle_sample(RateInfo& rate, float sample) {
|
bool BitExtractor::RateInfo::handle_sample(float sample) {
|
||||||
// TODO: Still getting some clock misses at the start of messages.
|
samples_until_next -= 1;
|
||||||
rate.samples_until_next -= 1;
|
|
||||||
|
|
||||||
// Not time to process a sample yet.
|
// Time to process a sample?
|
||||||
if (rate.samples_until_next > 0)
|
if (samples_until_next > 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Sample signs are the same, both samples are in the same bit pulse.
|
bool value = signbit(sample); // NB: negative == '1'
|
||||||
auto has_new_bit = signbit(sample) == signbit(rate.last_sample);
|
bool bit_pushed = false;
|
||||||
if (has_new_bit)
|
|
||||||
rate.bits.push(signbit(sample)); // NB: negative == '1'
|
switch (state) {
|
||||||
|
case State::WaitForSample:
|
||||||
|
// Just need to wait for the first sample of the bit.
|
||||||
|
state = State::ReadyToSend;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::ReadyToSend:
|
||||||
|
if (!is_stable && prev_value != value) {
|
||||||
|
// Still looking for the clock signal but found a transition.
|
||||||
|
// Nudge the next sample a bit to try avoiding pulse edges.
|
||||||
|
samples_until_next += (sample_interval / 8.0);
|
||||||
|
} else {
|
||||||
|
// Either the clock has been found or both samples were
|
||||||
|
// (probably) in the same pulse. Send the bit.
|
||||||
|
// TODO: Wider/more samples for noise reduction?
|
||||||
|
state = State::WaitForSample;
|
||||||
|
bit_pushed = true;
|
||||||
|
bits.push(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// How long until the next sample?
|
// How long until the next sample?
|
||||||
rate.samples_until_next += rate.sample_interval;
|
samples_until_next += sample_interval;
|
||||||
rate.last_sample = sample;
|
prev_value = value;
|
||||||
|
|
||||||
return has_new_bit;
|
return bit_pushed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitExtractor::RateInfo::reset() {
|
||||||
|
state = State::WaitForSample;
|
||||||
|
samples_until_next = 0.0;
|
||||||
|
prev_value = false;
|
||||||
|
is_stable = false;
|
||||||
|
bits.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CodewordExtractor *************************************/
|
/* CodewordExtractor *************************************/
|
||||||
|
@ -91,17 +91,25 @@ class BitExtractor {
|
|||||||
static constexpr uint32_t clock_magic_number = 0xAAAAAAAA;
|
static constexpr uint32_t clock_magic_number = 0xAAAAAAAA;
|
||||||
|
|
||||||
struct RateInfo {
|
struct RateInfo {
|
||||||
|
enum class State : uint8_t {
|
||||||
|
WaitForSample,
|
||||||
|
ReadyToSend
|
||||||
|
};
|
||||||
|
|
||||||
const int16_t baud_rate = 0;
|
const int16_t baud_rate = 0;
|
||||||
float sample_interval = 0.0;
|
float sample_interval = 0.0;
|
||||||
|
|
||||||
|
State state = State::WaitForSample;
|
||||||
float samples_until_next = 0.0;
|
float samples_until_next = 0.0;
|
||||||
float last_sample = 0.0;
|
bool prev_value = false;
|
||||||
|
bool is_stable = false;
|
||||||
BitQueue bits{};
|
BitQueue bits{};
|
||||||
};
|
|
||||||
|
|
||||||
/* Updates a rate info with the given sample.
|
/* Updates a rate info with the given sample.
|
||||||
* Returns true if the rate info has a new bit in its queue. */
|
* Returns true if the rate info has a new bit in its queue. */
|
||||||
bool handle_sample(RateInfo& rate, float sample);
|
bool handle_sample(float sample);
|
||||||
|
void reset();
|
||||||
|
};
|
||||||
|
|
||||||
std::array<RateInfo, 3> known_rates_{
|
std::array<RateInfo, 3> known_rates_{
|
||||||
RateInfo{512},
|
RateInfo{512},
|
||||||
@ -112,10 +120,6 @@ class BitExtractor {
|
|||||||
|
|
||||||
uint32_t sample_rate_ = 0;
|
uint32_t sample_rate_ = 0;
|
||||||
RateInfo* current_rate_ = nullptr;
|
RateInfo* current_rate_ = nullptr;
|
||||||
|
|
||||||
float samples_until_next_ = 0.0;
|
|
||||||
float prev_sample_ = 0.0;
|
|
||||||
bool ready_to_send_ = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Extracts codeword batches from the BitQueue. */
|
/* Extracts codeword batches from the BitQueue. */
|
||||||
|
Loading…
Reference in New Issue
Block a user