docs: record the three noise-rejection controls and their invariants

README gains the three controls where each lives -- the trigger's `band`/`reject`, the
measurement's `noise`/`hf`, and the math channel's `de-noise` node -- each with its unit, what
"off" is, and what it costs, plus the two deliberate deviations from the design spec next to the
features they belong to: the band applies to `edge` and `window` only (a pulse's width is measured
from a crossing to its return, so a band there changes the measured width rather than rejecting
noise; a dropout is an absence with no level to compare), and the band is verified through
`BufferSearch` hit counts rather than `[condition-probe]` latch times, because probe-readings.md
states that a `t0` is not an assertion.

The invariant docs gain the rules a later change must not break: `EventCondition`'s two new fields
and the once-per-packet `configure(dt)` that must not reset; the crossing rule that a change is
between two *decided* sides; the measurement path's exception to "a readout quotes a stored
sample", why the band moves the edge decision only, why `measure_accumulate` loops samples outer,
and why a filtered window disables both bucket fast paths; that `MathOp` is append-only (the index
*is* the serialisation), that the median window is pooled in the runtime rather than held per node,
why the pole reuses `b0`/`z1`, and that median-then-pole is the design; the session's named
`noise`/`hf` keys and its thirteen-field node string that still reads twelve; and the two GTK
traps -- positionally brace-initialised dialogs, and a control that cannot apply being hidden
rather than disabled.
This commit is contained in:
Martino Ferrari committed 2026-10-13 16:05:28 +02:00
1 parent c38630643d
commit 749b34a2cb
7 files changed
+114 -1

No files matched your search

+18
View File
@@ -241,6 +241,24 @@ and reading values off the widgets *inside* the editor cannot see that. It is th
* **A `Delete` key removes the selected *wire* and never falls through to the selected node.**
A key whose target depends on an invisible selection state is how a graph gets destroyed by
accident; the `delete node` button stays the only way to remove a node.
- **`MathOp` is append-only, because `math_op_at()` *is* the serialisation.** A session stores a node's op
as its index through that accessor, so an op inserted in the middle reinterprets every node in every file
written before it; `DeNoise` is therefore index 12, after `Div`, and sits last in the dropdown.
`math_op_uses_filter()` keeps its meaning of *a biquad* -- `math_op_uses_fc()` is the wider question
("does this op read `fc` at all") and is what the dialog's corner row asks.
- **The de-noise node's median window is *pooled* in `MathRuntime::median`, not held in `MathNodeRt`**, so a
node's state stays on one cache line; nine doubles per node would push every node in the graph, filter or
not, across two. `MathNodeRt::med_at` / `::med_n` / `::med_pos` / `::med_have` are that pool's slice,
length, write position and fill, and the pool is sized in `math_prepare()`.
- **Median *then* pole, and the order is the design.** The median kills an impulse outright -- one shorter
than half its window cannot move it at all -- and the pole takes the broadband hash it leaves behind. The
other order feeds the pole the impulse, which smears it over the pole's whole settling time and leaves the
median a wide bump it cannot reject.
- **The de-noise pole reuses `b0` and `z1`, deliberately.** `math_prepare()` carries `z1` across a
re-prepare, so the pole keeps its history through a decimation change exactly as the biquads do -- while the
median window starts from rest, the same choice the biquads' `z2` makes. `median_n()` rounds *up* to odd and
caps at 9, and it is what enforces the window rather than the dialog: a session file holding 4 or 40 still
loads. The cap is what `double sorted[9]` in the step is sized for.
- **The math channels' cost is the *producer's*, and the instrument must not be part of it.**
`push_math_sample()` runs once per stored slot -- up to the wire rate -- so what a regression
here looks like is `gaps`, never a frame time. Three things were taken off that path and must