feat: a level band and a signal that stops can be the trigger's event too

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-07 18:01:07 +02:00
1 parent 0876ecfe22
commit a26770ec32
3 files changed
+198 -41

No files matched your search

+50 -1
View File
@@ -17,12 +17,23 @@ void EventDetector::reset() noexcept {
in_pulse_ = false;
pulse_up_ = false;
pulse_start_s_ = 0.0;
inside_ = false;
last_cross_s_ = 0.0;
dropped_ = false;
}
double EventDetector::feed(double value, double t) noexcept {
if (!have_prev_) {
prev_ = value;
have_prev_ = true;
// The window condition needs a side to change *from*, and the dropout a last-seen time
// to count from; neither can be derived from a crossing that has not happened. Seeding
// them here is what lets both handlers run without a "have we started" guard.
const double lo = cond_.level < cond_.level2 ? cond_.level : cond_.level2;
const double hi = cond_.level < cond_.level2 ? cond_.level2 : cond_.level;
inside_ = value >= lo && value <= hi;
last_cross_s_ = t;
dropped_ = false;
return kNoEvent;
}
double event = kNoEvent;
@@ -34,8 +45,11 @@ double EventDetector::feed(double value, double t) noexcept {
event = feed_pulse(value, t);
break;
case TriggerType::Window:
event = feed_window(value, t);
break;
case TriggerType::Dropout:
break; // implemented in the tasks that add their panel rows
event = feed_dropout(value, t);
break;
}
// After the handler: every crossing test above reads `prev_` as the previous sample.
prev_ = value;
@@ -98,4 +112,39 @@ double EventDetector::feed_pulse(double value, double t) noexcept {
return qualifies(width) ? t : kNoEvent;
}
double EventDetector::feed_window(double value, double t) noexcept {
const double lo = cond_.level < cond_.level2 ? cond_.level : cond_.level2;
const double hi = cond_.level < cond_.level2 ? cond_.level2 : cond_.level;
const bool inside = value >= lo && value <= hi;
if (inside == inside_) return kNoEvent;
inside_ = inside;
// The event is the *change of side*, so `Rising` reads as "into the band" and `Falling`
// as "out of it". A band of zero width therefore never fires at all, because no sample
// can be on a different side of it than its neighbour: the panel refuses one.
const bool fired = (cond_.edge == TriggerEdge::Rising && inside) ||
(cond_.edge == TriggerEdge::Falling && !inside) ||
cond_.edge == TriggerEdge::Both;
return fired ? t : kNoEvent;
}
double EventDetector::feed_dropout(double value, double t) noexcept {
const bool up = crossed_up(value);
const bool down = crossed_down(value);
const bool crossed = (cond_.edge == TriggerEdge::Rising && up) ||
(cond_.edge == TriggerEdge::Falling && down) ||
(cond_.edge == TriggerEdge::Both && (up || down));
if (crossed) {
last_cross_s_ = t;
dropped_ = false;
return kNoEvent;
}
// One event per silence, not one per sample of it: without the latch a dead signal would
// report an event on every sample after the timeout and fill the search's hit list with
// one answer repeated.
if (dropped_) return kNoEvent;
if (t - last_cross_s_ <= cond_.timeout_s) return kNoEvent;
dropped_ = true;
return t;
}
} // namespace udpscope