diff --git a/src/acquisition.cpp b/src/acquisition.cpp index 5ded4c1..3c8d900 100644 --- a/src/acquisition.cpp +++ b/src/acquisition.cpp @@ -136,6 +136,41 @@ std::uint32_t Acquisition::build_stamps(const udps_frame_t* frame, int time_sign } stamps_.assign(n, 0.0); + // Relative mode: the stamp is a pure function of position and the *declared* rate. Nothing + // below this block runs -- not the arrival-cadence override, not the degeneracy guard, not + // the glitch bridge, not the monotonic clamp's reason for existing -- because a position + // cannot jitter, cannot fail to advance and cannot jump. + if (cfg_.relative_time) { + if (batch_ == 0) { + batch_ = n; + std::fprintf(stderr, + "[udpscope] relative time: batch=%u samples/packet, rate=%.6g S/s declared\n", + batch_, model_.partition().base_dt > 0.0 ? 1.0 / model_.partition().base_dt + : 0.0); + std::fflush(stderr); + } else if (n != batch_) { + // A frame whose length disagrees with the first one is an inconsistency in the + // producer, not a loss: the counter still names one packet. Warn once and keep the + // established batch, which is only used to size holes -- the timeline itself is built + // from the lengths actually seen, so it stays exact for this frame either way. + if (!warned_batch_) { + warned_batch_ = true; + std::fprintf(stderr, + "[udpscope] warning: packet carries %u samples but the stream's first " + "carried %u; keeping %u as the packet length a lost packet is worth.\n", + n, batch_, batch_); + std::fflush(stderr); + } + } + double dt = model_.partition().base_dt; + if (!(dt > 0.0)) dt = 1.0e-3; + for (std::uint32_t k = 0; k < n; ++k) { + stamps_[k] = double(slot_base_ + k) * dt; + } + slot_base_ += n; + return n; + } + double period = model_.partition().base_dt; if (!(period > 0.0)) period = 1.0e-3; // The cadence of the samples actually stored: the partition's slot interval (base diff --git a/src/acquisition.hpp b/src/acquisition.hpp index 96898bf..2fa6fd4 100644 --- a/src/acquisition.hpp +++ b/src/acquisition.hpp @@ -90,6 +90,17 @@ class Acquisition { /// signal carrying volt-like values, others from the arrival clock -- which is a /// time base no reader can make sense of (see `build_stamps`). int time_source_ = 0; + /// Relative-time mode's position counter: samples seen since the stream began, which is + /// what the counter names. Advanced by `batch_` for every packet the counter reports + /// missing (before the frame's own samples) and then by the frame's own `n`, so it is + /// exactly `counter * batch + sample_index` for a constant batch and degrades gracefully + /// when a frame's length disagrees. + std::uint64_t slot_base_ = 0; + /// Samples per DATA packet, fixed by the first frame. Only ever needed to size what is + /// *absent*: a delivered frame carries its own length. 0 until the first frame. + std::uint32_t batch_ = 0; + /// Latches the one warning about a frame whose length disagrees with the stream's first. + bool warned_batch_ = false; std::vector stamps_; /// The decimation filter's state: a running sum per channel (and one for the shared /// clock) over the group of base samples that becomes one stored sample. A boxcar