test: the noise probe reads the de-noise node, and the math probe builds one
The probe gains a staged chain rather than one phase, and the schedule moves with it: `UDPSCOPE_PROBE_NOISE_MS` now times the *setup* stage, which installs the de-noise graph through `apply_math_channels()` -- a structural rebuild that replaces the buffer and drops its history -- five seconds before anything scans it. The legs and the five measurements then run at about 10.5 s, and the reading that says the median rejected every impulse at 15.5 s, so the documented run needs a 20 s mock and an 18 s exit. The de-noise channel is installed **disabled** and enabled only at 13 s, for its own reading. An acquired channel is a share of the ring, so a fifth one shortens every channel's retained span -- and the six `BufferSearch` leg counts and `pkpk bare` are all peaks and counts *of that window*. Installing it enabled (as the plan had it) moved `bare` from 49 to 21, `ratio` from 7.00 to 3.00 and `pkpk bare` from 2.988 to 2.488, i.e. it would have rewritten the older feature's documented proof to record a regression that is not one. With it enabled only at the end, every reading from Tasks 4 and 8 is bit-for-bit what the doc records, and the new line reads `denoise raw=2.987696/1 denoised=2.000000/1 ratio=0.669 channels=1` -- `denoised` exactly the clean sine's 2.0, which is the claim the node makes. Two other findings, both folded in: a derived channel whose source is switched off through the panel's checkbox follows it off and does *not* come back when the source returns (the enable-all probe's last act switches `sine` off at ~3 s), and the probes reload leftover math channels from `$XDG_CONFIG_HOME/udpscope`, which is why a `setup channels=` count above 1 means an earlier run's session, not a defect. `math_probe` gains a third channel with a `DeNoise` node so the editor's median row is built and synced on a baseline run: `3 math channels on 'sine' and 'noise'`, every other line as before, `math cost: 103.3 ns/slot` (one reading of the same path). verification.md: the recipe, and why the two stages may not be collapsed -- a probe that rebuilt the buffer under its own legs reads `bare=0` and looks like a broken comparator. Also that the noise and math probes may not share a run: both write `app.math`.
This commit is contained in:
1 parent
5c2c221777
commit
c38630643d
6 files changed
+181
-21
No files matched your search
+4
-1
@@ -5423,7 +5423,10 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), drop_probe, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_NOISE_MS")) {
|
||||
g_timeout_add(static_cast<guint>(std::atoi(env)), noise_probe, &app);
|
||||
const int ms = std::atoi(env);
|
||||
// Times the *setup* stage; `noise_probe` runs 5000 ms after it, so 3000 puts the legs at the same
|
||||
// 8000 ms they were measured at.
|
||||
if (ms > 0) g_timeout_add(static_cast<guint>(ms), noise_probe_setup, &app);
|
||||
}
|
||||
if (const char* env = g_getenv("UDPSCOPE_PROBE_ROWMENU_MS")) {
|
||||
rowmenu_probe_next(app, 0, static_cast<guint>(std::atoi(env)));
|
||||
|
||||
@@ -1422,6 +1422,9 @@ gboolean events_group_probe_step(gpointer user);
|
||||
void events_group_probe_next(App& a, int step, guint delay_ms);
|
||||
gboolean drop_probe(gpointer user);
|
||||
gboolean noise_probe(gpointer user);
|
||||
gboolean noise_probe_setup(gpointer user);
|
||||
gboolean noise_probe_math(gpointer user);
|
||||
gboolean noise_probe_denoise(gpointer user);
|
||||
void collect_spins(GtkWidget* w, std::vector<double>* out);
|
||||
gboolean defaults_probe(gpointer user);
|
||||
gboolean keys_probe(gpointer user);
|
||||
|
||||
+103
-1
@@ -326,11 +326,28 @@ gboolean math_probe(gpointer user) {
|
||||
lp.output = 1;
|
||||
channels.push_back(lp);
|
||||
|
||||
// A third channel so the editor's new form row is built and synced on a baseline run. Assigned field
|
||||
// by field: a partial brace-initialiser would add a fifth build warning.
|
||||
MathChannel dn;
|
||||
dn.name = "math denoise";
|
||||
dn.enabled = true;
|
||||
MathNode dn_src;
|
||||
dn_src.op = MathOp::Source;
|
||||
dn_src.source = name;
|
||||
MathNode dn_op;
|
||||
dn_op.op = MathOp::DeNoise;
|
||||
dn_op.in_a = 0;
|
||||
dn_op.med_n = 5;
|
||||
dn_op.fc = 500.0;
|
||||
dn.nodes = {dn_src, dn_op};
|
||||
dn.output = 1;
|
||||
channels.push_back(dn);
|
||||
|
||||
// Through the app's list, which is the pane's source of truth: a probe that wrote the model
|
||||
// directly left the two disagreeing, and the next pane edit then silently dropped its channels.
|
||||
a->math = std::move(channels);
|
||||
apply_math_channels(*a);
|
||||
std::fprintf(stderr, "[math-probe] 2 math channels on '%s' and '%s'\n", name.c_str(),
|
||||
std::fprintf(stderr, "[math-probe] 3 math channels on '%s' and '%s'\n", name.c_str(),
|
||||
late_name.c_str());
|
||||
// And open the editor on the first one: as much a construction crash test as a state check,
|
||||
// since a modal window is presented and its canvas, form and gestures are all built here.
|
||||
@@ -3503,6 +3520,49 @@ int noise_channel(App* a, const char* name) {
|
||||
|
||||
} // namespace
|
||||
|
||||
// Stage one of three. `apply_math_channels()` is a structural rebuild that drops the buffer's
|
||||
// history, and every reading in `noise_probe` is taken over retained history -- so the rebuild
|
||||
// happens here, five seconds before the first leg runs, and the buffer refills in between. The
|
||||
// de-noise channel also needs that time: a math channel starts producing when it is applied.
|
||||
//
|
||||
// The channel is installed **disabled**, and `noise_probe_math` enables it two and a half seconds
|
||||
// before its own reading. An acquired channel is a share of the ring, so a fifth one shortens every
|
||||
// channel's retained span -- and that moves every figure above it: the six search legs' counts scale
|
||||
// with the retained buffer, and `pkpk bare` is a peak-to-peak *of that window*. Those readings are
|
||||
// the older feature's proof, so the new channel stays out of the acquisition plan until they have
|
||||
// all run, and only the de-noise comparison is taken with it in.
|
||||
//
|
||||
// Fields assigned rather than brace-initialised: a partial brace-initialiser on `MathNode` is
|
||||
// what the two `-Wmissing-field-initializers` in this file already are, and the build baseline
|
||||
// is exactly four warnings.
|
||||
gboolean noise_probe_setup(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
MathChannel dn;
|
||||
dn.name = "math denoise";
|
||||
dn.enabled = false; // enabled by `noise_probe_math`, so it costs no ring while the legs run
|
||||
MathNode src_node;
|
||||
src_node.op = MathOp::Source;
|
||||
src_node.source = "sine";
|
||||
MathNode dn_node;
|
||||
dn_node.op = MathOp::DeNoise;
|
||||
dn_node.in_a = 0;
|
||||
dn_node.med_n = 5;
|
||||
// The median alone: a 2-sample impulse is shorter than half a 5-sample window, so it is rejected
|
||||
// outright and the pole has nothing left to do. A corner here would attenuate the 5 Hz sine as well
|
||||
// and make the peak-to-peak reading a function of two things.
|
||||
dn_node.fc = 0.0;
|
||||
dn.nodes = {src_node, dn_node};
|
||||
dn.output = 1;
|
||||
a->math.push_back(dn);
|
||||
apply_math_channels(*a);
|
||||
std::fprintf(stderr, "[noise-probe] setup channels=%zu\n", a->math.size());
|
||||
std::fflush(stderr);
|
||||
// 3300 + 4700 = 8000 ms, where Tasks 4 and 8 measured the legs.
|
||||
g_timeout_add(5000, noise_probe, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean noise_probe(gpointer user) {
|
||||
static int phase = 0;
|
||||
App* a = static_cast<App*>(user);
|
||||
@@ -3621,6 +3681,48 @@ gboolean noise_probe(gpointer user) {
|
||||
std::fflush(stderr);
|
||||
a->search.clear();
|
||||
update_search_panel(*a);
|
||||
g_timeout_add(2500, noise_probe_math, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean noise_probe_math(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
{
|
||||
// Enabled here for the reason `noise_probe_setup` gives: until this moment the channel must not
|
||||
// be a share of the ring. Enabled *by name*, through the same switch the panel drives, so the
|
||||
// model and the panel agree.
|
||||
const std::vector<SignalInfo>& signals = a->model->signals();
|
||||
for (std::size_t i = 0; i < signals.size(); ++i) {
|
||||
if (signals[i].name == "math denoise") a->model->set_signal_enabled(i, true);
|
||||
}
|
||||
}
|
||||
// A fill before the reading: the channel's first sample is the first one after this enable, and a
|
||||
// peak-to-peak needs a window with a sine trough *and* a crest in it. Two and a half seconds is
|
||||
// twelve cycles of the 5 Hz source.
|
||||
g_timeout_add(2500, noise_probe_denoise, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean noise_probe_denoise(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
const std::size_t base = a->measures.size();
|
||||
// Both unfiltered and at no band: the only difference between the two readings is the node.
|
||||
MeasureOptions options;
|
||||
a->measures.push_back({std::string("sine"), MeasureType::PeakToPeak, options});
|
||||
a->measures.push_back({std::string("math denoise"), MeasureType::PeakToPeak, options});
|
||||
rebuild_measures_pane(*a);
|
||||
update_measures(*a, /*force=*/true, /*accumulate=*/true);
|
||||
const MeasureResult raw =
|
||||
base < a->measure_results.size() ? a->measure_results[base] : MeasureResult{};
|
||||
const MeasureResult den =
|
||||
base + 1 < a->measure_results.size() ? a->measure_results[base + 1] : MeasureResult{};
|
||||
std::fprintf(stderr,
|
||||
"[noise-probe] denoise raw=%.6f/%d denoised=%.6f/%d ratio=%.3f channels=%zu\n",
|
||||
raw.primary, int(raw.valid), den.primary, int(den.valid),
|
||||
raw.primary > 0.0 ? den.primary / raw.primary : 0.0, a->math.size());
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user