From 6abfadae4f27f1d7e63e5cb512b723fd4f56b8a3 Mon Sep 17 00:00:00 2001 From: serversdown Date: Sat, 23 May 2026 21:58:20 +0000 Subject: [PATCH] viewers: render pre-trigger samples (time_axis is metadata, not an array) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /db/events/{id}/waveform.json endpoint returns `time_axis` as a metadata object — {sample_rate, pretrig_samples, t0_ms, dt_ms, n_samples, total_samples, rectime_seconds} — not a per-sample times array. Both viewers (sfm_webapp.html sidecar modal + event_browser.html) were treating it as an array, silently falling back to a derived path that ignored pretrig entirely and started the time axis at 0. Symptom: trigger line drawn at the very left edge of every chart, no visible "leading up to the event" samples even though they're in the decoded data. Fix: read time_axis.t0_ms (negative when pretrig samples exist), time_axis.dt_ms, build per-sample times as `t0_ms + i * dt_ms`. Trigger line lands at sample where t crosses 0; pretrig samples render at negative t to the left of it. Confirmed on a K558 event with 208 pretrig samples + 2 sec rectime at 1024 sps — time axis now spans -203 ms to +2046 ms, trigger line at ~9% from the left edge as expected. Co-Authored-By: Claude Opus 4.7 (1M context) --- sfm/event_browser.html | 19 +++++++++---------- sfm/sfm_webapp.html | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/sfm/event_browser.html b/sfm/event_browser.html index 0dce1b0..c3b7516 100644 --- a/sfm/event_browser.html +++ b/sfm/event_browser.html @@ -522,8 +522,13 @@ function renderWaveform(data) { charts = {}; const channels = data.channels || {}; - const timeAxis = data.time_axis || null; // ms relative to trigger - const triggerMs = data.trigger_ms ?? 0; + // time_axis is METADATA from sfm.plot.v1 — sample_rate, pretrig_samples, + // t0_ms (first-sample time relative to trigger; negative when pretrig + // exists), dt_ms. Trigger is at t=0 by convention. + const ta = data.time_axis || {}; + const sr = ta.sample_rate || 1024; + const dtMs = ta.dt_ms || (1000.0 / sr); + const t0Ms = ta.t0_ms != null ? ta.t0_ms : 0; const isPrintMode = document.body.classList.contains('print-view'); // Which channels actually have data → determines which one renders the @@ -578,14 +583,8 @@ function renderWaveform(data) { wrap.appendChild(canvasWrap); chartsDiv.appendChild(wrap); - // Build time labels — use server-provided time_axis if present, else derive from sample_rate - let times; - if (timeAxis && timeAxis.length === values.length) { - times = timeAxis; - } else { - const sr = data.sample_rate || 1024; - times = values.map((_, i) => (i / sr * 1000 - triggerMs)); - } + // Per-sample time in ms relative to trigger. Negative for pre-trigger samples. + const times = values.map((_, i) => t0Ms + i * dtMs); // Downsample for rendering const MAX_POINTS = 4000; diff --git a/sfm/sfm_webapp.html b/sfm/sfm_webapp.html index 6c68b38..df23b3a 100644 --- a/sfm/sfm_webapp.html +++ b/sfm/sfm_webapp.html @@ -2572,8 +2572,14 @@ function _renderScWaveform(data) { _destroyScCharts(); const channels = data.channels || {}; - const timeAxis = data.time_axis || null; - const triggerMs = data.trigger_ms ?? 0; + // time_axis is METADATA, not an array — it carries sample_rate, + // pretrig_samples, t0_ms (first-sample time relative to trigger, + // negative when pretrig samples exist), and dt_ms. Trigger is at + // t=0 by convention. + const ta = data.time_axis || {}; + const sr = ta.sample_rate || 1024; + const dtMs = ta.dt_ms || (1000.0 / sr); + const t0Ms = ta.t0_ms != null ? ta.t0_ms : 0; // Which channels have data — determines which one renders the shared bottom axis. const withData = _SC_CHANNEL_ORDER.filter(ch => @@ -2612,14 +2618,8 @@ function _renderScWaveform(data) { wrap.appendChild(canvasWrap); chartsDiv.appendChild(wrap); - // Build time axis. Prefer server-provided time_axis; else derive from sample_rate. - let times; - if (timeAxis && timeAxis.length === values.length) { - times = timeAxis; - } else { - const sr = data.sample_rate || 1024; - times = values.map((_, i) => (i / sr * 1000 - triggerMs)); - } + // Per-sample time in ms relative to trigger. Negative for pre-trigger samples. + const times = values.map((_, i) => t0Ms + i * dtMs); // Downsample for rendering when very long. const MAX = 3000;