udpscope: an oscilloscope view for a UDPStreamer source

GTK4 and libadwaita. It receives one UDP stream through the vendored
udpsclient, buffers it in a large ring, and draws it with the vendored
plotter: a trigger with a pre/post event window, a pause that freezes the
window, cursors, measurements, math channels in a node editor, an event cache,
named configurations, preferences, and an export of the visible window to
.npy/.csv. Waveform files can be opened and analysed in place.

The interface is two threads -- a producer that owns the socket and the GTK
main thread that owns every widget and the GL context -- sharing one model
under one mutex, with samples crossing through lock-free rings.

deps/plotter is a submodule pinned to the commit this tree builds against;
deps/udpsclient is vendored in place. docs/claude/ documents the rules that
cost a defect to learn, and SPECS.md is the requirement document.
This commit is contained in:
Martino Ferrari committed 2026-10-11 15:21:40 +02:00
1 parent 8f54841c2c
commit 0e256764d5
41 files changed
+2081 -154

No files matched your search

+11
View File
@@ -24,6 +24,8 @@ LayoutState collect_layout(App& app) {
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
row.signals.push_back(r.signal_name[static_cast<std::size_t>(s)]);
}
const std::vector<float>& weights = app.rows->row_weights();
if (static_cast<std::size_t>(i) < weights.size()) row.weight = weights[static_cast<std::size_t>(i)];
st.rows.push_back(std::move(row));
}
for (std::size_t si = 0; si < model.signals().size(); ++si) {
@@ -313,6 +315,15 @@ void apply_layout(App& app, const LayoutState& st) {
if (rows > RowSet::kMaxRows) rows = RowSet::kMaxRows;
if (rows > 0) app.rows->set_row_count(rows);
// The rows' shares of the height come from the same file as their signals: a sizing a user dragged is
// an arrangement, and the layout is where an arrangement lives. Absent from an older file, the weights
// are all 1 -- equal rows -- which is what such a file was saved with.
{
std::vector<float> weights;
weights.reserve(st.rows.size());
for (const LayoutState::Row& wr : st.rows) weights.push_back(static_cast<float>(wr.weight));
app.rows->set_row_weights(std::move(weights));
}
for (int i = 0; i < rows; ++i) {
int slot = 0;
for (const std::string& name : st.rows[static_cast<std::size_t>(i)].signals) {