summaryrefslogtreecommitdiff
path: root/tools/editor/script.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/editor/script.js')
-rw-r--r--tools/editor/script.js123
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