From a0dd0a27c4d6831fb2fb5ad81283f36512ef16ef Mon Sep 17 00:00:00 2001 From: skal Date: Sat, 7 Feb 2026 15:07:01 +0100 Subject: update doc, optimize spectral_editor --- tools/spectral_editor/script.js | 140 ++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 91 deletions(-) (limited to 'tools/spectral_editor') diff --git a/tools/spectral_editor/script.js b/tools/spectral_editor/script.js index 392d3a5..cafd7e9 100644 --- a/tools/spectral_editor/script.js +++ b/tools/spectral_editor/script.js @@ -1083,18 +1083,7 @@ function drawReferenceSpectrogram(ctx) { // Sample spectrogram const specValue = state.referenceSpectrogram[frameIdx * state.referenceDctSize + bin]; - - // Logarithmic intensity mapping (dB scale) - // Maps wide dynamic range to visible range - const amplitude = Math.abs(specValue); - let intensity = 0; - if (amplitude > 0.0001) { // Noise floor - const dB = 20.0 * Math.log10(amplitude); - const dB_min = -60.0; // Noise floor (-60 dB) - const dB_max = 40.0; // Peak (40 dB headroom) - const normalized = (dB - dB_min) / (dB_max - dB_min); - intensity = Math.floor(Math.max(0, Math.min(255, normalized * 255))); - } + const intensity = amp_to_dB(specValue, 255); // Write pixel const pixelIdx = (screenY * state.canvasWidth + screenX) * 4; @@ -1153,24 +1142,13 @@ function drawProceduralSpectrogram(ctx) { const specValue = curveSpec[frameIdx * state.referenceDctSize + bin]; // Logarithmic intensity mapping with steeper falloff for procedural curves - const amplitude = Math.abs(specValue); - let intensity = 0.0; - if (amplitude > 0.001) { // Higher noise floor for cleaner visualization - const dB = 20.0 * Math.log10(amplitude); - const dB_min = -40.0; // Higher floor = steeper falloff (was -60) - const dB_max = 40.0; // Peak - const normalized = (dB - dB_min) / (dB_max - dB_min); - intensity = Math.max(0, Math.min(1.0, normalized)); // 0.0 to 1.0 - } - - if (intensity > 0.01) { // Only draw visible pixels - const pixelIdx = (screenY * state.canvasWidth + screenX) * 4; - // Use constant color with alpha for intensity (pure colors) - imgData.data[pixelIdx + 0] = color.r; - imgData.data[pixelIdx + 1] = color.g; - imgData.data[pixelIdx + 2] = color.b; - imgData.data[pixelIdx + 3] = Math.floor(intensity * 255); // Alpha = intensity - } + const intensity = amp_to_dB(specValue, 255.); + const pixelIdx = (screenY * state.canvasWidth + screenX) * 4; + // Use constant color with alpha for intensity (pure colors) + imgData.data[pixelIdx + 0] = color.r; + imgData.data[pixelIdx + 1] = color.g; + imgData.data[pixelIdx + 2] = color.b; + imgData.data[pixelIdx + 3] = intensity; } } @@ -1468,6 +1446,19 @@ function updatePlayhead() { requestAnimationFrame(updatePlayhead); } +// Logarithmic intensity mapping (dB scale) +// Maps wide dynamic range to visible range +function amp_to_dB(amplitude, max_value) { + amplitude = Math.abs(amplitude); + if (amplitude < 0.0001) return 0; // noise floor + const dB_min = -40.0; // Noise floor + const dB_max = 40.0; // Peak (40 dB headroom) + const dB_scale = max_value * (1. / (dB_max - dB_min)); + const dB = 20.0 * Math.log10(amplitude); + const normalized = (dB - dB_min) * dB_scale; + return Math.floor(Math.max(0, Math.min(max_value, normalized))); +} + function drawSpectrumViewer() { const viewer = document.getElementById('spectrumViewer'); const canvas = document.getElementById('spectrumCanvas'); @@ -1485,7 +1476,7 @@ function drawSpectrumViewer() { frameIdx = state.mouseFrame; } - if (frameIdx < 0 || frameIdx >= (state.referenceNumFrames || 100)) return; + if (frameIdx < 0 || frameIdx >= state.referenceNumFrames) return; // Clear canvas ctx.fillStyle = '#1e1e1e'; @@ -1494,17 +1485,37 @@ function drawSpectrumViewer() { const numBars = 100; // Downsample to 100 bars for performance const barWidth = canvas.width / numBars; + // Draw spectrum bars (both reference and procedural overlaid) + function draw_spectrum(spectrum, is_ref_spectrum) { + if (!spectrum) return; + for (let i = 0; i < numBars; i++) { + const binIdx = Math.floor(i * state.referenceDctSize / numBars); + const height = amp_to_dB(spectrum[binIdx], canvas.height); + const gradient = ctx.createLinearGradient(0, canvas.height - height, 0, canvas.height); + if (is_ref_spectrum) { + // Draw reference spectrum (green, behind) + gradient.addColorStop(0, '#00ff00'); + gradient.addColorStop(1, '#004400'); + } else { + // Draw procedural spectrum (red, overlaid) + gradient.addColorStop(0, '#ff5555'); // Bright red + gradient.addColorStop(1, '#550000'); // Dark red + ctx.globalAlpha = 0.7; + } + ctx.fillStyle = gradient; + ctx.fillRect(i * barWidth, canvas.height - height, barWidth, height); + ctx.globalAlpha = 1.0; + } + } + + const size = state.referenceDctSize; + const pos = frameIdx * size; // Get reference spectrum (if available) - let refSpectrum = null; if (state.referenceSpectrogram && frameIdx < state.referenceNumFrames) { - refSpectrum = new Float32Array(state.referenceDctSize); - for (let bin = 0; bin < state.referenceDctSize; bin++) { - refSpectrum[bin] = state.referenceSpectrogram[frameIdx * state.referenceDctSize + bin]; - } + draw_spectrum(state.referenceSpectrogram.subarray(pos, pos + size), true); } // Get procedural spectrum (if curves exist) - let procSpectrum = null; if (state.curves.length > 0) { const numFrames = state.referenceNumFrames || 100; const fullProcSpec = new Float32Array(state.referenceDctSize * numFrames); @@ -1513,60 +1524,7 @@ function drawSpectrumViewer() { }); // Extract just this frame - procSpectrum = new Float32Array(state.referenceDctSize); - for (let bin = 0; bin < state.referenceDctSize; bin++) { - procSpectrum[bin] = fullProcSpec[frameIdx * state.referenceDctSize + bin]; - } - } - - // Draw spectrum bars (both reference and procedural overlaid) - for (let i = 0; i < numBars; i++) { - const binIdx = Math.floor(i * state.referenceDctSize / numBars); - - // Draw reference spectrum (green, behind) - if (refSpectrum) { - const amplitude = Math.abs(refSpectrum[binIdx]); - let height = 0; - if (amplitude > 0.0001) { - const dB = 20.0 * Math.log10(amplitude); - const dB_min = -60.0; - const dB_max = 40.0; - const normalized = (dB - dB_min) / (dB_max - dB_min); - height = Math.max(0, Math.min(canvas.height, normalized * canvas.height)); - } - - if (height > 0) { - const gradient = ctx.createLinearGradient(0, canvas.height - height, 0, canvas.height); - gradient.addColorStop(0, '#00ff00'); - gradient.addColorStop(1, '#004400'); - ctx.fillStyle = gradient; - ctx.fillRect(i * barWidth, canvas.height - height, barWidth - 1, height); - } - } - - // Draw procedural spectrum (red, overlaid) - if (procSpectrum) { - const amplitude = Math.abs(procSpectrum[binIdx]); - let height = 0; - if (amplitude > 0.001) { - const dB = 20.0 * Math.log10(amplitude); - const dB_min = -40.0; // Same as procedural spectrogram rendering - const dB_max = 40.0; - const normalized = (dB - dB_min) / (dB_max - dB_min); - height = Math.max(0, Math.min(canvas.height, normalized * canvas.height)); - } - - if (height > 0) { - const gradient = ctx.createLinearGradient(0, canvas.height - height, 0, canvas.height); - gradient.addColorStop(0, '#ff5555'); // Bright red - gradient.addColorStop(1, '#550000'); // Dark red - ctx.fillStyle = gradient; - // Make it slightly transparent to see overlap - ctx.globalAlpha = 0.7; - ctx.fillRect(i * barWidth, canvas.height - height, barWidth - 1, height); - ctx.globalAlpha = 1.0; - } - } + draw_spectrum(fullProcSpec.subarray(pos, pos + size), false); } // Draw frequency labels -- cgit v1.2.3