summaryrefslogtreecommitdiff
path: root/tools/editor
diff options
context:
space:
mode:
Diffstat (limited to 'tools/editor')
-rw-r--r--tools/editor/script.js169
1 files changed, 123 insertions, 46 deletions
diff --git a/tools/editor/script.js b/tools/editor/script.js
index 0926ea4..c764803 100644
--- a/tools/editor/script.js
+++ b/tools/editor/script.js
@@ -19,6 +19,11 @@ let shapes = []; // Array to store all drawn shapes (lines, ellipses, etc.)
// Web Audio Context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+// Audio Constants (should match C++ side)
+const SAMPLE_RATE = 32000;
+const MAX_FREQ = SAMPLE_RATE / 2; // Nyquist frequency
+const MIN_FREQ = 20; // Lower bound for log scale visualization
+
// --- Utility Functions for Audio Processing ---
// JavaScript equivalent of C++ idct_512
function javascript_idct_512(input) {
@@ -130,27 +135,24 @@ function drawSpectrogram(specData) {
}
const numFrames = specData.header.num_frames;
- const binHeight = height / dctSize; // Height of each frequency bin
const frameWidth = width / numFrames; // Width of each time frame
- // Find max value for normalization (for better visualization)
- let maxAbsValue = 0;
- for (let i = 0; i < specData.data.length; i++) {
- maxAbsValue = Math.max(maxAbsValue, Math.abs(specData.data[i]));
- }
- if (maxAbsValue === 0) maxAbsValue = 1; // Avoid division by zero
-
- // Draw each frame's spectral data
+ // Draw each frame's spectral data with log frequency scale
for (let frameIndex = 0; frameIndex < numFrames; frameIndex++) {
const frameDataStart = frameIndex * dctSize;
const xPos = frameIndex * frameWidth;
- for (let binIndex = 0; binIndex < dctSize; binIndex++) {
+ // To draw with log scale, we iterate over canvas y-coordinates
+ // and map them back to frequency bins
+ for (let y = 0; y < height; y++) {
+ const binIndex = canvasYToBinIndexLog(y, specData);
+ if (binIndex < 0 || binIndex >= dctSize) continue; // Out of bounds
+
const value = specData.data[frameDataStart + binIndex];
- const intensity = Math.min(1, Math.abs(value) / maxAbsValue); // Normalized intensity
+ 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 - (binIndex * binHeight) - binHeight, frameWidth, binHeight);
+ ctx.fillRect(xPos, height - y - 1, frameWidth, 1); // Draw a 1-pixel height line for each y
}
}
@@ -274,15 +276,22 @@ function handleMouseUp(event) {
const cy = startY + (endPos.y - startY) / 2;
const centerCoords = canvasToSpectrogramCoords(cx, cy, currentSpecData);
+ // Map canvas radii to frequency/time spans. Log scale aware.
const radiusXFrames = Math.floor((rx / canvas.width) * currentSpecData.header.num_frames);
- const radiusYBins = Math.floor((ry / canvas.height) * dctSize);
+ const radiusYFreq = canvasDeltaYToFreqDeltaLog(ry, canvas.height); // Delta in Hz
+ const centerFreq = canvasYToFreqLog(cy, canvas.height); // Center in Hz
+ const binCRadius = freqToBinIndexLog(centerFreq + radiusYFreq / 2); // Max bin
+ const binC = freqToBinIndexLog(centerFreq); // Center bin
+ const binCMin = freqToBinIndexLog(centerFreq - radiusYFreq / 2); // Min bin
newShape = {
type: 'ellipse',
cx: cx, cy: cy,
rx: rx, ry: ry,
frameC: centerCoords.frame, binC: centerCoords.bin,
- radiusFrames: radiusXFrames, radiusBins: radiusYBins,
+ radiusFrames: radiusXFrames,
+ // Store as freq range for easy application to spec data
+ minBin: binCMin, maxBin: binCRadius,
amplitude: 0.5,
color: 'green',
};
@@ -313,8 +322,8 @@ function handleMouseUp(event) {
if (newShape) {
// Capture the state *before* applying the new shape for undo
- const previousDataSnapshot = new Float32Array(currentSpecData.data);
- const previousShapesSnapshot = shapes.map(s => ({ ...s })); // Deep copy shapes
+ const previousDataSnapshot = new Float32Array(currentSpecData.data); // Copy of actual data
+ const previousShapesSnapshot = shapes.map(s => ({ ...s })); // Deep copy shapes array
applyShapeToSpectrogram(newShape, currentSpecData); // Modify currentSpecData directly
shapes.push(newShape);
@@ -322,13 +331,12 @@ function handleMouseUp(event) {
type: 'add_shape',
shape: newShape,
undo: () => {
- // Revert shapes array
- shapes = previousShapesSnapshot;
- // Revert currentSpecData.data to the snapshot before this action
+ // To undo, restore previous shapes and previous data
+ shapes = previousShapesSnapshot;
currentSpecData.data = previousDataSnapshot;
},
redo: () => {
- // Re-apply the shape and update currentSpecData.data
+ // To redo, add the shape back and apply it to current data
shapes.push(newShape);
applyShapeToSpectrogram(newShape, currentSpecData);
}
@@ -338,6 +346,7 @@ function handleMouseUp(event) {
updateUndoRedoButtons();
}
+// --- Spectrogram Data Manipulation ---
function applyShapeToSpectrogram(shape, targetSpecData) {
if (!targetSpecData || !targetSpecData.data || targetSpecData.header.num_frames === 0) return;
@@ -360,7 +369,6 @@ function applyShapeToSpectrogram(shape, targetSpecData) {
while (true) {
if (currentX >= 0 && currentX < numFrames && currentY >= 0 && currentY < dctSize) {
- const index = currentX * dctSize + currentY;
// Apply amplitude and width
for (let b = -shape.width; b <= shape.width; b++) {
const binToAffect = currentY + b;
@@ -379,9 +387,29 @@ function applyShapeToSpectrogram(shape, targetSpecData) {
}
break;
case 'ellipse':
- // Apply ellipse to spectrogram data
- // TODO: Implement ellipse algorithm
- console.log("Applying ellipse to spectrogram data (TODO).");
+ // Apply ellipse to spectrogram data (log frequency aware)
+ const centerFrame = shape.frameC;
+ const centerBin = shape.binC;
+ const radiusFrames = shape.radiusFrames;
+ const minBin = shape.minBin;
+ const maxBin = shape.maxBin;
+
+ for (let f = centerFrame - radiusFrames; f <= centerFrame + radiusFrames; f++) {
+ if (f < 0 || f >= numFrames) continue;
+ for (let b = minBin; b <= maxBin; b++) {
+ if (b < 0 || b >= dctSize) continue;
+
+ // Check if (f, b) is within the ellipse
+ const normX = (f - centerFrame) / radiusFrames;
+ const normY = (binIndexToFreqLog(b) - binIndexToFreqLog(centerBin)) /
+ (binIndexToFreqLog(maxBin) - binIndexToFreqLog(centerBin)); // Normalized freq diff
+
+ if (normX * normX + normY * normY <= 1) {
+ targetSpecData.data[f * dctSize + b] += shape.amplitude;
+ targetSpecData.data[f * dctSize + b] = Math.max(-1, Math.min(1, targetSpecData.data[f * dctSize + b]));
+ }
+ }
+ }
break;
case 'noise_rect':
// Apply noise to a rectangular region
@@ -408,14 +436,34 @@ 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'); // New redo button
+const redoButton = document.getElementById('redoButton');
+const listenOriginalButton = document.getElementById('listenOriginalButton');
+const listenGeneratedButton = document.getElementById('listenGeneratedButton');
lineToolButton.addEventListener('click', () => { activeTool = 'line'; console.log('Line tool selected'); });
ellipseToolButton.addEventListener('click', () => { activeTool = 'ellipse'; console.log('Ellipse tool selected'); });
noiseToolButton.addEventListener('click', () => { activeTool = 'noise'; console.log('Noise tool selected'); });
undoButton.addEventListener('click', handleUndo);
-redoButton.addEventListener('click', handleRedo); // Redo button listener
+redoButton.addEventListener('click', handleRedo);
+
+listenOriginalButton.addEventListener('click', () => {
+ if (originalSpecData) {
+ playSpectrogramData(originalSpecData);
+ } else {
+ alert("No original SPEC data loaded.");
+ }
+});
+
+listenGeneratedButton.addEventListener('click', () => {
+ if (currentSpecData) {
+ // Ensure currentSpecData reflects all shapes before playing
+ redrawCanvas(); // This updates currentSpecData based on shapes
+ playSpectrogramData(currentSpecData);
+ } else {
+ alert("No generated SPEC data to play.");
+ }
+});
// --- Undo/Redo Logic ---
function addAction(action) {
@@ -433,7 +481,7 @@ function handleUndo() {
return;
}
const actionToUndo = undoStack.pop();
- actionToUndo.undo(); // Execute the inverse operation stored in the action
+ actionToUndo.undo();
redoStack.push(actionToUndo);
redrawCanvas();
updateUndoRedoButtons();
@@ -446,7 +494,7 @@ function handleRedo() {
}
const actionToRedo = redoStack.pop();
- actionToRedo.redo(); // Re-apply the action
+ actionToRedo.redo();
undoStack.push(actionToRedo);
redrawCanvas();
updateUndoRedoButtons();
@@ -474,30 +522,59 @@ function redrawCanvas() {
function updateUndoRedoButtons() {
undoButton.disabled = undoStack.length === 0;
- redoButton.disabled = redoStack.length === 0; // Update redo button state
+ redoButton.disabled = redoStack.length === 0;
}
-// --- Utility to map canvas coords to spectrogram bins/frames ---
-function canvasToSpectrogramCoords(canvasX, canvasY, specData) {
- const canvasWidth = canvas.width;
- const canvasHeight = canvas.height;
- const numFrames = specData.header.num_frames;
+// --- 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) {
+ return (binIndex / dctSize) * MAX_FREQ;
+}
- const frameIndex = Math.floor((canvasX / canvasWidth) * numFrames);
- const binIndex = Math.floor(((canvasHeight - canvasY) / canvasHeight) * dctSize);
+// Maps a frequency in Hz to its corresponding linear bin index
+function freqToBinIndex(freq) {
+ return Math.floor((freq / MAX_FREQ) * dctSize);
+}
- return { frame: frameIndex, bin: binIndex };
+// Converts a frequency (Hz) to a Y-coordinate on the canvas (log scale)
+function freqToCanvasYLog(freq, canvasHeight) {
+ if (freq < MIN_FREQ) freq = MIN_FREQ; // Clamp minimum frequency
+ const logMin = Math.log(MIN_FREQ);
+ const logMax = Math.log(MAX_FREQ);
+ const logFreq = Math.log(freq);
+ const normalizedLog = (logFreq - logMin) / (logMax - logMin);
+ return canvasHeight * (1 - normalizedLog); // Y-axis is inverted
}
-function spectrogramToCanvasCoords(frameIndex, binIndex, specData) {
- const canvasWidth = canvas.width;
- const canvasHeight = canvas.height;
- const numFrames = specData.header.num_frames;
+// Converts a Y-coordinate on the canvas to a frequency (Hz) (log scale)
+function canvasYToFreqLog(canvasY, canvasHeight) {
+ const normalizedLog = 1 - (canvasY / canvasHeight);
+ const logMin = Math.log(MIN_FREQ);
+ const logMax = Math.log(MAX_FREQ);
+ const logFreq = normalizedLog * (logMax - logMin) + logMin;
+ return Math.exp(logFreq);
+}
- const canvasX = (frameIndex / numFrames) * canvasWidth;
- const canvasY = canvasHeight - ((binIndex / dctSize) * canvasHeight);
+// Converts canvas Y-coordinate to log-scaled bin index
+function canvasYToBinIndexLog(canvasY, specData) {
+ const freq = canvasYToFreqLog(canvasY, canvas.height);
+ return freqToBinIndex(freq); // Use linear bin index from calculated log freq
+}
- return { x: canvasX, y: canvasY };
+// Converts log-scaled bin index to canvas Y-coordinate
+function binIndexToCanvasYLog(binIndex, specData) {
+ const freq = binIndexToFreq(binIndex);
+ return freqToCanvasYLog(freq, canvas.height);
+}
+
+// Helper to get frequency delta from canvas delta (for ellipse radius in freq)
+function canvasDeltaYToFreqDeltaLog(canvasDeltaY, canvasHeight) {
+ // This is an approximation as delta in log scale is not linear
+ // For small deltas around a center, it can be approximated
+ const centerCanvasY = canvasHeight / 2;
+ const freqAtCenter = canvasYToFreqLog(centerCanvasY, canvasHeight);
+ const freqAtCenterPlusDelta = canvasYToFreqLog(centerCanvasY - canvasDeltaY, canvasHeight);
+ return Math.abs(freqAtCenterPlusDelta - freqAtCenter);
}
// Initial setup for canvas size (can be updated on window resize)
@@ -527,7 +604,7 @@ async function playSpectrogramData(specData) {
currentAudioSource = null;
}
- const sampleRate = 32000; // Fixed sample rate
+ const sampleRate = SAMPLE_RATE; // Fixed sample rate
const numFrames = specData.header.num_frames;
const totalAudioSamples = numFrames * dctSize; // Total samples in time domain
@@ -588,4 +665,4 @@ function sizeof(type) {
return Float32Array.BYTES_PER_ELEMENT;
}
return 0;
-}
+} \ No newline at end of file