* @brief Bounded (time, value) ring that decimates on ingest to span a fixed window. */
| 386 | * @brief Bounded (time, value) ring that decimates on ingest to span a fixed window. |
| 387 | */ |
| 388 | struct TimeRing { |
| 389 | AxisData time; |
| 390 | AxisData value; |
| 391 | double interval; |
| 392 | double nextEmit; |
| 393 | double accMin; |
| 394 | double accMax; |
| 395 | double accMinTime; |
| 396 | double accMaxTime; |
| 397 | int cellSlots; |
| 398 | |
| 399 | /** |
| 400 | * @brief Constructs the ring with `capacity` slots covering `windowSec` seconds. Each |
| 401 | * grid cell may retain two slots (min + max), so the interval reserves both to |
| 402 | * keep a saturated source spanning the full window. |
| 403 | */ |
| 404 | explicit TimeRing(int capacity = 1, double windowSec = 1.0) |
| 405 | : time(static_cast<std::size_t>(capacity < 1 ? 1 : capacity)) |
| 406 | , value(static_cast<std::size_t>(capacity < 1 ? 1 : capacity)) |
| 407 | , interval(2.0 * windowSec / std::max(1, capacity)) |
| 408 | , nextEmit(0.0) |
| 409 | , accMin(0.0) |
| 410 | , accMax(0.0) |
| 411 | , accMinTime(0.0) |
| 412 | , accMaxTime(0.0) |
| 413 | , cellSlots(0) |
| 414 | {} |
| 415 | |
| 416 | /** |
| 417 | * @brief Clears retained samples and the decimation cell state. |
| 418 | */ |
| 419 | void clear() |
| 420 | { |
| 421 | time.clear(); |
| 422 | value.clear(); |
| 423 | cellSlots = 0; |
| 424 | nextEmit = 0.0; |
| 425 | } |
| 426 | |
| 427 | /** |
| 428 | * @brief Appends one (time, value), decimating to a min/max envelope pair per cell on an |
| 429 | * absolute time grid, replacing the drifting peak-pick that aliased high-rate |
| 430 | * bipolar signals into shimmer. The open cell's slots update in place so the |
| 431 | * newest sample is visible immediately at any input rate. |
| 432 | */ |
| 433 | void appendDecimated(double t, double v) |
| 434 | { |
| 435 | Q_ASSERT(interval > 0.0); |
| 436 | Q_ASSERT(time.capacity() == value.capacity()); |
| 437 | |
| 438 | if (time.raw() == nullptr || value.raw() == nullptr) [[unlikely]] |
| 439 | return; |
| 440 | |
| 441 | if (!std::isfinite(t) || !std::isfinite(v)) [[unlikely]] |
| 442 | return; |
| 443 | |
| 444 | if (time.size() > 0 && t < time[time.size() - 1]) [[unlikely]] |
| 445 | t = time[time.size() - 1]; |