ui: a measurement's noise band and HF reject are settable and persisted
Two rows in the measure dialog, built the way the threshold and side rows are and appended
beside them: the band as a percentage of the window's own peak-to-peak (the unit the engine
takes), the corner in Hz with 0 off. They are seeded when editing, shown only where they do
something -- the band for the crossing types, since it moves the edge decision and nothing
else, the reject for all of them, since it sits before both walks -- and read into
`MeasureOptions` on apply.
They persist as two *named* keys, `noise` and `hf`, on the same `o <<` chain as the keys
beside them and read back through the same keyed reader, so a session written before this
change loads with 10.0 and 0.0: today's behaviour exactly, with no version check.
[configs] now proves the round trip rather than assuming it. The probe sets the first
measurement's band and corner to 7.5 and 250 before saving, so a key that never reached the
file reads back as the defaults:
[configs] round trip read=1 comment='written by the configs probe' ... measures=4 math=0 noise=7.5 hf=250
The rest of that line is unchanged, and the run is free of Gtk-CRITICAL.
This commit is contained in:
1 parent
380ddb9a77
commit
84aebf4116
6 files changed
+60
-4
No files matched your search
@@ -851,9 +851,13 @@ popover row reads `failed: <strerror>` — one stderr line, no retry storm again
|
||||
[defaults] calibrated: 2.5 -1.25 model=2.5 -1.25
|
||||
[configs] before=default active=default
|
||||
[configs] save-as=1 names=probe,default active=probe
|
||||
[configs] round trip read=1 comment='written by the configs probe' buffer_mb=8 relative=0 capture=0 plots=1 signals=4 measures=0 math=0
|
||||
[configs] round trip read=1 comment='written by the configs probe' buffer_mb=8 relative=0 capture=0 plots=1 signals=4 measures=0 math=0 noise=-1 hf=-1
|
||||
[configs] after forget names=default
|
||||
```
|
||||
`noise=` and `hf=` are the first measurement's band and corner as they came back from the file. The probe
|
||||
sets them to **7.5 and 250** before saving, so a key that never reached the file reads back as the defaults
|
||||
(10 and 0) instead -- which is the only way a round trip can tell a written key from an absent one. They read
|
||||
`-1` when the saved configuration holds no measurement, which is what the run above does.
|
||||
|
||||
| field | value it must read | the defect a wrong reading names |
|
||||
| --- | --- | --- |
|
||||
|
||||
+12
-1
@@ -556,6 +556,16 @@ bool load_layout(const std::string& path, LayoutState& out) {
|
||||
m.options.threshold_pct = std::stod(value);
|
||||
} catch (...) {
|
||||
}
|
||||
} else if (key == "noise") {
|
||||
try {
|
||||
m.options.noise_pct = std::stod(value);
|
||||
} catch (...) {
|
||||
}
|
||||
} else if (key == "hf") {
|
||||
try {
|
||||
m.options.hf_fc = std::stod(value);
|
||||
} catch (...) {
|
||||
}
|
||||
} else if (key == "above") {
|
||||
m.options.above = value == "true";
|
||||
} else if (key == "show") {
|
||||
@@ -668,7 +678,8 @@ bool save_layout(const std::string& path, const LayoutState& state) {
|
||||
o << "\n[[measure]]\nsignal = " << toml_escape(m.signal) << "\ntype = " << kind
|
||||
<< "\nthreshold = " << m.options.threshold_pct
|
||||
<< "\nabove = " << (m.options.above ? "true" : "false") << "\nshow = "
|
||||
<< (m.options.show_on_plot ? "true" : "false") << "\n";
|
||||
<< (m.options.show_on_plot ? "true" : "false") << "\nnoise = " << m.options.noise_pct
|
||||
<< "\nhf = " << m.options.hf_fc << "\n";
|
||||
}
|
||||
|
||||
// A sibling temp file + rename: a crash mid-write leaves the old layout, not a
|
||||
|
||||
+19
@@ -1678,6 +1678,22 @@ void open_measure_dialog(App& app, int edit_index) {
|
||||
GtkWidget* side_row = labelled_row("pulse above level", above, &side_label);
|
||||
gtk_box_append(GTK_BOX(box), side_row);
|
||||
|
||||
// A percentage of the window's own peak-to-peak, which is the unit the engine takes.
|
||||
GtkWidget* noise = spin(0.0, 50.0, 1.0, 1, 10.0);
|
||||
// `labelled_row()` writes the label out unconditionally, and these two never retitle, so the
|
||||
// out-parameters are named and then explicitly unused rather than passed as nullptr.
|
||||
GtkWidget* noise_label = nullptr;
|
||||
GtkWidget* noise_row = labelled_row("noise band %", noise, &noise_label);
|
||||
gtk_box_append(GTK_BOX(box), noise_row);
|
||||
(void)noise_label;
|
||||
|
||||
// 0 is off. The engine clamps to just under the stream's Nyquist, which the dialog cannot know.
|
||||
GtkWidget* hf = spin(0.0, 1.0e7, 1.0, 3, 0.0);
|
||||
GtkWidget* hf_label = nullptr;
|
||||
GtkWidget* hf_row = labelled_row("HF reject Hz", hf, &hf_label);
|
||||
gtk_box_append(GTK_BOX(box), hf_row);
|
||||
(void)hf_label;
|
||||
|
||||
GtkWidget* show = gtk_check_button_new_with_label("show on plot");
|
||||
gtk_widget_set_tooltip_text(show,
|
||||
"draw the result on the plot: a ruler at the level, a marker "
|
||||
@@ -1687,6 +1703,7 @@ void open_measure_dialog(App& app, int edit_index) {
|
||||
auto* d = new MeasureDialog{&app, edit_index, signal_drop, type_drop,
|
||||
threshold, above, show, window,
|
||||
threshold_row, threshold_label, side_row, side_label,
|
||||
noise, noise_row, hf, hf_row,
|
||||
sig_names};
|
||||
app.measure_dialog = window;
|
||||
g_signal_connect(window, "destroy", G_CALLBACK(+[](GtkWidget*, gpointer user) {
|
||||
@@ -1709,6 +1726,8 @@ void open_measure_dialog(App& app, int edit_index) {
|
||||
}
|
||||
}
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(threshold), editing->options.threshold_pct);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(noise), editing->options.noise_pct);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(hf), editing->options.hf_fc);
|
||||
gtk_switch_set_active(GTK_SWITCH(above), editing->options.above);
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(show), editing->options.show_on_plot);
|
||||
} else {
|
||||
|
||||
@@ -910,6 +910,12 @@ struct MeasureDialog {
|
||||
GtkWidget* threshold_label = nullptr;
|
||||
GtkWidget* side_row = nullptr;
|
||||
GtkWidget* side_label = nullptr;
|
||||
/// The two noise-rejection rows. `noise` is a percentage of the window's peak-to-peak, `hf` a corner in
|
||||
/// Hz with 0 off; both are shown only where they do something (see `measure_dialog_sync`).
|
||||
GtkWidget* noise = nullptr;
|
||||
GtkWidget* noise_row = nullptr;
|
||||
GtkWidget* hf = nullptr;
|
||||
GtkWidget* hf_row = nullptr;
|
||||
/// The names the signal dropdown was built from, in order. `apply` resolves an index
|
||||
/// through *this*, not through the model's signal list: the dropdown lists the plotted
|
||||
/// traces, so index `i` here is the i-th trace -- indexing the CONFIG with it selected
|
||||
|
||||
@@ -94,6 +94,10 @@ void measure_dialog_sync(MeasureDialog* d) {
|
||||
const bool side = measure_uses_side(type);
|
||||
gtk_widget_set_visible(d->threshold_row, threshold);
|
||||
gtk_widget_set_visible(d->side_row, side);
|
||||
// The band moves the edge decision, so it is shown for exactly the crossing types. The HF reject sits
|
||||
// before both walks, so it is shown for all of them.
|
||||
gtk_widget_set_visible(d->noise_row, measure_uses_noise(type));
|
||||
gtk_widget_set_visible(d->hf_row, TRUE);
|
||||
gtk_widget_set_visible(d->show, measure_can_show_on_plot(type));
|
||||
|
||||
// The words follow the type: one row, several meanings.
|
||||
@@ -166,6 +170,8 @@ void on_measure_dialog_apply(GtkButton*, gpointer user) {
|
||||
m.options.threshold_pct = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->threshold));
|
||||
m.options.above = gtk_switch_get_active(GTK_SWITCH(d->above));
|
||||
m.options.show_on_plot = gtk_check_button_get_active(GTK_CHECK_BUTTON(d->show));
|
||||
m.options.noise_pct = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->noise));
|
||||
m.options.hf_fc = gtk_spin_button_get_value(GTK_SPIN_BUTTON(d->hf));
|
||||
if (d->edit >= 0 && static_cast<std::size_t>(d->edit) < app.measures.size()) {
|
||||
app.measures[static_cast<std::size_t>(d->edit)] = std::move(m);
|
||||
} else {
|
||||
|
||||
+12
-2
@@ -2953,6 +2953,13 @@ gboolean configs_probe(gpointer user) {
|
||||
std::fprintf(stderr, "[configs] before=%s active=%s\n", joined().c_str(),
|
||||
active_configuration(a->source_host, a->source_port).c_str());
|
||||
|
||||
// A non-default band and corner before the save, so the round trip below proves the two keys are
|
||||
// *written*: left at their defaults, a key that never made it to the file would read back the same.
|
||||
const bool has_measure = !a->measures.empty();
|
||||
if (has_measure) {
|
||||
a->measures.front().options.noise_pct = 7.5;
|
||||
a->measures.front().options.hf_fc = 250.0;
|
||||
}
|
||||
const bool saved = save_configuration_as(*a, "probe", "written by the configs probe");
|
||||
std::fprintf(stderr, "[configs] save-as=%d names=%s active=%s\n", saved ? 1 : 0,
|
||||
joined().c_str(), active_configuration(a->source_host, a->source_port).c_str());
|
||||
@@ -2967,11 +2974,14 @@ gboolean configs_probe(gpointer user) {
|
||||
if (!sig.empty()) ++signals;
|
||||
}
|
||||
}
|
||||
const double noise_back = st.measures.empty() ? -1.0 : st.measures.front().options.noise_pct;
|
||||
const double hf_back = st.measures.empty() ? -1.0 : st.measures.front().options.hf_fc;
|
||||
std::fprintf(stderr,
|
||||
"[configs] round trip read=%d comment='%s' buffer_mb=%.6g relative=%d "
|
||||
"capture=%u plots=%zu signals=%d measures=%zu math=%zu\n",
|
||||
"capture=%u plots=%zu signals=%d measures=%zu math=%zu noise=%.6g hf=%.6g\n",
|
||||
read ? 1 : 0, st.comment.c_str(), st.buffer_mb, st.relative_time ? 1 : 0,
|
||||
st.capture_packets, st.rows.size(), signals, st.measures.size(), st.math.size());
|
||||
st.capture_packets, st.rows.size(), signals, st.measures.size(), st.math.size(),
|
||||
noise_back, hf_back);
|
||||
|
||||
forget_configuration(a->source_host, a->source_port, "probe");
|
||||
std::fprintf(stderr, "[configs] after forget names=%s\n", joined().c_str());
|
||||
|
||||
Reference in new issue
Block a user