fix: a dropout probe outlasts the sine's own crossings, so only a real silence latches it
- Leg 1: widen to 1.0 s / 50% pre so the square's opening edge is in the snapshot - Leg 2: use sine (not square) for the window channel, edge = falling so the band exit is deterministic, and read earlier from vals[end-2] rather than half the snapshot - Leg 3: raise timeout to 0.3 s (> half-period), accept either crossing direction, widen to 1.0 s / 50% pre to reach the crossing that sits 0.3 s before x=0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
a26770ec32
commit
a00663510f
1 file changed
+49
-25
+49
-25
@@ -8461,16 +8461,25 @@ gboolean fields_probe(gpointer user) {
|
|||||||
/// 10 ms packets), so comparing trigger_time() against a mock-clock target is meaningless.
|
/// 10 ms packets), so comparing trigger_time() against a mock-clock target is meaningless.
|
||||||
///
|
///
|
||||||
/// Shape 0 (pulse `<`): x = 0 is the glitch's closing edge. Scans backwards for the most
|
/// Shape 0 (pulse `<`): x = 0 is the glitch's closing edge. Scans backwards for the most
|
||||||
/// recent upward crossing; `measured = -stamp` is the glitch width (≈ 0.002 s).
|
/// recent upward crossing; `measured = -stamp` is the glitch width (≈ 0.002 s). Window 0.2 s,
|
||||||
|
/// 10% pre (0.02 s back) — the glitch is 0.002 s wide so 0.02 s comfortably contains it.
|
||||||
///
|
///
|
||||||
/// Shape 1 (pulse `>`): same scan; `measured` is the ordinary square half-period (≈ 0.200 s).
|
/// Shape 1 (pulse `>`): same scan; `measured` is the ordinary square half-period (≈ 0.200 s).
|
||||||
|
/// Needs a 1.0 s window with 50% pre (0.5 s) because the opening edge of the captured pulse
|
||||||
|
/// is 0.2 s before x = 0, beyond the 0.02 s pre a 0.2 s window would provide.
|
||||||
///
|
///
|
||||||
/// Shape 2 (window): x = 0 is the moment the signal left the band. Reports `at0` (the
|
/// Shape 2 (window): edge is set to `falling` (out-of-band), so x = 0 is the moment the
|
||||||
/// sample value at x = 0, which must be outside the band) and `earlier` (a sample a few
|
/// signal left the band. Uses the sine channel because the square has no sample inside
|
||||||
/// stored periods back, which must be inside).
|
/// [-0.5, +0.5] and can never produce a band exit. Reports `at0` (the sample at x = 0, which
|
||||||
|
/// must be outside the band) and `earlier` (the sample immediately before it, which must be
|
||||||
|
/// inside). Window 0.2 s, 10% pre — the crossing is one sample before x = 0.
|
||||||
///
|
///
|
||||||
/// Shape 3 (dropout): x = 0 is `quiet_at + timeout`, the moment the timeout expired. Scans
|
/// Shape 3 (dropout): x = 0 is `quiet_at + timeout`, the moment the timeout expired. Scans
|
||||||
/// backwards for the most recent level crossing; `measured = -stamp` should equal `timeout`.
|
/// backwards for the most recent crossing in either direction, because the last sine crossing
|
||||||
|
/// before the quiet period is downward for the `--quiet-at 4.26` case and an upward-only scan
|
||||||
|
/// would miss it. `timeout` must exceed the sine's 0.1 s half-period; 0.3 s is used so that
|
||||||
|
/// an ordinary live sine cannot satisfy the condition. Needs a 1.0 s window with 50% pre
|
||||||
|
/// because the crossing is `timeout` ≈ 0.3 s before x = 0.
|
||||||
///
|
///
|
||||||
/// All legs print one `[condition-probe]` line with `nan` for any unmeasurable field.
|
/// All legs print one `[condition-probe]` line with `nan` for any unmeasurable field.
|
||||||
struct ConditionProbe {
|
struct ConditionProbe {
|
||||||
@@ -8479,9 +8488,8 @@ struct ConditionProbe {
|
|||||||
double square_period = 0.4;
|
double square_period = 0.4;
|
||||||
/// With `--quiet-at`, the sine stops at a level on the other side of zero, so `quiet_at`
|
/// With `--quiet-at`, the sine stops at a level on the other side of zero, so `quiet_at`
|
||||||
/// is its last crossing and the dropout's event time is exactly `quiet_at + timeout`.
|
/// is its last crossing and the dropout's event time is exactly `quiet_at + timeout`.
|
||||||
/// Unlike the glitch this happens *once*, so the assertion is the absolute t0.
|
|
||||||
double quiet_at = -1.0;
|
double quiet_at = -1.0;
|
||||||
double timeout = 0.050;
|
double timeout = 0.300;
|
||||||
/// 0 = pulse width `<` (the glitch), 1 = pulse width `>`, 2 = window, 3 = dropout. Selected
|
/// 0 = pulse width `<` (the glitch), 1 = pulse width `>`, 2 = window, 3 = dropout. Selected
|
||||||
/// by `UDPSCOPE_PROBE_CONDITION_SHAPE`; the default keeps Task 3's glitch leg.
|
/// by `UDPSCOPE_PROBE_CONDITION_SHAPE`; the default keeps Task 3's glitch leg.
|
||||||
int shape = 0;
|
int shape = 0;
|
||||||
@@ -8519,20 +8527,24 @@ gboolean condition_probe_report(gpointer user) {
|
|||||||
const std::size_t end = static_cast<std::size_t>(it - st.begin());
|
const std::size_t end = static_cast<std::size_t>(it - st.begin());
|
||||||
|
|
||||||
if (g_condition.shape == 2) {
|
if (g_condition.shape == 2) {
|
||||||
// Window leg: x=0 is the moment the signal left the band.
|
// Window leg: x=0 is the moment the signal left the band (falling / out-of-band edge).
|
||||||
// `at0` is the sample at x=0 (must be outside [lo,hi]).
|
// `at0` is the sample at x=0: must be outside [lo, hi], i.e. |at0| >= 0.5.
|
||||||
// `earlier` is a sample a few stored periods back (must be inside).
|
// `earlier` is the sample immediately before x=0: must be inside, i.e. |earlier| < 0.5.
|
||||||
|
// Using the adjacent sample makes the assertion exact and independent of snapshot size.
|
||||||
if (end >= 1) at0 = static_cast<double>(vals[end - 1]);
|
if (end >= 1) at0 = static_cast<double>(vals[end - 1]);
|
||||||
// "A few stored periods" back: use half the snapshot length as a rough offset.
|
if (end >= 2) earlier = static_cast<double>(vals[end - 2]);
|
||||||
const std::size_t back = end > 4 ? end / 2 : 0;
|
|
||||||
if (back < vals.size()) earlier = static_cast<double>(vals[back]);
|
|
||||||
} else {
|
} else {
|
||||||
// Pulse and dropout legs: scan backwards for the most recent upward crossing
|
// Pulse legs: scan backwards for the most recent upward crossing
|
||||||
// (prev < level, value >= level) and negate the stamp to get a duration.
|
// (prev < level, value >= level) and negate the stamp to get a duration.
|
||||||
|
// Dropout leg: accept a crossing in either direction because the last sine crossing
|
||||||
|
// before the quiet period may be downward, and an upward-only scan would miss it.
|
||||||
for (std::size_t i = end; i >= 2; --i) {
|
for (std::size_t i = end; i >= 2; --i) {
|
||||||
const double prev = static_cast<double>(vals[i - 2]);
|
const double prev = static_cast<double>(vals[i - 2]);
|
||||||
const double val = static_cast<double>(vals[i - 1]);
|
const double val = static_cast<double>(vals[i - 1]);
|
||||||
if (prev < level && val >= level) {
|
const bool crossed = (g_condition.shape == 3)
|
||||||
|
? ((prev < level) != (val < level)) // either direction
|
||||||
|
: (prev < level && val >= level); // upward only
|
||||||
|
if (crossed) {
|
||||||
measured = -st[i - 1];
|
measured = -st[i - 1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -8554,12 +8566,20 @@ gboolean condition_probe_report(gpointer user) {
|
|||||||
gboolean condition_probe_arm(gpointer user) {
|
gboolean condition_probe_arm(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||||
// A window short enough that the pre-trigger share is already behind the glitch.
|
// Legs 0 and 2 need only a short snapshot (the event is one sample or one glitch-width back).
|
||||||
apply_time_window(*a, 0.2);
|
// Legs 1 and 3 need a 1.0 s window with 50% pre so the opening edge of a 0.2 s pulse and
|
||||||
|
// the last sine crossing before a 0.3 s timeout are both captured.
|
||||||
|
const bool wide = (g_condition.shape == 1 || g_condition.shape == 3);
|
||||||
|
const double win_s = wide ? 1.0 : 0.2;
|
||||||
|
const double pre_pct = wide ? 50.0 : 10.0;
|
||||||
|
apply_time_window(*a, win_s);
|
||||||
const std::vector<ChannelInfo>& channels = a->model->channels();
|
const std::vector<ChannelInfo>& channels = a->model->channels();
|
||||||
int sel = -1;
|
int sel = -1;
|
||||||
const bool dropout = g_condition.shape == 3;
|
const bool dropout = g_condition.shape == 3;
|
||||||
const char* want_name = dropout ? "sine" : "square";
|
// Shape 2 (window) and shape 3 (dropout) both use the sine: the square has no sample inside
|
||||||
|
// [-0.5, +0.5] and cannot produce a band change-of-side, and the dropout relies on the sine
|
||||||
|
// going quiet. Shapes 0 and 1 (pulse-width) use the square.
|
||||||
|
const char* want_name = (g_condition.shape == 2 || dropout) ? "sine" : "square";
|
||||||
for (std::size_t i = 0; i < channels.size(); ++i) {
|
for (std::size_t i = 0; i < channels.size(); ++i) {
|
||||||
if (channels[i].name == want_name) sel = static_cast<int>(i);
|
if (channels[i].name == want_name) sel = static_cast<int>(i);
|
||||||
}
|
}
|
||||||
@@ -8575,10 +8595,13 @@ gboolean condition_probe_arm(gpointer user) {
|
|||||||
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_type_drop), type);
|
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_type_drop), type);
|
||||||
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_cmp_drop),
|
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_cmp_drop),
|
||||||
g_condition.shape == 1 ? 1u : 0u); // `>` for leg 1, `<` otherwise
|
g_condition.shape == 1 ? 1u : 0u); // `>` for leg 1, `<` otherwise
|
||||||
// `both` for the window and the dropout: a band is entered *and* left, and a dropout has no
|
// Window leg uses `falling` (index 1), meaning out-of-band, which makes the latch deterministic:
|
||||||
// direction at all. Rising for the two pulse-width legs, which open on a rising excursion.
|
// x=0 is always a band exit, so at0 is outside and the previous sample is inside.
|
||||||
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_edge_drop),
|
// Dropout has no direction (index 2, `both`). Pulse-width legs use `rising` (index 0).
|
||||||
(dropout || g_condition.shape == 2) ? 2u : 0u);
|
guint edge_idx = 0u;
|
||||||
|
if (g_condition.shape == 2) edge_idx = 1u;
|
||||||
|
else if (dropout) edge_idx = 2u;
|
||||||
|
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_edge_drop), edge_idx);
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_width_spin), 0.010);
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_width_spin), 0.010);
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_timeout_spin), g_condition.timeout);
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_timeout_spin), g_condition.timeout);
|
||||||
// The band for the window leg: straddling zero, well inside the square's +/-1, so each
|
// The band for the window leg: straddling zero, well inside the square's +/-1, so each
|
||||||
@@ -8587,7 +8610,7 @@ gboolean condition_probe_arm(gpointer user) {
|
|||||||
// Zero for every leg but the window, whose band is `[-0.5, +0.5]`.
|
// Zero for every leg but the window, whose band is `[-0.5, +0.5]`.
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_level_spin),
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_level_spin),
|
||||||
g_condition.shape == 2 ? -0.5 : 0.0);
|
g_condition.shape == 2 ? -0.5 : 0.0);
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_pre_spin), 10.0);
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_pre_spin), pre_pct);
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_holdoff_spin), 0.0);
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_holdoff_spin), 0.0);
|
||||||
publish_trigger_fields(a);
|
publish_trigger_fields(a);
|
||||||
if (a->trigger_toggle != nullptr)
|
if (a->trigger_toggle != nullptr)
|
||||||
@@ -8609,8 +8632,9 @@ gboolean condition_probe(gpointer user) {
|
|||||||
g_condition.shape = 3;
|
g_condition.shape = 3;
|
||||||
}
|
}
|
||||||
// `square` is not acquired until it is enabled, and the trigger can only name a channel
|
// `square` is not acquired until it is enabled, and the trigger can only name a channel
|
||||||
// that is: enabling is what starts acquisition.
|
// that is: enabling is what starts acquisition. Shape 2 (window) needs the sine because the
|
||||||
const char* want = g_condition.shape == 3 ? "sine" : "square";
|
// square has no sample inside [-0.5, +0.5] and can never change sides of the band.
|
||||||
|
const char* want = (g_condition.shape == 2 || g_condition.shape == 3) ? "sine" : "square";
|
||||||
const std::vector<SignalInfo>& signals = a->model->signals();
|
const std::vector<SignalInfo>& signals = a->model->signals();
|
||||||
for (std::size_t i = 0; i < signals.size(); ++i) {
|
for (std::size_t i = 0; i < signals.size(); ++i) {
|
||||||
if (signals[i].name == want) a->model->set_signal_enabled(i, true);
|
if (signals[i].name == want) a->model->set_signal_enabled(i, true);
|
||||||
|
|||||||
Reference in new issue
Block a user