[fix](trx-frontend-http): optimize spectrum and waterfall rendering performance
Six hot-path optimizations that reduce per-frame CPU cost: 1. Waterfall color LUT: Pre-compute a 256-entry RGBA lookup table (bins are i8 = 256 possible values) instead of calling waterfallColorRgba() per-pixel with HSL→RGB math + Math.pow(). Eliminates ~2000+ HSL conversions per frame across both waterfalls. 2. Noise floor O(N)→O(N log N): Replace .slice().sort() with an in-place quickselect algorithm for 15th-percentile estimation. For 1024 bins this is ~10× faster. 3. Reuse spectrum bin buffers: SSE handler and buildSpectrumRenderData now reuse pre-allocated arrays instead of creating new Array(N) and .map() allocations every frame. Reduces GC pressure. 4. Cache canvas dimensions: drawSpectrum and drawSpectrumWaterfall read cached CSS dimensions instead of querying clientWidth/ clientHeight every frame (which forces layout recalculation). Dimensions refreshed on resize and layout changes. 5. Cache DOM references: getElementById calls for zoom indicator and minimap elements moved to module-level constants instead of querying the DOM on every drawSpectrum call. 6. Efficient array trimming: Peak hold pruning uses in-place splice from front instead of .filter() (which allocates a new array). Waterfall row trimming uses splice instead of repeated .shift(). https://claude.ai/code/session_01G6wuNCkckbHHsU7w5zCtW2 Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1384,10 +1384,9 @@ function pushHeaderSignalSample(sUnits) {
|
|||||||
|
|
||||||
function trimOverviewWaterfallRows() {
|
function trimOverviewWaterfallRows() {
|
||||||
if (!overviewCanvas) return;
|
if (!overviewCanvas) return;
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const maxRows = Math.max(1, Math.floor(overviewCanvas.height / _cachedDpr));
|
||||||
const maxRows = Math.max(1, Math.floor(overviewCanvas.height / dpr));
|
if (overviewWaterfallRows.length > maxRows) {
|
||||||
while (overviewWaterfallRows.length > maxRows) {
|
overviewWaterfallRows.splice(0, overviewWaterfallRows.length - maxRows);
|
||||||
overviewWaterfallRows.shift();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1465,20 +1464,17 @@ function drawOverviewWaterfall(W, H, pal) {
|
|||||||
overviewWfTexWidth = iW;
|
overviewWfTexWidth = iW;
|
||||||
overviewWfTexHeight = iH;
|
overviewWfTexHeight = iH;
|
||||||
|
|
||||||
|
ensureWaterfallLut(pal, minDb, maxDb);
|
||||||
|
|
||||||
function renderRow(dstY, srcBins) {
|
function renderRow(dstY, srcBins) {
|
||||||
if (!Array.isArray(srcBins) || srcBins.length === 0) return;
|
if (!Array.isArray(srcBins) || srcBins.length === 0) return;
|
||||||
const { startIdx, endIdx } = overviewVisibleBinWindow(lastSpectrumData, srcBins.length);
|
const { startIdx, endIdx } = overviewVisibleBinWindow(lastSpectrumData, srcBins.length);
|
||||||
const spanBins = Math.max(1, endIdx - startIdx);
|
const spanBins = Math.max(1, endIdx - startIdx);
|
||||||
const rowBase = dstY * rowStride;
|
const rowBase = dstY * rowStride;
|
||||||
|
const iwM1 = Math.max(1, iW - 1);
|
||||||
for (let x = 0; x < iW; x++) {
|
for (let x = 0; x < iW; x++) {
|
||||||
const frac = x / Math.max(1, iW - 1);
|
const binIdx = Math.min(endIdx, startIdx + ((x * spanBins / iwM1) | 0));
|
||||||
const binIdx = Math.min(endIdx, startIdx + Math.floor(frac * spanBins));
|
waterfallLutWrite(overviewWfTexData, rowBase + x * 4, srcBins[binIdx]);
|
||||||
const c = waterfallColorRgba(srcBins[binIdx], pal, minDb, maxDb);
|
|
||||||
const p = rowBase + x * 4;
|
|
||||||
overviewWfTexData[p + 0] = Math.round(c[0] * 255);
|
|
||||||
overviewWfTexData[p + 1] = Math.round(c[1] * 255);
|
|
||||||
overviewWfTexData[p + 2] = Math.round(c[2] * 255);
|
|
||||||
overviewWfTexData[p + 3] = Math.round(c[3] * 255);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1582,6 +1578,38 @@ function waterfallColorRgba(db, pal, minDb, maxDb) {
|
|||||||
return cssColorToRgba(`hsla(${hue}, ${pal.waterfallSat}%, ${light}%, ${alpha})`);
|
return cssColorToRgba(`hsla(${hue}, ${pal.waterfallSat}%, ${light}%, ${alpha})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 256-entry waterfall color lookup table (bins are i8 = 256 possible values).
|
||||||
|
// Eliminates per-pixel HSL→RGBA computation in the waterfall rendering hot path.
|
||||||
|
let _wfLutKey = "";
|
||||||
|
const _wfLut = new Uint8Array(256 * 4); // [r,g,b,a] × 256 entries, 0-255 range
|
||||||
|
|
||||||
|
function ensureWaterfallLut(pal, minDb, maxDb) {
|
||||||
|
const key = `${pal.waterfallHue}|${pal.waterfallSat}|${pal.waterfallLight}|${pal.waterfallAlpha}|${minDb}|${maxDb}|${waterfallGamma}`;
|
||||||
|
if (key === _wfLutKey) return;
|
||||||
|
_wfLutKey = key;
|
||||||
|
for (let i = 0; i < 256; i++) {
|
||||||
|
// i8 range: -128 to 127 (dB values in the spectrum)
|
||||||
|
const db = i < 128 ? i : i - 256;
|
||||||
|
const c = waterfallColorRgba(db, pal, minDb, maxDb);
|
||||||
|
const p = i * 4;
|
||||||
|
_wfLut[p + 0] = (c[0] * 255 + 0.5) | 0;
|
||||||
|
_wfLut[p + 1] = (c[1] * 255 + 0.5) | 0;
|
||||||
|
_wfLut[p + 2] = (c[2] * 255 + 0.5) | 0;
|
||||||
|
_wfLut[p + 3] = (c[3] * 255 + 0.5) | 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast waterfall pixel write using LUT. `db` is the raw i8 bin value.
|
||||||
|
function waterfallLutWrite(texData, offset, db) {
|
||||||
|
// Convert signed i8 to 0-255 LUT index
|
||||||
|
const idx = ((db | 0) + 256) & 0xFF;
|
||||||
|
const p = idx * 4;
|
||||||
|
texData[offset] = _wfLut[p];
|
||||||
|
texData[offset + 1] = _wfLut[p + 1];
|
||||||
|
texData[offset + 2] = _wfLut[p + 2];
|
||||||
|
texData[offset + 3] = _wfLut[p + 3];
|
||||||
|
}
|
||||||
|
|
||||||
function formatFreq(hz) {
|
function formatFreq(hz) {
|
||||||
if (!Number.isFinite(hz)) return "--";
|
if (!Number.isFinite(hz)) return "--";
|
||||||
if (hz >= 1_000_000_000) {
|
if (hz >= 1_000_000_000) {
|
||||||
@@ -2766,6 +2794,8 @@ function updateSpectrumAutoHeight() {
|
|||||||
|
|
||||||
root.style.setProperty("--overview-plot-height", `${nextOverviewHeight}px`);
|
root.style.setProperty("--overview-plot-height", `${nextOverviewHeight}px`);
|
||||||
root.style.setProperty("--spectrum-plot-height", `${nextSpectrumHeight}px`);
|
root.style.setProperty("--spectrum-plot-height", `${nextSpectrumHeight}px`);
|
||||||
|
// Refresh cached canvas sizes after layout change.
|
||||||
|
if (typeof _updateCachedCanvasSizes === "function") _updateCachedCanvasSizes();
|
||||||
if (lastSpectrumData) {
|
if (lastSpectrumData) {
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
scheduleOverviewDraw();
|
scheduleOverviewDraw();
|
||||||
@@ -9014,6 +9044,7 @@ let spectrumRange = 90;
|
|||||||
let waterfallGamma = 1.0;
|
let waterfallGamma = 1.0;
|
||||||
const SPECTRUM_HEADROOM_DB = 20;
|
const SPECTRUM_HEADROOM_DB = 20;
|
||||||
const SPECTRUM_SMOOTH_ALPHA = 0.42;
|
const SPECTRUM_SMOOTH_ALPHA = 0.42;
|
||||||
|
let _spectrumBinBuf = []; // Reusable buffer for SSE bin decoding
|
||||||
|
|
||||||
// Crosshair state (CSS coords relative to spectrum canvas).
|
// Crosshair state (CSS coords relative to spectrum canvas).
|
||||||
let spectrumCrosshairX = null;
|
let spectrumCrosshairX = null;
|
||||||
@@ -9102,9 +9133,14 @@ function pruneSpectrumPeakHoldFrames(now = Date.now()) {
|
|||||||
clearSpectrumPeakHoldFrames();
|
clearSpectrumPeakHoldFrames();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
spectrumPeakHoldFrames = spectrumPeakHoldFrames.filter((frame) => {
|
// In-place removal from front (frames are time-ordered).
|
||||||
return frame && Array.isArray(frame.bins) && now - frame.t <= holdMs;
|
let removeCount = 0;
|
||||||
});
|
for (let i = 0; i < spectrumPeakHoldFrames.length; i++) {
|
||||||
|
const f = spectrumPeakHoldFrames[i];
|
||||||
|
if (f && Array.isArray(f.bins) && now - f.t <= holdMs) break;
|
||||||
|
removeCount++;
|
||||||
|
}
|
||||||
|
if (removeCount > 0) spectrumPeakHoldFrames.splice(0, removeCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushSpectrumPeakHoldFrame(frame) {
|
function pushSpectrumPeakHoldFrame(frame) {
|
||||||
@@ -9144,27 +9180,60 @@ function buildSpectrumPeakHoldBins(currentBins) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Estimate noise floor as the 15th-percentile of visible bins (same heuristic as Auto).
|
// Estimate noise floor as the 15th-percentile of visible bins (same heuristic as Auto).
|
||||||
|
// Uses O(N) nth-element selection instead of O(N log N) sort.
|
||||||
function estimateNoiseFloorDb(bins) {
|
function estimateNoiseFloorDb(bins) {
|
||||||
if (!Array.isArray(bins) || bins.length === 0) return null;
|
if (!Array.isArray(bins) || bins.length === 0) return null;
|
||||||
const sorted = bins.slice().sort((a, b) => a - b);
|
const k = Math.floor(bins.length * 0.15);
|
||||||
return sorted[Math.floor(sorted.length * 0.15)];
|
return nthElement(bins, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// O(N) average-case selection algorithm (Floyd-Rivest / quickselect).
|
||||||
|
function nthElement(arr, k) {
|
||||||
|
const tmp = _nthScratch.length >= arr.length ? _nthScratch : new Float64Array(arr.length);
|
||||||
|
if (tmp.length > _nthScratch.length) _nthScratch = tmp;
|
||||||
|
for (let i = 0; i < arr.length; i++) tmp[i] = arr[i];
|
||||||
|
let lo = 0, hi = arr.length - 1;
|
||||||
|
while (lo < hi) {
|
||||||
|
const pivot = tmp[lo + ((hi - lo) >> 1)];
|
||||||
|
let i = lo, j = hi;
|
||||||
|
while (i <= j) {
|
||||||
|
while (tmp[i] < pivot) i++;
|
||||||
|
while (tmp[j] > pivot) j--;
|
||||||
|
if (i <= j) { const t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t; i++; j--; }
|
||||||
|
}
|
||||||
|
if (j < k) lo = i;
|
||||||
|
if (k < i) hi = j;
|
||||||
|
}
|
||||||
|
return tmp[k];
|
||||||
|
}
|
||||||
|
let _nthScratch = new Float64Array(0);
|
||||||
|
|
||||||
|
// Pre-allocated buffer for smoothed spectrum bins (avoids .map() allocation per frame).
|
||||||
|
let _smoothBins = [];
|
||||||
|
|
||||||
function buildSpectrumRenderData(frame) {
|
function buildSpectrumRenderData(frame) {
|
||||||
if (!frame || !Array.isArray(frame.bins)) return frame;
|
if (!frame || !Array.isArray(frame.bins)) return frame;
|
||||||
|
const n = frame.bins.length;
|
||||||
const prev = lastSpectrumRenderData;
|
const prev = lastSpectrumRenderData;
|
||||||
const canBlend =
|
const canBlend =
|
||||||
prev &&
|
prev &&
|
||||||
Array.isArray(prev.bins) &&
|
Array.isArray(prev.bins) &&
|
||||||
prev.bins.length === frame.bins.length &&
|
prev.bins.length === n &&
|
||||||
prev.sample_rate === frame.sample_rate &&
|
prev.sample_rate === frame.sample_rate &&
|
||||||
prev.center_hz === frame.center_hz;
|
prev.center_hz === frame.center_hz;
|
||||||
const bins = frame.bins.map((value, idx) => {
|
if (_smoothBins.length !== n) _smoothBins = new Array(n);
|
||||||
if (!canBlend) return value;
|
const src = frame.bins;
|
||||||
const prevValue = prev.bins[idx];
|
if (canBlend) {
|
||||||
return prevValue + (value - prevValue) * SPECTRUM_SMOOTH_ALPHA;
|
const prevBins = prev.bins;
|
||||||
});
|
const alpha = SPECTRUM_SMOOTH_ALPHA;
|
||||||
return { ...frame, bins };
|
for (let i = 0; i < n; i++) {
|
||||||
|
_smoothBins[i] = prevBins[i] + (src[i] - prevBins[i]) * alpha;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < n; i++) _smoothBins[i] = src[i];
|
||||||
|
}
|
||||||
|
// Return object reusing the frame's metadata.
|
||||||
|
return { bins: _smoothBins, center_hz: frame.center_hz, sample_rate: frame.sample_rate, rds: frame.rds };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns { loHz, hiHz, visLoHz, visHiHz, fullSpanHz, visSpanHz } and clamps
|
// Returns { loHz, hiHz, visLoHz, visHiHz, fullSpanHz, visSpanHz } and clamps
|
||||||
@@ -9353,8 +9422,10 @@ function startSpectrumStreaming() {
|
|||||||
const b64 = evt.data.slice(commaB + 1);
|
const b64 = evt.data.slice(commaB + 1);
|
||||||
const hadSpectrum = !!lastSpectrumData;
|
const hadSpectrum = !!lastSpectrumData;
|
||||||
const raw = atob(b64);
|
const raw = atob(b64);
|
||||||
const bins = new Array(raw.length);
|
const len = raw.length;
|
||||||
for (let i = 0; i < raw.length; i++) bins[i] = (raw.charCodeAt(i) << 24 >> 24);
|
if (_spectrumBinBuf.length !== len) _spectrumBinBuf = new Array(len);
|
||||||
|
const bins = _spectrumBinBuf;
|
||||||
|
for (let i = 0; i < len; i++) bins[i] = (raw.charCodeAt(i) << 24 >> 24);
|
||||||
// Preserve any RDS data from the last rds event.
|
// Preserve any RDS data from the last rds event.
|
||||||
const rds = lastSpectrumData?.rds;
|
const rds = lastSpectrumData?.rds;
|
||||||
lastSpectrumData = { bins, center_hz: centerHz, sample_rate: sampleRate, rds };
|
lastSpectrumData = { bins, center_hz: centerHz, sample_rate: sampleRate, rds };
|
||||||
@@ -9718,9 +9789,9 @@ function scheduleSpectrumDraw() {
|
|||||||
function drawSpectrum(data) {
|
function drawSpectrum(data) {
|
||||||
if (!spectrumCanvas || !spectrumGl || !spectrumGl.ready) return;
|
if (!spectrumCanvas || !spectrumGl || !spectrumGl.ready) return;
|
||||||
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = _cachedDpr;
|
||||||
const cssW = spectrumCanvas.clientWidth || 640;
|
const cssW = _cachedSpectrumCssW;
|
||||||
const cssH = spectrumCanvas.clientHeight || 160;
|
const cssH = _cachedSpectrumCssH;
|
||||||
spectrumGl.ensureSize(cssW, cssH, dpr);
|
spectrumGl.ensureSize(cssW, cssH, dpr);
|
||||||
const W = spectrumCanvas.width;
|
const W = spectrumCanvas.width;
|
||||||
const H = spectrumCanvas.height;
|
const H = spectrumCanvas.height;
|
||||||
@@ -9808,33 +9879,30 @@ function drawSpectrum(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Zoom indicator ──
|
// ── Zoom indicator ──
|
||||||
const zoomEl = document.getElementById("spectrum-zoom-indicator");
|
if (_spectrumZoomEl) {
|
||||||
if (zoomEl) {
|
|
||||||
if (spectrumZoom > 1.01) {
|
if (spectrumZoom > 1.01) {
|
||||||
zoomEl.textContent = spectrumZoom.toFixed(1) + "x";
|
_spectrumZoomEl.textContent = spectrumZoom.toFixed(1) + "x";
|
||||||
zoomEl.style.display = "block";
|
_spectrumZoomEl.style.display = "block";
|
||||||
} else {
|
} else {
|
||||||
zoomEl.style.display = "none";
|
_spectrumZoomEl.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Zoom minimap ──
|
// ── Zoom minimap ──
|
||||||
const minimapEl = document.getElementById("spectrum-minimap");
|
if (_spectrumMinimapEl) {
|
||||||
if (minimapEl) {
|
|
||||||
if (spectrumZoom > 1.01) {
|
if (spectrumZoom > 1.01) {
|
||||||
minimapEl.style.display = "block";
|
_spectrumMinimapEl.style.display = "block";
|
||||||
const viewFrac = 1 / spectrumZoom;
|
const viewFrac = 1 / spectrumZoom;
|
||||||
const halfVis = viewFrac / 2;
|
const halfVis = viewFrac / 2;
|
||||||
const panClamped = Math.min(Math.max(spectrumPanFrac, halfVis), 1 - halfVis);
|
const panClamped = Math.min(Math.max(spectrumPanFrac, halfVis), 1 - halfVis);
|
||||||
const viewL = panClamped - halfVis;
|
const viewL = panClamped - halfVis;
|
||||||
const viewR = panClamped + halfVis;
|
const viewR = panClamped + halfVis;
|
||||||
const inner = minimapEl.querySelector(".minimap-view");
|
if (_spectrumMinimapInner) {
|
||||||
if (inner) {
|
_spectrumMinimapInner.style.left = (viewL * 100) + "%";
|
||||||
inner.style.left = (viewL * 100) + "%";
|
_spectrumMinimapInner.style.width = ((viewR - viewL) * 100) + "%";
|
||||||
inner.style.width = ((viewR - viewL) * 100) + "%";
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
minimapEl.style.display = "none";
|
_spectrumMinimapEl.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9859,6 +9927,32 @@ let spectrumWfTexReady = false;
|
|||||||
let spectrumWfDrawPending = false;
|
let spectrumWfDrawPending = false;
|
||||||
const SPECTRUM_WF_TEX_MAX_W = 1024;
|
const SPECTRUM_WF_TEX_MAX_W = 1024;
|
||||||
|
|
||||||
|
// Cached DOM references for drawSpectrum (avoid getElementById per frame).
|
||||||
|
const _spectrumZoomEl = document.getElementById("spectrum-zoom-indicator");
|
||||||
|
const _spectrumMinimapEl = document.getElementById("spectrum-minimap");
|
||||||
|
const _spectrumMinimapInner = _spectrumMinimapEl ? _spectrumMinimapEl.querySelector(".minimap-view") : null;
|
||||||
|
|
||||||
|
// Cached canvas dimensions (updated on resize instead of reading clientWidth/clientHeight per frame).
|
||||||
|
let _cachedSpectrumCssW = 640, _cachedSpectrumCssH = 160;
|
||||||
|
let _cachedSpecWfCssW = 640, _cachedSpecWfCssH = 120;
|
||||||
|
let _cachedDpr = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
function _updateCachedCanvasSizes() {
|
||||||
|
_cachedDpr = window.devicePixelRatio || 1;
|
||||||
|
if (spectrumCanvas) {
|
||||||
|
_cachedSpectrumCssW = spectrumCanvas.clientWidth || 640;
|
||||||
|
_cachedSpectrumCssH = spectrumCanvas.clientHeight || 160;
|
||||||
|
}
|
||||||
|
if (spectrumWaterfallCanvas) {
|
||||||
|
_cachedSpecWfCssW = spectrumWaterfallCanvas.clientWidth || 640;
|
||||||
|
_cachedSpecWfCssH = spectrumWaterfallCanvas.clientHeight || 120;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Refresh on resize; also called from scheduleSpectrumLayout.
|
||||||
|
window.addEventListener("resize", _updateCachedCanvasSizes);
|
||||||
|
// Initial read.
|
||||||
|
_updateCachedCanvasSizes();
|
||||||
|
|
||||||
function pushSpectrumWaterfallFrame(data) {
|
function pushSpectrumWaterfallFrame(data) {
|
||||||
if (!spectrumWaterfallCanvas || !data || !Array.isArray(data.bins) || data.bins.length === 0) return;
|
if (!spectrumWaterfallCanvas || !data || !Array.isArray(data.bins) || data.bins.length === 0) return;
|
||||||
spectrumWfRows.push(data.bins.slice());
|
spectrumWfRows.push(data.bins.slice());
|
||||||
@@ -9869,10 +9963,9 @@ function pushSpectrumWaterfallFrame(data) {
|
|||||||
|
|
||||||
function trimSpectrumWaterfallRows() {
|
function trimSpectrumWaterfallRows() {
|
||||||
if (!spectrumWaterfallCanvas) return;
|
if (!spectrumWaterfallCanvas) return;
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const maxRows = Math.max(1, Math.floor(_cachedSpecWfCssH * _cachedDpr));
|
||||||
const maxRows = Math.max(1, Math.floor((spectrumWaterfallCanvas.clientHeight || 120) * dpr));
|
if (spectrumWfRows.length > maxRows) {
|
||||||
while (spectrumWfRows.length > maxRows) {
|
spectrumWfRows.splice(0, spectrumWfRows.length - maxRows);
|
||||||
spectrumWfRows.shift();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9889,9 +9982,9 @@ function drawSpectrumWaterfall() {
|
|||||||
if (!spectrumWaterfallCanvas || !spectrumWaterfallGl || !spectrumWaterfallGl.ready) return;
|
if (!spectrumWaterfallCanvas || !spectrumWaterfallGl || !spectrumWaterfallGl.ready) return;
|
||||||
if (!lastSpectrumData || spectrumWfRows.length === 0) return;
|
if (!lastSpectrumData || spectrumWfRows.length === 0) return;
|
||||||
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = _cachedDpr;
|
||||||
const cssW = spectrumWaterfallCanvas.clientWidth || 640;
|
const cssW = _cachedSpecWfCssW;
|
||||||
const cssH = spectrumWaterfallCanvas.clientHeight || 120;
|
const cssH = _cachedSpecWfCssH;
|
||||||
spectrumWaterfallGl.ensureSize(cssW, cssH, dpr);
|
spectrumWaterfallGl.ensureSize(cssW, cssH, dpr);
|
||||||
const W = spectrumWaterfallCanvas.width;
|
const W = spectrumWaterfallCanvas.width;
|
||||||
const H = spectrumWaterfallCanvas.height;
|
const H = spectrumWaterfallCanvas.height;
|
||||||
@@ -9923,20 +10016,17 @@ function drawSpectrumWaterfall() {
|
|||||||
spectrumWfTexWidth = iW;
|
spectrumWfTexWidth = iW;
|
||||||
spectrumWfTexHeight = iH;
|
spectrumWfTexHeight = iH;
|
||||||
|
|
||||||
|
ensureWaterfallLut(pal, minDb, maxDb);
|
||||||
|
|
||||||
function renderRow(dstY, srcBins) {
|
function renderRow(dstY, srcBins) {
|
||||||
if (!Array.isArray(srcBins) || srcBins.length === 0) return;
|
if (!Array.isArray(srcBins) || srcBins.length === 0) return;
|
||||||
const { startIdx, endIdx } = overviewVisibleBinWindow(lastSpectrumData, srcBins.length);
|
const { startIdx, endIdx } = overviewVisibleBinWindow(lastSpectrumData, srcBins.length);
|
||||||
const spanBins = Math.max(1, endIdx - startIdx);
|
const spanBins = Math.max(1, endIdx - startIdx);
|
||||||
const rowBase = dstY * rowStride;
|
const rowBase = dstY * rowStride;
|
||||||
|
const iwM1 = Math.max(1, iW - 1);
|
||||||
for (let x = 0; x < iW; x++) {
|
for (let x = 0; x < iW; x++) {
|
||||||
const frac = x / Math.max(1, iW - 1);
|
const binIdx = Math.min(endIdx, startIdx + ((x * spanBins / iwM1) | 0));
|
||||||
const binIdx = Math.min(endIdx, startIdx + Math.floor(frac * spanBins));
|
waterfallLutWrite(spectrumWfTexData, rowBase + x * 4, srcBins[binIdx]);
|
||||||
const c = waterfallColorRgba(srcBins[binIdx], pal, minDb, maxDb);
|
|
||||||
const p = rowBase + x * 4;
|
|
||||||
spectrumWfTexData[p + 0] = Math.round(c[0] * 255);
|
|
||||||
spectrumWfTexData[p + 1] = Math.round(c[1] * 255);
|
|
||||||
spectrumWfTexData[p + 2] = Math.round(c[2] * 255);
|
|
||||||
spectrumWfTexData[p + 3] = Math.round(c[3] * 255);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user