diff options
Diffstat (limited to 'tools/editor/script.js')
| -rw-r--r-- | tools/editor/script.js | 123 |
1 files changed, 107 insertions, 16 deletions
diff --git a/tools/editor/script.js b/tools/editor/script.js index 48baa2e..d781172 100644 --- a/tools/editor/script.js +++ b/tools/editor/script.js @@ -20,7 +20,7 @@ let shapes = []; // Array to store all drawn shapes (lines, ellipses, etc.) const audioContext = new (window.AudioContext || window.webkitAudioContext)(); // Audio Constants (should match C++ side) -const SAMPLE_RATE = 32000; +const SAMPLE_RATE = 32000; const MAX_FREQ = SAMPLE_RATE / 2; // Nyquist frequency const MIN_FREQ = 20; // Lower bound for log scale visualization @@ -32,7 +32,7 @@ const lineToolButton = document.getElementById('lineTool'); const ellipseToolButton = document.getElementById('ellipseTool'); const noiseToolButton = document.getElementById('noiseTool'); const undoButton = document.getElementById('undoButton'); -const redoButton = document.getElementById('redoButton'); +const redoButton = document.getElementById('redoButton'); const listenOriginalButton = document.getElementById('listenOriginalButton'); const listenGeneratedButton = document.getElementById('listenGeneratedButton'); @@ -199,6 +199,29 @@ function canvasDeltaYToFreqDeltaLog(canvasDeltaY, canvasHeight) { return Math.abs(freqAtCenterPlusDelta - freqAtCenter); } +// Initial setup for canvas size (can be updated on window resize) +window.addEventListener('resize', () => { + if (originalSpecData) { + canvas.width = window.innerWidth * 0.7; + canvas.height = 400; // Fixed height + redrawCanvas(); + } +}); + +// 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]; @@ -265,7 +288,7 @@ function getColorForIntensity(intensity) { } function drawSpectrogram(specData) { - const width = canvas.width; + const width = canvas.width; const height = canvas.height; ctx.clearRect(0, 0, width, height); @@ -293,7 +316,7 @@ function drawSpectrogram(specData) { const value = specData.data[frameDataStart + binIndex]; const intensity = Math.min(1, Math.abs(value) / 1.0); // Assuming values are normalized to [-1, 1] - + ctx.fillStyle = getColorForIntensity(intensity); ctx.fillRect(xPos, height - y - 1, frameWidth, 1); // Draw a 1-pixel height line for each y } @@ -309,7 +332,7 @@ function drawShape(shape) { // This draws the final, persistent shape. Preview is drawn in handleMouseMove. ctx.strokeStyle = shape.color || 'red'; ctx.lineWidth = shape.width || 2; - + switch (shape.type) { case 'line': ctx.beginPath(); @@ -351,7 +374,7 @@ function handleMouseDown(event) { function handleMouseMove(event) { if (!isDrawing || !activeTool) return; const pos = getMousePos(event); - + redrawCanvas(); // Clear and redraw persistent state ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)'; // Preview color @@ -587,9 +610,9 @@ function applyShapeToSpectrogram(shape, targetSpecData) { function addAction(action) { undoStack.push(action); if (undoStack.length > MAX_HISTORY_SIZE) { - undoStack.shift(); + undoStack.shift(); } - redoStack = []; + redoStack = []; updateUndoRedoButtons(); } @@ -599,9 +622,9 @@ function handleUndo() { return; } const actionToUndo = undoStack.pop(); - actionToUndo.undo(); + actionToUndo.undo(); redoStack.push(actionToUndo); - redrawCanvas(); + redrawCanvas(); updateUndoRedoButtons(); } @@ -612,9 +635,9 @@ function handleRedo() { } const actionToRedo = redoStack.pop(); - actionToRedo.redo(); + actionToRedo.redo(); undoStack.push(actionToRedo); - redrawCanvas(); + redrawCanvas(); updateUndoRedoButtons(); } @@ -628,17 +651,85 @@ function redrawCanvas() { } // Start with a fresh copy of the original data - currentSpecData.data = new Float32Array(originalSpecData.data); + currentSpecData.data = new Float32Array(originalSpecData.data); // Replay all shapes from the `shapes` array to `currentSpecData` shapes.forEach(shape => { - applyShapeToSpectrogram(shape, currentSpecData); + applyShapeToSpectrogram(shape, currentSpecData); }); - drawSpectrogram(currentSpecData); + drawSpectrogram(currentSpecData); } function updateUndoRedoButtons() { undoButton.disabled = undoStack.length === 0; - redoButton.disabled = redoStack.length === 0; + redoButton.disabled = redoStack.length === 0; +} + +// Initial setup for canvas size (can be updated on window resize) +window.addEventListener('resize', () => { + if (originalSpecData) { + canvas.width = window.innerWidth * 0.7; + canvas.height = 400; // Fixed height + redrawCanvas(); + } +}); + +// Initial call to set button states +updateUndoRedoButtons(); + +// --- Audio Playback Functions --- +let currentAudioSource = null; // To stop currently playing audio + +async function playSpectrogramData(specData) { + if (!specData || !specData.data || specData.header.num_frames === 0) { + alert("No spectrogram data to play."); + return; + } + + if (currentAudioSource) { + currentAudioSource.stop(); + currentAudioSource.disconnect(); + currentAudioSource = null; + } + + const sampleRate = SAMPLE_RATE; // Fixed sample rate + const numFrames = specData.header.num_frames; + const totalAudioSamples = numFrames * dctSize; // Total samples in time domain + + 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); + const timeDomainFrame = javascript_idct_512(spectralFrame); + + // 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]; + } + } + } + + currentAudioSource = audioContext.createBufferSource(); + currentAudioSource.buffer = audioBuffer; + currentAudioSource.connect(audioContext.destination); + currentAudioSource.start(); + + 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 |
