new do1_noise_subtract logic

This commit is contained in:
awe
2026-04-27 19:54:07 +03:00
parent 8175d1cba0
commit b9559cbe8c

View File

@ -512,11 +512,11 @@ void print_help(const char* exe_name) {
<< " DO1 outputs 00110011... continuously (toggle every 2 ADC ticks)\n" << " DO1 outputs 00110011... continuously (toggle every 2 ADC ticks)\n"
<< " without per-tick DOUT updates from PC\n" << " without per-tick DOUT updates from PC\n"
<< " do1_noise_subtract -> with tty + do1_toggle_per_frame, use DI1 state from synchronous DIN\n" << " do1_noise_subtract -> with tty + do1_toggle_per_frame, use DI1 state from synchronous DIN\n"
<< " (expected DO1->DI1 loopback) to average recent noise steps and subtract it\n" << " (expected DO1->DI1 loopback) to take previous DI1=HIGH step as baseline and subtract it\n"
<< " from useful steps before tty output\n" << " from useful steps before tty output\n"
<< " (only corrected DO1=LOW steps are sent to tty)\n" << " (only corrected DO1=LOW steps are sent to tty)\n"
<< " noise_avg_steps:N -> number of recent DO1=HIGH noise steps per channel used as subtraction baseline\n" << " noise_avg_steps:N -> kept for compatibility in do1_noise_subtract mode (current baseline is previous HIGH step)\n"
<< " (required when do1_noise_subtract is enabled)\n" << " (still required when do1_noise_subtract is enabled)\n"
<< " tty fast stream-only modes -> di1_group_avg or do1_noise_subtract; skip CSV/SVG/live outputs\n" << " tty fast stream-only modes -> di1_group_avg or do1_noise_subtract; skip CSV/SVG/live outputs\n"
<< " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n"
<< "\n" << "\n"
@ -545,7 +545,7 @@ void print_help(const char* exe_name) {
<< "For tty output, use di1_group_avg together with di1:trace to emit one averaged 4-word step\n" << "For tty output, use di1_group_avg together with di1:trace to emit one averaged 4-word step\n"
<< "per constant DI1 run while keeping the current ring-buffered binary frame format.\n" << "per constant DI1 run while keeping the current ring-buffered binary frame format.\n"
<< "For DO1 subtraction with loopback, use do1_toggle_per_frame + do1_noise_subtract + noise_avg_steps:N;\n" << "For DO1 subtraction with loopback, use do1_toggle_per_frame + do1_noise_subtract + noise_avg_steps:N;\n"
<< "DI1 from synchronous DIN is used as the actual DO1 state marker.\n" << "DI1 from synchronous DIN is used as the actual DO1 state marker, and each LOW step subtracts previous HIGH step.\n"
<< "Amplitude mode keeps the raw AD8317 voltage as captured on X1; no inversion is applied.\n" << "Amplitude mode keeps the raw AD8317 voltage as captured on X1; no inversion is applied.\n"
<< "\n" << "\n"
<< "Phase profile example:\n" << "Phase profile example:\n"
@ -1232,22 +1232,16 @@ struct TtyGroupAverageState {
}; };
struct Do1NoiseSubtractState { struct Do1NoiseSubtractState {
uint32_t noise_avg_steps = 0;
std::array<std::vector<double>, 2> noise_ring;
std::array<std::size_t, 2> noise_ring_head {};
std::array<std::size_t, 2> noise_ring_size {};
std::array<double, 2> noise_ring_sum {};
std::array<double, 2> step_sum {}; std::array<double, 2> step_sum {};
std::array<uint32_t, 2> step_count {}; std::array<uint32_t, 2> step_count {};
std::array<double, 2> last_high_avg {};
std::array<bool, 2> last_high_valid {};
bool step_level_initialized = false; bool step_level_initialized = false;
bool step_di1_high = false; bool step_di1_high = false;
uint16_t next_index = 1; uint16_t next_index = 1;
void configure(uint32_t history_steps) { void configure(uint32_t history_steps) {
noise_avg_steps = history_steps; (void) history_steps;
for (auto& ring : noise_ring) {
ring.assign(noise_avg_steps, 0.0);
}
reset_packet(); reset_packet();
} }
@ -1258,12 +1252,11 @@ struct Do1NoiseSubtractState {
void reset_packet() { void reset_packet() {
clear_step(); clear_step();
last_high_avg = {};
last_high_valid = {};
step_level_initialized = false; step_level_initialized = false;
step_di1_high = false; step_di1_high = false;
next_index = 1; next_index = 1;
noise_ring_head = {};
noise_ring_size = {};
noise_ring_sum = {};
} }
void start_new_step(bool di1_high) { void start_new_step(bool di1_high) {
@ -1291,38 +1284,26 @@ struct Do1NoiseSubtractState {
return step_sum[lch] / static_cast<double>(step_count[lch]); return step_sum[lch] / static_cast<double>(step_count[lch]);
} }
void push_noise_average(uint32_t lch, double avg_value) { void update_last_high(uint32_t lch, double avg_value) {
if ((noise_avg_steps == 0U) || (lch >= noise_ring.size())) { if (lch >= last_high_avg.size()) {
return; return;
} }
last_high_avg[lch] = avg_value;
auto& ring = noise_ring[lch]; last_high_valid[lch] = true;
auto& head = noise_ring_head[lch];
auto& size = noise_ring_size[lch];
auto& sum = noise_ring_sum[lch];
if (size < noise_avg_steps) {
ring[size] = avg_value;
sum += avg_value;
++size;
return;
}
sum -= ring[head];
ring[head] = avg_value;
sum += avg_value;
head = (head + 1U) % noise_avg_steps;
} }
double noise_baseline(uint32_t lch) const { bool has_last_high(uint32_t lch) const {
if (lch >= noise_ring_size.size()) { if (lch >= last_high_valid.size()) {
return false;
}
return last_high_valid[lch];
}
double last_high(uint32_t lch) const {
if (lch >= last_high_avg.size()) {
return 0.0; return 0.0;
} }
const std::size_t size = noise_ring_size[lch]; return last_high_avg[lch];
if (size == 0U) {
return 0.0;
}
return noise_ring_sum[lch] / static_cast<double>(size);
} }
void finish_step() { clear_step(); } void finish_step() { clear_step(); }
@ -1885,21 +1866,28 @@ int run(const Config& cfg) {
const double ch2_avg = (cfg.channel_count <= 1U) ? 0.0 : tty_do1_noise_state.step_average(1U); const double ch2_avg = (cfg.channel_count <= 1U) ? 0.0 : tty_do1_noise_state.step_average(1U);
if (tty_do1_noise_state.step_di1_high) { if (tty_do1_noise_state.step_di1_high) {
tty_do1_noise_state.push_noise_average(0U, ch1_avg); tty_do1_noise_state.update_last_high(0U, ch1_avg);
if (cfg.channel_count > 1U) { if (cfg.channel_count > 1U) {
tty_do1_noise_state.push_noise_average(1U, ch2_avg); tty_do1_noise_state.update_last_high(1U, ch2_avg);
} }
tty_do1_noise_state.finish_step(); tty_do1_noise_state.finish_step();
return; return;
} }
if (!tty_do1_noise_state.has_last_high(0U) ||
((cfg.channel_count > 1U) && !tty_do1_noise_state.has_last_high(1U))) {
// Skip LOW output until the previous HIGH step baseline is available.
tty_do1_noise_state.finish_step();
return;
}
if (tty_do1_noise_state.next_index >= 0xFFFFU) { if (tty_do1_noise_state.next_index >= 0xFFFFU) {
fail("TTY protocol step index overflow within packet"); fail("TTY protocol step index overflow within packet");
} }
const double ch1_corrected = ch1_avg - tty_do1_noise_state.noise_baseline(0U); const double ch1_corrected = ch1_avg - tty_do1_noise_state.last_high(0U);
const double ch2_corrected = const double ch2_corrected =
(cfg.channel_count <= 1U) ? 0.0 : (ch2_avg - tty_do1_noise_state.noise_baseline(1U)); (cfg.channel_count <= 1U) ? 0.0 : (ch2_avg - tty_do1_noise_state.last_high(1U));
append_tty_frame((cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU, append_tty_frame((cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU,
tty_do1_noise_state.next_index, tty_do1_noise_state.next_index,