summaryrefslogtreecommitdiff
path: root/tools/editor
diff options
context:
space:
mode:
Diffstat (limited to 'tools/editor')
-rw-r--r--tools/editor/script.js168
1 files changed, 165 insertions, 3 deletions
diff --git a/tools/editor/script.js b/tools/editor/script.js
index 3afa0ff..0c5200b 100644
--- a/tools/editor/script.js
+++ b/tools/editor/script.js
@@ -508,159 +508,321 @@ function updateUndoRedoButtons() {
}
// --- 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;
+
}
+
+
// Maps a frequency in Hz to its corresponding linear bin index
+
function freqToBinIndex(freq) {
+
return Math.floor((freq / MAX_FREQ) * dctSize);
+
}
+
+
// Maps a frequency (Hz) to its corresponding log-scaled bin index
+
function freqToBinIndexLog(freq) {
+
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 Math.floor(normalizedLog * dctSize);
+
}
+
+
// Maps a log-scaled bin index to its corresponding frequency in Hz
+
function binIndexToFreqLog(binIndex) {
+
const normalizedLog = binIndex / dctSize;
+
const logMin = Math.log(MIN_FREQ);
+
const logMax = Math.log(MAX_FREQ);
+
const logFreq = normalizedLog * (logMax - logMin) + logMin;
+
return Math.exp(logFreq);
+
}
+
+
// 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
+
}
+
+
// 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);
+
}
+
+
// 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
+
}
+
+
// 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)
+
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 ---
+
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)`);
+
}
+
+
// --- Playback Button Event Listeners ---
+
listenOriginalButton.addEventListener('click', () => {
+
if (originalSpecData) {
+
playSpectrogramData(originalSpecData);
- } else {
+
+ }
+
+ 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 {
+
+ }
+
+ else {
+
alert("No generated SPEC data to play.");
+
}
+
});
+
+
// --- 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
+
+}
+
+