From 545b2f365f1420ce7a637ddfcd9ea6641ef037d6 Mon Sep 17 00:00:00 2001 From: kamil Date: Thu, 9 Apr 2026 16:58:47 +0300 Subject: [PATCH] clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall sample_clock_hz:max range:0.2 di1:trace di1_group_avg full_res_live live_history_packets:8 duration_ms:100 packet_limit:0 csv:capture.csv svg:capture.svgC --- .gitignore | 14 ++ capture_file_writer.cpp | 456 ++++++++++++++++++++++++++++++++++------ capture_file_writer.h | 11 +- live_plot.html | 114 +++++++++- live_plot.js | 43 ++-- live_plot.json | 43 ++-- main.cpp | 44 +++- 7 files changed, 623 insertions(+), 102 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0217ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Build artifacts +*.exe +*.obj +*.pdb +*.ilk +*.idb + +# Generated capture outputs +*.capture_spool.bin +*.csv +*.svg +*.json +*.html +live_*.js diff --git a/capture_file_writer.cpp b/capture_file_writer.cpp index e3dcbf1..e4cf6ea 100644 --- a/capture_file_writer.cpp +++ b/capture_file_writer.cpp @@ -15,6 +15,9 @@ namespace { constexpr std::size_t kLivePlotMaxColumns = 800; +constexpr std::size_t kLivePlotMidColumns = 4096; +constexpr std::size_t kLivePlotHighColumns = 16384; +constexpr std::size_t kLivePlotRawFrameLimit = 50000; constexpr uint32_t kCsvSpoolMagic = 0x4C564353U; // "SVCL" constexpr uint32_t kCsvSpoolVersion = 2U; @@ -37,6 +40,14 @@ struct PlotPoint { double value = 0.0; }; +struct Di1GroupedTraces { + std::vector ch1; + std::vector ch2; + std::vector rss; +}; + +std::size_t packet_frame_count(const CapturePacket& packet); + std::size_t packet_channel_count(const CapturePacket& packet) { return (packet.channel_count <= 1U) ? 1U : 2U; } @@ -49,6 +60,22 @@ bool packet_has_di1_trace(const CapturePacket& packet) { return packet.has_di1_trace && !packet.di1.empty(); } +std::vector build_rss_values(const CapturePacket& packet) { + const std::size_t frames = packet_frame_count(packet); + std::vector rss; + if (!packet_has_ch2(packet) || (frames == 0U)) { + return rss; + } + + rss.reserve(frames); + for (std::size_t i = 0; i < frames; ++i) { + const double ch1 = packet.ch1[i]; + const double ch2 = packet.ch2[i]; + rss.push_back(std::sqrt((ch1 * ch1) + (ch2 * ch2))); + } + return rss; +} + std::size_t packet_frame_count(const CapturePacket& packet) { std::size_t frames = packet_has_ch2(packet) ? std::min(packet.ch1.size(), packet.ch2.size()) : packet.ch1.size(); if (packet_has_di1_trace(packet)) { @@ -97,6 +124,74 @@ std::vector build_min_max_trace(const std::vector& data, std: return result; } +std::vector build_full_trace(const std::vector& data) { + std::vector result; + result.reserve(data.size()); + for (std::size_t i = 0; i < data.size(); ++i) { + result.push_back({i, data[i]}); + } + return result; +} + +void append_offset_trace(std::vector& dst, + const std::vector& src, + std::size_t sample_offset) { + dst.reserve(dst.size() + src.size()); + for (const auto& point : src) { + dst.push_back({sample_offset + point.sample_index, point.value}); + } +} + +Di1GroupedTraces build_di1_grouped_traces(const std::vector& ch1, + const std::vector& ch2, + const std::vector& di1, + bool has_ch2) { + Di1GroupedTraces traces; + const std::size_t frames = std::min(ch1.size(), di1.size()); + if (frames == 0U) { + return traces; + } + + traces.ch1.reserve(frames / 2U + 1U); + if (has_ch2) { + traces.ch2.reserve(frames / 2U + 1U); + traces.rss.reserve(frames / 2U + 1U); + } + + std::size_t begin = 0U; + while (begin < frames) { + const uint8_t level = di1[begin]; + std::size_t end = begin + 1U; + while ((end < frames) && (di1[end] == level)) { + ++end; + } + + double sum1 = 0.0; + double sum2 = 0.0; + for (std::size_t i = begin; i < end; ++i) { + sum1 += ch1[i]; + if (has_ch2 && (i < ch2.size())) { + sum2 += ch2[i]; + } + } + + const double count = static_cast(end - begin); + const std::size_t mid = begin + ((end - begin - 1U) / 2U); + const double avg1 = sum1 / count; + traces.ch1.push_back({mid, avg1}); + + if (has_ch2) { + const double avg2 = sum2 / count; + traces.ch2.push_back({mid, avg2}); + traces.rss.push_back({mid, std::sqrt((avg1 * avg1) + (avg2 * avg2))}); + } + + begin = end; + } + + return traces; +} + std::vector build_digital_step_trace(const std::vector& data) { std::vector result; if (data.empty()) { @@ -184,6 +279,24 @@ std::string json_points(const std::vector& trace, double frame_freq_h return out.str(); } +std::string json_packet_markers(const std::vector>& markers, + double frame_freq_hz) { + std::ostringstream out; + out << std::fixed << std::setprecision(9); + out << "["; + bool first = true; + for (const auto& marker : markers) { + if (!first) { + out << ","; + } + first = false; + out << "{\"t\":" << (static_cast(marker.first) / frame_freq_hz) + << ",\"label\":\"P" << marker.second << "\"}"; + } + out << "]"; + return out.str(); +} + [[noreturn]] void fail_write(const std::string& message); std::string replace_extension(const std::string& path, const std::string& new_extension) { @@ -296,22 +409,25 @@ void write_live_html_document(const std::string& path, const std::string& data_s << " Auto-refresh every 500 ms\n" << " \n" << " \n" - << "
Open this page once and leave it open. It refreshes its data script to pick up new packets.
\n" + << "
Open this page once and leave it open. It refreshes its data script to pick up a rolling continuous packet window.
\n" << "
\n" << " \n" << " \n" + << " \n" + << " \n" << " \n" << " \n" << " \n" << " \n" - << " Wheel: X zoom, Shift+wheel: Y zoom, double-click: reset\n" + << " Wheel: X zoom, Shift+wheel: Y zoom, Left/Right buttons or arrow keys: pan X, double-click: reset. The live view shows a rolling continuous packet window; CSV stores all samples.\n" << "
\n" << " \n" - << "
\n" - << " CH1\n" - << " CH2\n" - << " DI1\n" - << "
\n" + << "
\n" + << " CH1\n" + << " CH2\n" + << " sqrt(CH1^2 + CH2^2)\n" + << " DI1\n" + << "
\n" << " \n" << " \n" << "