From c162adb1df64dafaaf0b7472ec8e61ccb5f0bc32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Monta=C3=B1es?= Date: Thu, 19 Mar 2026 22:13:41 +0100 Subject: [PATCH] feat: waveform uses real time (ms) - clock speed affects wave width - Slow clock (1000ms) = wider pulses - Fast clock (100ms) = narrower pulses - Timeline shows ms/s labels - Zoom controls scale the time axis --- js/waveform.js | 124 +++++++++++++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 51 deletions(-) diff --git a/js/waveform.js b/js/waveform.js index 020e769..fe38711 100644 --- a/js/waveform.js +++ b/js/waveform.js @@ -31,7 +31,8 @@ export function recordSample() { if (!changed && state.timeStep > 0) return; - state.timeStep++; + // Manual toggles advance by simSpeed too for consistency + state.timeStep += state.simSpeed; gates.forEach(g => { if (!waveData[g.id]) waveData[g.id] = []; const arr = waveData[g.id]; @@ -43,7 +44,8 @@ export function recordSample() { } export function forceRecordSample() { - state.timeStep++; + // Advance time by the current simSpeed (in ms) to reflect real time + state.timeStep += state.simSpeed; state.gates.forEach(g => { if (!state.waveData[g.id]) state.waveData[g.id] = []; state.waveData[g.id].push({ t: state.timeStep, value: g.value }); @@ -71,7 +73,10 @@ export function setEvaluateAll(fn) { export function updateWaveInfo() { const totalSamples = Object.values(state.waveData).reduce((sum, arr) => sum + arr.length, 0); - document.getElementById('wave-info').textContent = `T=${state.timeStep} | ${totalSamples} samples`; + const timeLabel = state.timeStep >= 1000 + ? `${(state.timeStep/1000).toFixed(1)}s` + : `${state.timeStep}ms`; + document.getElementById('wave-info').textContent = `T=${timeLabel} | ${totalSamples} samples`; } export function clearWaveData() { @@ -121,32 +126,48 @@ export function drawWaveforms() { return; } - // Auto-scroll to show latest (only if we're already near the end) - const maxVisible = Math.floor(wc.width / state.waveZoom); - const isNearEnd = state.waveScroll >= state.timeStep - maxVisible - 2; - if (state.timeStep > maxVisible && isNearEnd) { - state.waveScroll = state.timeStep - maxVisible; - } - // Clamp scroll to valid range - state.waveScroll = Math.max(0, Math.min(state.timeStep - 1, state.waveScroll)); + // pxPerMs: how many pixels per millisecond of simulation time + const pxPerMs = state.waveZoom / 100; // waveZoom=20 → 0.2 px/ms + + // Total width in pixels for all recorded time + const totalPx = state.timeStep * pxPerMs; + + // Visible width in ms + const visibleMs = wc.width / pxPerMs; + + // Auto-scroll to show latest (only if near the end) + const isNearEnd = state.waveScroll >= state.timeStep - visibleMs - state.simSpeed * 2; + if (totalPx > wc.width && isNearEnd) { + state.waveScroll = state.timeStep - visibleMs; + } + state.waveScroll = Math.max(0, state.waveScroll); + + // Helper: convert simulation time (ms) to pixel X + const tToX = (t) => (t - state.waveScroll) * pxPerMs; + + // Draw time grid (every gridMs milliseconds) + let gridMs = 500; + if (pxPerMs * gridMs < 30) gridMs = 1000; + if (pxPerMs * gridMs < 30) gridMs = 2000; + if (pxPerMs * gridMs > 200) gridMs = 200; + if (pxPerMs * gridMs > 200) gridMs = 100; - // Draw time grid wctx.strokeStyle = '#151520'; wctx.lineWidth = 1; - for (let t = Math.ceil(state.waveScroll); t <= state.timeStep; t++) { - const x = (t - state.waveScroll) * state.waveZoom; + const startT = Math.floor(state.waveScroll / gridMs) * gridMs; + for (let t = startT; t <= state.timeStep; t += gridMs) { + const x = tToX(t); if (x < 0 || x > wc.width) continue; wctx.beginPath(); wctx.moveTo(x, 0); wctx.lineTo(x, wc.height); wctx.stroke(); - if (t % 5 === 0 || state.waveZoom > 30) { - wctx.fillStyle = '#333'; - wctx.font = '9px monospace'; - wctx.textAlign = 'center'; - wctx.fillText(`${t}`, x, 10); - } + wctx.fillStyle = '#333'; + wctx.font = '9px monospace'; + wctx.textAlign = 'center'; + const label = t >= 1000 ? `${(t/1000).toFixed(1)}s` : `${t}ms`; + wctx.fillText(label, x, 10); } // Row dividers @@ -159,7 +180,7 @@ export function drawWaveforms() { wctx.stroke(); }); - // Draw signals + // Draw signals using actual timestamps tracked.forEach((gate, i) => { const data = state.waveData[gate.id] || []; if (data.length === 0) return; @@ -173,49 +194,50 @@ export function drawWaveforms() { wctx.lineWidth = 1.5; wctx.beginPath(); - let lastVal = 0; let started = false; - // Build complete signal timeline - const timeline = []; - for (let t = 1; t <= state.timeStep; t++) { - const sample = data.filter(s => s.t <= t).pop(); - timeline.push(sample ? sample.value : 0); - } - - for (let t = 0; t < timeline.length; t++) { - const x = (t + 1 - state.waveScroll) * state.waveZoom; - const val = timeline[t]; - const y = val ? yHigh : yLow; + for (let s = 0; s < data.length; s++) { + const sample = data[s]; + const nextT = s < data.length - 1 ? data[s + 1].t : state.timeStep; + const x1 = tToX(sample.t); + const x2 = tToX(nextT); + const y = sample.value ? yHigh : yLow; if (!started) { - wctx.moveTo(x, y); + // Draw from time 0 to first sample + const x0 = tToX(0); + const initY = yLow; // default low before first sample + if (x0 < wc.width) { + wctx.moveTo(Math.max(0, x0), initY); + if (sample.t > 0) wctx.lineTo(x1, initY); + wctx.lineTo(x1, y); + } started = true; } else { - if (val !== lastVal) { - wctx.lineTo(x, lastVal ? yHigh : yLow); - wctx.lineTo(x, y); + // Vertical transition from previous value + const prevVal = data[s - 1].value; + if (sample.value !== prevVal) { + wctx.lineTo(x1, prevVal ? yHigh : yLow); + wctx.lineTo(x1, y); } - wctx.lineTo(x + state.waveZoom, y); } - lastVal = val; + // Horizontal line to next transition + wctx.lineTo(x2, y); + + // Fill high regions + if (sample.value) { + wctx.save(); + wctx.globalAlpha = 0.08; + wctx.fillStyle = color; + wctx.fillRect(x1, yHigh, x2 - x1, sigH); + wctx.restore(); + } } wctx.stroke(); - - // Fill area under signal - wctx.globalAlpha = 0.08; - wctx.fillStyle = color; - for (let t = 0; t < timeline.length; t++) { - const x = (t + 1 - state.waveScroll) * state.waveZoom; - if (timeline[t]) { - wctx.fillRect(x, yHigh, state.waveZoom, sigH); - } - } - wctx.globalAlpha = 1; }); // Cursor line at current time - const cursorX = (state.timeStep - state.waveScroll) * state.waveZoom; + const cursorX = tToX(state.timeStep); if (cursorX >= 0 && cursorX <= wc.width) { wctx.strokeStyle = '#00e59966'; wctx.lineWidth = 1;