The help was 14 Pango-markup `.txt` bodies in a second GResource, drawn into a paged `GtkWindow` built in `help_window.cpp`. That made F1 a viewer of ours with no search index, no cross-references and no translation, and it kept the manual in the *binary* when every other desktop application's is a document on disk. It is now a real Mallard set: help/C/org.udpscope.app/ index.page + 14 topic pages + figures/*.png and `show_help()` hands `help:org.udpscope.app` to whatever the desktop registered for the `help:` scheme -- Yelp on GNOME -- through a `GtkUriLauncher`, reporting a launch failure with a `GtkAlertDialog` rather than a click that silently does nothing. The paged window's builder, the page table and `help_self_check()`'s markup validation are gone with the resource. The document identifier is the desktop file's own basename (its desktop-file id), derived in CMakeLists from `data/org.udpscope.app.desktop.in`, so the same string is the `.desktop` name, the GApplication id and the install directory at once. The set is installed by a new `install()` rule to `<datadir>/help/C/org.udpscope.app/` -- where the freedesktop Help System Specification's `help:` URI resolves `help:<id>` -- so the manual is read from a datadir by another process even though the binary has no install step. `UDPSCOPE_HELP_NAME` and `UDPSCOPE_HELP_DIR` are compiled in from that one place. The 14 pages were migrated fact for fact, but restructured the Mallard way: a `<desc>` per page (what a search result shows), `<steps>` for procedures, `<terms>` for option lists, `<gui>`/`<key>`/`<keyseq>` for interface words and keys, `<note>` for the asides, and a topic graph via `<link type="guide" xref="index#main"/>`. The figures are annotated screenshots produced by `tools/make-help-shots.sh`, which drives the app headlessly against `tools/mock_udpstreamer.py` and draws the boxes with ImageMagick; the side panel's box is measured from the app's own `side=NNNx` dump reading, the header and status bands come from a documented table, and a crop whose standard deviation is 0 fails the script. `cmake --build build --target test-help` checks the set the way `test-ui` checks the `.ui` files: `yelp-check`'s offline subcommands (links, media, orphans, ids) and a `yelp-build html` render. `yelp-check validate` is deliberately absent -- it fetches the Mallard RelaxNG schema over the network and fails offline for that reason, not for a fault in the pages. The `[help]` probe reading changes with the resource it was written for. `parsed == pages` over in-binary markup cannot exist now, so `help_self_check()` scans `UDPSCOPE_HELP_DIR` and the line reads `topics=14 index=1 figures=3`; `topics=0 index=0` is the manual missing from the tree, which is the finding the old count named. Verified: clean build with the documented three warnings (the help bundle's `-Woverlength-strings` is gone, so the baseline moves from four to three); test-ui 14/14; test-help clean (the four subcommands and 15 pages rendered); a temp-prefix install lands 15 `.page` files and 3 figures at `share/help/C/org.udpscope.app/`; the headless run reads `[help] topics=14 index=1 figures=3 dir=...` with 0 Gtk-CRITICAL. Yelp's own window could not be exercised on this box -- it starts under Xvfb, activates its portals and never maps, the same WebKit/portal limitation that stops a grab -- so its rendering and its search index are unverified here; the content pipeline was (`yelp-build` renders the whole set). Docs: the manual's build/install/open story in `build-and-run.md` and README; the warning baseline in `verification.md`/CLAUDE.md; the help bullets in `invariants-gtk-and-chrome.md`; the `[help]` line in `probe-readings.md`.
110 lines
5.5 KiB
Plaintext
110 lines
5.5 KiB
Plaintext
<page xmlns="http://projectmallard.org/1.0/"
|
|
type="topic" style="task"
|
|
id="math">
|
|
|
|
<info>
|
|
<link type="guide" xref="index#analysis"/>
|
|
<link type="seealso" xref="signals"/>
|
|
<desc>Building a derived channel from a graph of source nodes, operations and one output,
|
|
and how the graph is evaluated.</desc>
|
|
<credit type="author copyright">
|
|
<name>Martino Ferrari</name>
|
|
</credit>
|
|
</info>
|
|
|
|
<title>Math channels</title>
|
|
|
|
<p>A math channel is a signal computed from other signals. You build it as a small graph —
|
|
source nodes, operations, one output — and udpscope then treats the result as an ordinary
|
|
channel.</p>
|
|
|
|
<section id="reach">
|
|
<title>How to reach it</title>
|
|
<steps>
|
|
<item>
|
|
<p>Press the math button in the toolbar, or <key>g</key>.</p>
|
|
</item>
|
|
<item>
|
|
<p>The pane's <gui>+</gui> adds a channel, and opening one shows the graph editor:
|
|
nodes as boxes, click a box to select it and edit its parameters beside the canvas.</p>
|
|
</item>
|
|
<item>
|
|
<p>Inside the editor, <gui>add node</gui> is a menu button whose popover picks the op,
|
|
so the node arrives as the operation you asked for.</p>
|
|
</item>
|
|
</steps>
|
|
</section>
|
|
|
|
<section id="gestures">
|
|
<title>Graph gestures</title>
|
|
<p><gui>drag</gui> from an output port to an input port to wire them; drag a wired input
|
|
port off its connection to detach it; click a wire and press <key>Delete</key> to remove
|
|
it; drag a box to move it. <gui>Wheel</gui> zooms about the pointer. <gui>Drag the
|
|
background</gui> to pan. <key>f</key> fits all nodes into view;
|
|
<keyseq><key>Ctrl</key><key>0</key></keyseq> resets the view. <gui>tidy</gui>, the button
|
|
beside <gui>fit</gui> in the editor, lays the graph out in topological columns and frames
|
|
it. The orange dot marks the output node; click <gui>set as output</gui> to move it.</p>
|
|
</section>
|
|
|
|
<section id="knowing">
|
|
<title>Worth knowing</title>
|
|
<list>
|
|
<item><p>The graph is evaluated as an ordered list, so a node may only read ones
|
|
<em>above</em> it. A wire drawn right to left re-sorts the list silently so the graph
|
|
stays valid. A cycle is unrepresentable: the connection is refused rather than created,
|
|
and the line beneath the form says why — a loop, or a node asked to read itself.</p></item>
|
|
<item><p>A derived channel <em>is</em> a channel. It appears in the signal panel, can be
|
|
plotted, measured, exported, and used as a source for another math channel.</p></item>
|
|
<item><p>The math runs on the <em>stored</em> samples — decimated and averaged — so a
|
|
cutoff you type in Hz means the rate the filter will actually run at. Change the window
|
|
across a decimation boundary and the coefficients are rebuilt for the new rate.</p></item>
|
|
<item><p>A source may only name an <em>acquired</em> channel. The stream's timestamp
|
|
signal is not one: a graph sourced from it validates and then reads NaN for ever, so the
|
|
dropdown does not offer it.</p></item>
|
|
<item><p>The output defaults to the <em>last</em> node, and a node added to a linear
|
|
graph becomes the output. Choosing an earlier node explicitly is respected.</p></item>
|
|
<item><p>Every numeric field starts at its own identity value — gain 1, offset 0 — not
|
|
at the bottom of its range.</p></item>
|
|
</list>
|
|
</section>
|
|
|
|
<section id="why">
|
|
<title>Why an operation sometimes does nothing</title>
|
|
<p>A graph's <em>output</em> is the node whose value becomes the channel, and it is drawn
|
|
as the orange dot. A new graph is built source, then operation, and the last node added
|
|
becomes the output — but a graph whose output is left on the source is valid and simply
|
|
passes its input through, which is the usual reason a gain or a filter appears to have no
|
|
effect. <gui>Set as output</gui> moves the dot.</p>
|
|
|
|
<p><gui>Apply</gui> is insensitive while the graph is incomplete. A line beneath the form
|
|
says why: an input that is not connected, no output chosen, or a <em>channel</em> node
|
|
naming a signal the scope is not acquiring. Only the channels being acquired are offered,
|
|
because a source that is not acquired reads as no data for ever — and a graph that reads
|
|
NaN is harder to diagnose than one that would not save.</p>
|
|
|
|
<p>The filters are real filters. A math channel's samples are produced as the stream
|
|
arrives, once per stored slot, so a low pass carries the history a filter needs. A cutoff
|
|
typed in hertz is against the <em>stored</em> rate, which is the wire rate until a wide
|
|
window makes the buffer decimate — the status line's rate is that number, and the filters
|
|
are rebuilt whenever it changes.</p>
|
|
|
|
<figure>
|
|
<title>The math channel editor: nodes as boxes on a canvas, the orange dot marking the
|
|
output, and the selected node's parameters beside it.</title>
|
|
<media type="image" mime="image/png" src="figures/math-editor.png" width="938" height="420">
|
|
<p>A sine source wired through a gain into a filter, with the output on the last
|
|
node.</p>
|
|
</media>
|
|
</figure>
|
|
</section>
|
|
|
|
<section id="example">
|
|
<title>Example</title>
|
|
<p><code>[sine] → [gain x2]</code> gives a channel reading <code>y=[-2, 2]</code>. A
|
|
100 Hz low pass on the same source passes 50 Hz at 0.97 and 2 kHz at 0.002; a 1 kHz band
|
|
pass with Q=2 passes 200 Hz at 0.05, 1 kHz at 0.95 and 4 kHz at 0.05. Each derived channel
|
|
must hold exactly as many samples as the shared clock has time slots.</p>
|
|
</section>
|
|
|
|
</page>
|