summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-01-28 12:04:44 +0100
committerskal <pascal.massimino@gmail.com>2026-01-28 12:04:44 +0100
commita570e4d571ccdd205f140ed294aa182c13d7bc2a (patch)
treeb4092fa20b4bae492685bd786663a98b469b8f27
parent5951650903b228bb01171f8d47965e22a949a518 (diff)
feat(editor): Add audio playback and redo functionality
Enhances the spectrogram editor with audio playback capabilities and a redo button. - : Added 'Listen Original' and 'Listen Generated' buttons, and a 'Redo' button. - : Added styling for the new buttons. - : Implemented JavaScript IDCT, Hanning window, and for audio synthesis. - Connected playback buttons to audio functionality. - Fully implemented logic.
-rw-r--r--tools/editor/index.html6
-rw-r--r--tools/editor/script.js112
-rw-r--r--tools/editor/style.css22
3 files changed, 138 insertions, 2 deletions
diff --git a/tools/editor/index.html b/tools/editor/index.html
index 914ac93..6cb6af7 100644
--- a/tools/editor/index.html
+++ b/tools/editor/index.html
@@ -21,10 +21,14 @@
<button id="noiseTool">Noise</button>
<button id="undoButton">Undo</button>
<button id="redoButton">Redo</button>
+ <hr>
+ <h2>Playback</h2>
+ <button id="listenOriginalButton">Listen Original</button>
+ <button id="listenGeneratedButton">Listen Generated</button>
<!-- Add more tool controls later -->
</div>
</div>
<script src="script.js"></script>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/tools/editor/script.js b/tools/editor/script.js
index 1d6ca18..0926ea4 100644
--- a/tools/editor/script.js
+++ b/tools/editor/script.js
@@ -16,6 +16,38 @@ let startX, startY; // For tracking mouse down position
let shapes = []; // Array to store all drawn shapes (lines, ellipses, etc.)
+// Web Audio Context
+const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+// --- 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
+
// --- File Handling ---
const specFileInput = document.getElementById('specFileInput');
specFileInput.addEventListener('change', handleFileSelect);
@@ -478,4 +510,82 @@ window.addEventListener('resize', () => {
});
// Initial call to set button states
-updateUndoRedoButtons(); \ No newline at end of file
+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 = 32000; // 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 ---
+const listenOriginalButton = document.getElementById('listenOriginalButton');
+const listenGeneratedButton = document.getElementById('listenGeneratedButton');
+
+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.");
+ }
+});
+
+// --- 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;
+}
diff --git a/tools/editor/style.css b/tools/editor/style.css
index c02eb4a..7243ee3 100644
--- a/tools/editor/style.css
+++ b/tools/editor/style.css
@@ -55,3 +55,25 @@ h1, h2 {
#redoButton:hover {
background-color: #4cae4c;
}
+
+/* New styles for playback buttons */
+#listenOriginalButton,
+#listenGeneratedButton {
+ background-color: #5bc0de;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ margin-top: 5px;
+}
+
+#listenOriginalButton:hover,
+#listenGeneratedButton:hover {
+ background-color: #31b0d5;
+}
+
+hr {
+ border: 0;
+ height: 1px;
+ background-color: #ccc;
+ margin: 20px 0;
+} \ No newline at end of file