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:
1 parent
0876ecfe22
commit
a26770ec32
3 files changed
+198
-41
No files matched your search
+50
-1
@@ -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
|
||||
Reference in new issue
Block a user