From d82db7e301ff778b3c71a409263d696d9f561b74 Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 28 Jan 2026 21:34:59 +0100 Subject: fix the spec editor a bit --- tools/editor/dct.js | 2 + tools/editor/index.html | 2 + tools/editor/script.js | 113 ++++++------------------------------------------ 3 files changed, 17 insertions(+), 100 deletions(-) (limited to 'tools') diff --git a/tools/editor/dct.js b/tools/editor/dct.js index f60f27e..e48ce2b 100644 --- a/tools/editor/dct.js +++ b/tools/editor/dct.js @@ -1,3 +1,5 @@ +const dctSize = 512; // Default DCT size, read from header + // --- Utility Functions for Audio Processing --- // JavaScript equivalent of C++ idct_512 function javascript_idct_512(input) { diff --git a/tools/editor/index.html b/tools/editor/index.html index 6cb6af7..7c977e3 100644 --- a/tools/editor/index.html +++ b/tools/editor/index.html @@ -29,6 +29,8 @@ + + diff --git a/tools/editor/script.js b/tools/editor/script.js index aac52ff..abfd4f4 100644 --- a/tools/editor/script.js +++ b/tools/editor/script.js @@ -4,7 +4,6 @@ // --- Global Variables --- let currentSpecData = null; // Stores the currently displayed/edited spectrogram data let originalSpecData = null; // Stores the pristine, initially loaded spectrogram data -let dctSize = 512; // Default DCT size, read from header let undoStack = []; let redoStack = []; @@ -60,74 +59,6 @@ listenGeneratedButton.addEventListener('click', () => { }); -// --- Utility Functions for Audio Processing --- -// JavaScript equivalent of C++ idct_512 -function javascript_idct_512(input) { - const output = new Float32Array(dctSize); - const PI = Math.PI; - const N = dctSize; - - for (let n = 0; n < N; ++n) { - let sum = input[0] / 2.0; - for (let k = 1; k < N; ++k) { - sum += input[k] * Math.cos((PI / N) * k * (n + 0.5)); - } - output[n] = sum * (2.0 / N); - } - return output; -} - -// Hanning window for smooth audio transitions (JavaScript equivalent) -function hanningWindow(size) { - const window = new Float32Array(size); - const PI = Math.PI; - for (let i = 0; i < size; i++) { - window[i] = 0.5 * (1 - Math.cos((2 * PI * i) / (size - 1))); - } - return window; -} - -const hanningWindowArray = hanningWindow(dctSize); // Pre-calculate window - -// --- Signed Distance Functions (SDFs) --- -// Generic 2D vector operations -function vec2(x, y) { return { x: x, y: y }; } -function length(v) { return Math.sqrt(v.x * v.x + v.y * v.y); } -function dot(v1, v2) { return v1.x * v2.x + v1.y * v2.y; } -function sub(v1, v2) { return vec2(v1.x - v2.x, v1.y - v2.y); } -function mul(v, s) { return vec2(v.x * s, v.y * s); } -function div(v, s) { return vec2(v.x / s, v.y / s); } -function normalize(v) { return div(v, length(v)); } -function clamp(x, minVal, maxVal) { return Math.max(minVal, Math.min(x, maxVal)); } -function abs(v) { return vec2(Math.abs(v.x), Math.abs(v.y)); } -function max(v1, v2) { return vec2(Math.max(v1.x, v2.x), Math.max(v1.y, v2.y)); } -function sign(x) { return (x > 0) ? 1 : ((x < 0) ? -1 : 0); } - -// sdSegment(p, a, b) - signed distance to a line segment -// p: point, a: segment start, b: segment end -function sdSegment(p, a, b) { - const pa = sub(p, a); - const ba = sub(b, a); - const h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); - return length(sub(pa, mul(ba, h))); -} - -// sdEllipse(p, r) - signed distance to an ellipse (p relative to center, r is half-extents) -// p: point relative to ellipse center, r: half-extents (rx, ry) -function sdEllipse(p, r) { - const k0 = vec2(1, length(div(p, r))); - const k1 = vec2(length(div(p, r)), 1); - const f = ((dot(div(mul(p, p), k0), vec2(1, 1)) < dot(div(mul(p, p), k1), vec2(1, 1))) ? k0 : k1); - return length(sub(p, mul(r, normalize(mul(f, p))))) * sign(length(p) - r.x); // Simplified, original has length(p)-r.x which is only for circular -} - -// sdBox(p, r) - signed distance to a rectangle (p relative to center, r is half-extents) -// p: point relative to box center, r: half-extents (hx, hy) -function sdBox(p, r) { - const q = sub(abs(p), r); - return length(max(q, vec2(0, 0))) + Math.min(0.0, Math.max(q.x, q.y)); -} - // --- Utility to map canvas coords to spectrogram bins/frames (LOG SCALE) --- // Maps a linear frequency bin index to its corresponding frequency in Hz function binIndexToFreq(binIndex) { @@ -211,17 +142,6 @@ window.addEventListener('resize', () => { // Initial call to set button states updateUndoRedoButtons(); -// --- Utility for sizeof(float) in JS context --- -// This is a workaround since typeof(float) is not directly available. -// Float32Array.BYTES_PER_ELEMENT is used in handleFileSelect. -function sizeof(type) { - if (type === 'float') { - return Float32Array.BYTES_PER_ELEMENT; - } - return 0; -} - - // --- File Handling Functions --- async function handleFileSelect(event) { const file = event.target.files[0]; @@ -247,7 +167,10 @@ async function handleFileSelect(event) { return; } - dctSize = header.dct_size; + if (dctSize != header.dct_size) { + alert("Invalid dctSize in SPEC file"); + return; + } const dataStart = 16; const numBytes = header.num_frames * header.dct_size * Float32Array.BYTES_PER_ELEMENT; const spectralDataFloat = new Float32Array(buffer, dataStart, header.num_frames * header.dct_size); @@ -281,9 +204,10 @@ canvas.addEventListener('mouseout', handleMouseUp); // Treat mouse out as mouse // Function to get a color based on intensity (0 to 1) function getColorForIntensity(intensity) { // Example: Blue to white/yellow gradient - const h = (1 - intensity) * 240; // Hue from blue (240) to red (0), inverse for intensity - const s = 100; // Saturation - const l = intensity * 50 + 50; // Lightness from 50 to 100 + const log_intensity = Math.log(1. + intensity) / Math.log(2.); + const h = (1 - log_intensity) * 240; // Hue from blue (240) to red (0), inverse for intensity + const s = 60.; // Saturation + const l = log_intensity * 60 + 30; // Lightness from 30 to 90 return `hsl(${h}, ${s}%, ${l}%)`; } @@ -410,6 +334,10 @@ function handleMouseMove(event) { break; } ctx.setLineDash([]); // Reset line dash + + // debug the mouse position by draw a white square + ctx.fillStyle = '#fff'; + ctx.fillRect(pos.x - 10, pos.y - 10, 20, 20); } function handleMouseUp(event) { @@ -700,8 +628,6 @@ async function playSpectrogramData(specData) { const audioBuffer = audioContext.createBuffer(1, totalAudioSamples, sampleRate); const audioData = audioBuffer.getChannelData(0); // Mono channel - const windowArray = hanningWindow(dctSize); // Generate Hanning window for each frame - // Convert spectrogram frames (frequency domain) to audio samples (time domain) for (let frameIndex = 0; frameIndex < numFrames; frameIndex++) { const spectralFrame = specData.data.slice(frameIndex * dctSize, (frameIndex + 1) * dctSize); @@ -709,10 +635,7 @@ async function playSpectrogramData(specData) { // Apply Hanning window for smooth transitions for (let i = 0; i < dctSize; i++) { - const globalIndex = frameIndex * dctSize + i; - if (globalIndex < totalAudioSamples) { - audioData[globalIndex] += timeDomainFrame[i] * windowArray[i]; - } + audioData[frameIndex * dctSize + i] = timeDomainFrame[i] * hanningWindowArray[i]; } } @@ -723,13 +646,3 @@ async function playSpectrogramData(specData) { console.log(`Playing audio (Sample Rate: ${sampleRate}, Duration: ${audioBuffer.duration.toFixed(2)}s)`); } - -// --- Utility for sizeof(float) in JS context --- -// This is a workaround since typeof(float) is not directly available. -// Float32Array.BYTES_PER_ELEMENT is used in handleFileSelect. -function sizeof(type) { - if (type === 'float') { - return Float32Array.BYTES_PER_ELEMENT; - } - return 0; -} \ No newline at end of file -- cgit v1.2.3