perf: the measurements pane keys its shared walks by channel and HF corner

The pane shared its two walks per *channel*, but the HF corner is a per-measurement
setting: two measurements on one signal at different corners read two different signals
and cannot share a walk. `App::MeasureWindow` therefore carries `channel` and `fc`, the
container is a vector, and the grouping loop matches on both -- keeping its dedupe of the
level accumulators by `(pct, noise)`, since a type that does not read the band must not
split an accumulator over it.

Each window carries two filters, one per walk: they are separate walks, so separate state.
A filtered window also stops using the LOD pyramid, which one `if` does -- leaving
`buckets` empty makes `bulk_from == bulk_to == n`, so the raw walk covers the window and
`lo_search`/`hi_search` stay 0, which makes the pinning pass a no-op. Nothing downstream
needs to know.

The readout loop looks its window up by index rather than by channel, which is what the
`(channel, fc)` key needs.

The documented measurement baseline is unchanged, and it is the assertion:

    [measure-trace] average=0.000000 top=0.636620 peak to peak=2.000000 amplitude=1.000000
    [udpscope] measure cost: ... covered=75378013 buckets=1177164 raw=159674 (0.2%)

`top` is 2/pi to the digit -- the one reading a band leaking into the sums would move --
and `raw` at 0.2 % of `covered` says the fast path is still being used on an unfiltered
window.

The measure probe now enables `sine` itself, as the noise probe does: `enable_all_probe`
ends by switching a signal off, and with `sine` gone the probe measured `square`, where
`top` is 1.0 and the baseline above is unreachable.
This commit is contained in:
Martino Ferrari committed 2026-10-13 15:38:53 +02:00
1 parent 2730707372
commit 380ddb9a77
3 files changed
+79 -17

No files matched your search

+10
View File
@@ -1000,6 +1000,16 @@ gboolean math_probe(gpointer user) {
gboolean measure_probe(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
// The signal this probe measures is enabled *here*, for the same reason the noise probe enables its own:
// a probe must not depend on another probe's side effects. `enable_all_probe` ends by switching a signal
// off (it reads what a disabled signal does to the plots), and with `sine` gone the readings below were
// taken on `square` -- where `top` is 1.0, not the 2/pi the documented baseline is.
{
const std::vector<SignalInfo>& signals = a->model->signals();
for (std::size_t i = 0; i < signals.size(); ++i) {
if (signals[i].name == "sine") a->model->set_signal_enabled(i, true);
}
}
std::string name;
{
std::lock_guard<std::mutex> lock(a->model->mutex());