diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md
index f962fc3..c7fe8a8 100644
--- a/docs/claude/probe-readings.md
+++ b/docs/claude/probe-readings.md
@@ -1165,14 +1165,15 @@ through one row type, for the reason in `invariants-gtk-and-chrome.md`. The form
## `trigger panel:`
```
-[udpscope] trigger panel: boxes=13 controls_inside=13/13 search_widgets=4/4 search_inside=4/4
+[udpscope] trigger panel: boxes=15 controls_inside=15/15 search_widgets=4/4 search_inside=4/4
```
The trigger panel's frame, read from the window: `boxes` is the row boxes the `.ui` holds, `controls_inside`
-counts the thirteen controls the code built and then appended into those rows, `search_widgets` is the four
+counts the fifteen controls the code built and then appended into those rows -- the last two being the
+noise band and the HF reject, whose rows arrived with the noise-rejection work, `search_widgets` is the four
search buttons having resolved by id, and `search_inside` is those four being inside the block the expander
opens onto. A control built and never placed -- an id that drifted between the file and the code -- reads as
-`controls_inside` below 13 while every other line still looks right, which is the panel that has a field
+`controls_inside` below 15 while every other line still looks right, which is the panel that has a field
missing.
The search walk starts at `gtk_expander_get_child()`, not at the panel: an expander's child is not in the
diff --git a/src/ui.cpp b/src/ui.cpp
index 9594be9..7c57ecd 100644
--- a/src/ui.cpp
+++ b/src/ui.cpp
@@ -5812,7 +5812,7 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
(app.search_run_button != nullptr) + (app.search_cancel_button != nullptr) +
(app.search_prev_button != nullptr) + (app.search_next_button != nullptr);
std::printf(
- "[udpscope] trigger panel: boxes=%d controls_inside=%d/13 search_widgets=%d/4 "
+ "[udpscope] trigger panel: boxes=%d controls_inside=%d/15 search_widgets=%d/4 "
"search_inside=%d/4\n",
boxes,
inside(app.trigger_signal_drop, app.trigger_panel) +
@@ -5827,6 +5827,8 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
inside(app.trigger_level_spin, app.trigger_panel) +
inside(app.trigger_pre_spin, app.trigger_panel) +
inside(app.trigger_holdoff_spin, app.trigger_panel) +
+ inside(app.trigger_band_spin, app.trigger_panel) +
+ inside(app.trigger_hf_spin, app.trigger_panel) +
inside(app.events_record_switch, app.trigger_panel),
search_present,
inside(app.search_run_button, search_root) + inside(app.search_cancel_button, search_root) +
diff --git a/src/ui/trigger-panel.ui b/src/ui/trigger-panel.ui
index 9659bb0..8429fc4 100644
--- a/src/ui/trigger-panel.ui
+++ b/src/ui/trigger-panel.ui
@@ -162,6 +162,30 @@
+
+
+
+
+
+ horizontal
+ 6
+
+
+ HF reject Hz
+ start
+
+
+
+ horizontal
diff --git a/src/ui_app.hpp b/src/ui_app.hpp
index 363d63b..595d81a 100644
--- a/src/ui_app.hpp
+++ b/src/ui_app.hpp
@@ -170,6 +170,8 @@ struct App {
GtkWidget* trigger_level_spin = nullptr;
GtkWidget* trigger_pre_spin = nullptr;
GtkWidget* trigger_holdoff_spin = nullptr;
+ GtkWidget* trigger_band_spin = nullptr; ///< EventCondition::noise_band
+ GtkWidget* trigger_hf_spin = nullptr; ///< EventCondition::hf_fc, 0 is off
GtkWidget* signal_list = nullptr;
GtkWidget* pause_button = nullptr;
/// When the pause began, on the **monotonic wall clock**, or 0 while running. The elapsed time
diff --git a/src/ui_trigger.cpp b/src/ui_trigger.cpp
index 140e0aa..c8a9b72 100644
--- a/src/ui_trigger.cpp
+++ b/src/ui_trigger.cpp
@@ -69,6 +69,8 @@ void publish_trigger_fields(App* app) {
const double width2 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(app->trigger_width2_spin));
const double level2 = gtk_spin_button_get_value(GTK_SPIN_BUTTON(app->trigger_level2_spin));
const double timeout = gtk_spin_button_get_value(GTK_SPIN_BUTTON(app->trigger_timeout_spin));
+ const double band = gtk_spin_button_get_value(GTK_SPIN_BUTTON(app->trigger_band_spin));
+ const double hf = gtk_spin_button_get_value(GTK_SPIN_BUTTON(app->trigger_hf_spin));
{
std::lock_guard lock(app->model->mutex());
TriggerConfig& tc = app->model->trigger();
@@ -85,6 +87,8 @@ void publish_trigger_fields(App* app) {
tc.condition.width2_s = width2;
tc.condition.level2 = level2;
tc.condition.timeout_s = timeout;
+ tc.condition.noise_band = band;
+ tc.condition.hf_fc = hf;
}
}
@@ -110,6 +114,11 @@ void sync_trigger_rows(App& app) {
show(app.trigger_width2_row, pulse && (cmp == WidthCmp::Within || cmp == WidthCmp::Outside));
show(app.trigger_level2_row, window);
show(app.trigger_timeout_row, type == TriggerType::Dropout);
+ // A band widens the comparator every type but Dropout compares with; a dropout is an absence and a
+ // band cannot describe one. The HF reject sits before every comparison, so it applies to all types.
+ // The rows come from the file and hold their spin, so the spin's parent *is* the row.
+ show(gtk_widget_get_parent(app.trigger_band_spin), type != TriggerType::Dropout);
+ show(gtk_widget_get_parent(app.trigger_hf_spin), true);
// A band of zero width reduces the condition to a float-equality test on a real-valued
// signal, which is unusable: it fires on the exact value and nothing else, which in practice
// is never. The panel is where that is refused: the second level is pushed at least one
@@ -282,6 +291,13 @@ GtkWidget* build_trigger_panel(App& app) {
app.trigger_holdoff_spin = spin(0.0, 3600.0, 0.001, 4,
app.model->config().trigger_holdoff_s);
row_for(ui, "holdoff_row", app.trigger_holdoff_spin);
+ // A band in signal units, on the same scale as the level spins.
+ app.trigger_band_spin = spin(0.0, 1.0e12, 0.01, 4, 0.0);
+ row_for(ui, "band_row", app.trigger_band_spin);
+ // 0 is off. The upper bound is generous; `configure()` clamps to just under the stream's Nyquist,
+ // which the panel cannot know.
+ app.trigger_hf_spin = spin(0.0, 1.0e7, 1.0, 3, 0.0);
+ row_for(ui, "hf_row", app.trigger_hf_spin);
const char* modes[] = {"normal", "single", nullptr};
app.trigger_mode_drop = gtk_drop_down_new_from_strings(modes);
@@ -314,7 +330,8 @@ GtkWidget* build_trigger_panel(App& app) {
for (GtkWidget* w : {app.trigger_level_spin, app.trigger_pre_spin, app.trigger_holdoff_spin,
app.trigger_width_spin, app.trigger_width2_spin,
- app.trigger_level2_spin, app.trigger_timeout_spin}) {
+ app.trigger_level2_spin, app.trigger_timeout_spin, app.trigger_band_spin,
+ app.trigger_hf_spin}) {
g_signal_connect(w, "value-changed", G_CALLBACK(on_trigger_field_changed), &app);
}
g_signal_connect(app.trigger_edge_drop, "notify::selected",