added math signals
This commit is contained in:
1 parent
02620ca946
commit
aa2508e431
10 files changed
+1773
-27
No files matched your search
@@ -347,6 +347,77 @@ it holds more than the window the bound became the whole ring, and a zoom-out wa
|
||||
view past the window into data the window was never set to show. A partly filled window
|
||||
simply has an empty left-hand side.
|
||||
|
||||
**The panel switches are on the toolbar, and the panes own their own controls.** Cursors,
|
||||
Measurements and Math channels are icon toggles in the header bar rather than rows in the overflow
|
||||
menu, and **Reset cursors** sits in the cursors pane beside the mode it resets (it acts on that
|
||||
pane's instruments and is only visible while they exist). The rows read, left to right: **window control, trigger, pause** — the trigger and the pause are
|
||||
read against the span on screen, so they sit with the control that sets it — then, on the right,
|
||||
**cursors, measurements, math, sidebar** and **the menu last**. With `HeaderBar::pack_end()` the
|
||||
**first** packed is the rightmost, so the list is written right to left; that took a run of the dump
|
||||
to settle (two comments in the builder disagreed), and the line it prints
|
||||
(`panel controls: … ; toolbar, left to right: …`) is how a layout claim gets checked without a
|
||||
screenshot. The menu keeps what acts on the *data* (Fit, Export) and the two
|
||||
editors that are not panes (Layout, Signal list). The dump reports the
|
||||
arrangement as `panel controls: cursors=1 measurements=1 math=1 in the toolbar, reset=1 in the
|
||||
cursors pane`, computed on the first frame — a widget's parent is the only thing that says it, and
|
||||
the dump runs after the window is gone.
|
||||
|
||||
**The session is saved with the layout**, per data source, in
|
||||
`~/.config/udpscope/<host>_<port>.toml`: the display window, the rows and which signal each slot
|
||||
holds, the per-signal calibration, **which signals were enabled**, **the math channels with their
|
||||
graphs** and **the measurements pane**. Identity is the signal *name* everywhere, because a CONFIG
|
||||
renumbers the channels; the graphs are installed before the rows are assigned, since a row can hold
|
||||
a math channel's name and that name only exists as a signal once its graph is in. So a scope
|
||||
reopened on the same source comes back as the one that was closed — including a signal that was
|
||||
enabled only to feed a math channel.
|
||||
|
||||
**Math channels.** A math channel is derived from the others by a graph of operations and is then
|
||||
an *ordinary channel*: the model synthesises a signal entry for it, so an enabled one is acquired,
|
||||
plotted, measured, exported and listed in the signal panel exactly like a streamed signal — and
|
||||
another math channel can be its source. The operations are `channel` (a source), `gain + offset`,
|
||||
`abs`, `sqrt`, `clamp`, `low pass`, `high pass`, `band pass` (biquads, cutoffs in Hz against the
|
||||
stored rate), `sum`, `difference`, `product` and `ratio`; the arithmetic ops take two inputs, which
|
||||
is what makes branching — the sum of a raw and a filtered channel — expressible at all.
|
||||
|
||||
The editor is a compact node graph: each node is a box, its inputs are the squares on its left edge
|
||||
and its output the square on its right. Click a box to select it and its parameters appear in the
|
||||
form beside the canvas; click an output square and then an input square to wire them; drag a box to
|
||||
move it. The graph is a *list* walked in order, so a node can only read the ones above it — a cycle
|
||||
is not detected, it is unrepresentable, and an input square is only offered the nodes that qualify.
|
||||
The node carrying the channel's value is the **last one** of a graph built top to bottom (as the
|
||||
orange dot on the canvas says, and settable with *set as output*): it defaulted to the first node —
|
||||
the source — so every operation was computed and discarded, and a gain or a filter produced a
|
||||
channel identical to its input. Numeric fields are seeded with their own identity values too, since
|
||||
`gtk_spin_button_new_with_range()` leaves a spin button at its range's minimum: the offset started
|
||||
at -1e6, which turned `x*2` into `x*2 - 2000000`.
|
||||
|
||||
Wiring is checked before the channel is created, and the reason is shown rather than the dialog
|
||||
closing on the work.
|
||||
|
||||
A `Source` node lists only the channels the scope is actually **acquiring**: naming a disabled
|
||||
signal would read as no data for ever, and the stream's timestamp signal has no samples at all. The
|
||||
filters are re-prepared from the *stored* sample interval on **every** plan change — a CONFIG, an
|
||||
enable/disable, and a window change that moves the decimation — because a biquad's coefficients are
|
||||
a function of that interval and its state belongs to the rate it was running at; preparing them
|
||||
only when the graph is edited left a wrong cutoff and a ringing transient after any window change.
|
||||
|
||||
The samples are produced at **ingest**, once per stored slot, from the values the other channels
|
||||
just pushed — and a derived channel is *skipped* by the loop that reads the stream, since it has no
|
||||
packet signal to read. That mattered more than it looks: reading it gave zero, and pushing that
|
||||
alongside the real value advanced its sample ring twice per time slot, so the trace alternated
|
||||
between its value and 0 while the buffer filled and then slid a whole window off the time ring once
|
||||
both wrapped and vanished. One push per slot is the invariant, and the probe's dump prints it as `N
|
||||
samples vs M time slots` — a ratio the plot cannot show you. That is what makes the filters real: an IIR has memory, so a low pass computed from a
|
||||
window would be a different (and wrong) answer every time the window moved, while one fed the
|
||||
stream sample by sample is the filter the user asked for. It also means the pane's numbers and the
|
||||
plot agree by construction — both come from the same stored samples.
|
||||
|
||||
**Closing the editor clears it.** The dialog's pointer is cleared by a `close-request` handler, and
|
||||
`gtk_window_destroy()` — which is what Cancel and Save used to call — does **not** emit that signal.
|
||||
The pointer therefore stayed set on a destroyed window, and every later Edit took the "already open"
|
||||
branch and presented the corpse: a GTK critical and nothing on screen. Both buttons call
|
||||
`gtk_window_close()`, which emits the signal, clears the pointer, and destroys.
|
||||
|
||||
**The trigger marker is a readout, not a handle.** It is a cursor, and a cursor is grabbable by
|
||||
default — so parking it outside the axis, which is how it is hidden when no event is on screen, did
|
||||
not stop a *drag*: the controller holds a marker for the gesture's life and follows the pointer
|
||||
|
||||
Reference in new issue
Block a user