summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-06 16:53:41 +0100
committerskal <pascal.massimino@gmail.com>2026-02-06 16:53:41 +0100
commitf998bfcd7a6167ae6bdf5ad7f8685b2cdf1fe811 (patch)
tree6dc288b12ed30914007ea54eaa8b2cc2d778b71b
parent6ed5952afe5c7a03f82ea02d261c3be2d56bd6a1 (diff)
fix(audio): Remove Hamming window from synthesis (before IDCT)
Removed incorrect windowing before IDCT in both C++ and JavaScript. The Hamming window is ONLY for analysis (before DCT), not synthesis. Changes: - synth.cc: Removed windowing before IDCT (direct spectral → IDCT) - spectral_editor/script.js: Removed spectrum windowing, kept time-domain window for overlap-add - editor/script.js: Removed spectrum windowing, kept time-domain window for smooth transitions Windowing Strategy (Correct): - ANALYSIS (spectool.cc, gen.cc): Apply window BEFORE DCT - SYNTHESIS (synth.cc, editors): NO window before IDCT Why: - Analysis window reduces spectral leakage during DCT - Synthesis needs raw IDCT output for accurate reconstruction - Time-domain window after IDCT is OK for overlap-add smoothing Result: - Correct audio synthesis without spectral distortion - Spectrograms reconstruct properly - C++ and JavaScript now match correct approach All 23 tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
-rw-r--r--src/audio/synth.cc22
-rw-r--r--tools/editor/script.js13
-rw-r--r--tools/spectral_editor/script.js8
3 files changed, 13 insertions, 30 deletions
diff --git a/src/audio/synth.cc b/src/audio/synth.cc
index ada46fd..798a02e 100644
--- a/src/audio/synth.cc
+++ b/src/audio/synth.cc
@@ -4,7 +4,6 @@
#include "synth.h"
#include "audio/dct.h"
-#include "audio/window.h"
#include "util/debug.h"
#include <atomic>
#include <math.h>
@@ -41,9 +40,8 @@ static struct {
static Voice g_voices[MAX_VOICES];
static volatile float g_current_output_peak =
- 0.0f; // Global peak for visualization
-static float g_hamming_window[WINDOW_SIZE]; // Static window for optimization
-static float g_tempo_scale = 1.0f; // Playback speed multiplier
+ 0.0f; // Global peak for visualization
+static float g_tempo_scale = 1.0f; // Playback speed multiplier
#if !defined(STRIP_ALL)
static float g_elapsed_time_sec = 0.0f; // Tracks elapsed time for event hooks
@@ -56,8 +54,6 @@ void synth_init() {
#if !defined(STRIP_ALL)
g_elapsed_time_sec = 0.0f;
#endif /* !defined(STRIP_ALL) */
- // Initialize the Hamming window once
- hamming_window_512(g_hamming_window);
}
void synth_shutdown() {
@@ -214,10 +210,6 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan) {
}
void synth_render(float* output_buffer, int num_frames) {
- // Use the pre-calculated window
- // float window[WINDOW_SIZE];
- // hamming_window_512(window);
-
// Faster decay for more responsive visuals
g_current_output_peak *= 0.90f;
@@ -243,13 +235,9 @@ void synth_render(float* output_buffer, int num_frames) {
const float* spectral_frame = (const float*)v.active_spectral_data +
(v.current_spectral_frame * DCT_SIZE);
- float windowed_frame[DCT_SIZE];
- for (int j = 0; j < DCT_SIZE; ++j) {
- windowed_frame[j] =
- spectral_frame[j] * g_hamming_window[j]; // Use static window
- }
-
- idct_512(windowed_frame, v.time_domain_buffer);
+ // IDCT directly - no windowing needed for synthesis
+ // (Window is only used during analysis, before DCT)
+ idct_512(spectral_frame, v.time_domain_buffer);
v.buffer_pos = 0;
++v.current_spectral_frame;
diff --git a/tools/editor/script.js b/tools/editor/script.js
index ebc543a..06c9bef 100644
--- a/tools/editor/script.js
+++ b/tools/editor/script.js
@@ -632,17 +632,12 @@ async function playSpectrogramData(specData) {
for (let frameIndex = 0; frameIndex < numFrames; frameIndex++) {
const spectralFrame = specData.data.slice(frameIndex * dctSize, (frameIndex + 1) * dctSize);
- // Apply window to spectrum before IDCT (matches C++ synth.cc)
- const windowedSpectral = new Float32Array(dctSize);
- for (let i = 0; i < dctSize; i++) {
- windowedSpectral[i] = spectralFrame[i] * hanningWindowArray[i];
- }
-
- const timeDomainFrame = javascript_idct_512(windowedSpectral);
+ // IDCT (no windowing - window is only for analysis, not synthesis)
+ const timeDomainFrame = javascript_idct_512(spectralFrame);
- // Direct output (no additional windowing)
+ // Apply Hanning window for smooth transitions between frames
for (let i = 0; i < dctSize; i++) {
- audioData[frameIndex * dctSize + i] = timeDomainFrame[i];
+ audioData[frameIndex * dctSize + i] = timeDomainFrame[i] * hanningWindowArray[i];
}
}
diff --git a/tools/spectral_editor/script.js b/tools/spectral_editor/script.js
index 677a823..6c6dd49 100644
--- a/tools/spectral_editor/script.js
+++ b/tools/spectral_editor/script.js
@@ -1543,20 +1543,20 @@ function spectrogramToAudio(spectrogram, dctSize, numFrames) {
const window = hanningWindowArray;
for (let frameIdx = 0; frameIdx < numFrames; frameIdx++) {
- // Extract frame and apply window to spectrum (matches C++ synth.cc)
+ // Extract frame (no windowing - window is only for analysis, not synthesis)
const frame = new Float32Array(dctSize);
for (let b = 0; b < dctSize; b++) {
- frame[b] = spectrogram[frameIdx * dctSize + b] * window[b];
+ frame[b] = spectrogram[frameIdx * dctSize + b];
}
// IDCT
const timeFrame = javascript_idct_512(frame);
- // Overlap-add (no additional windowing - window already applied to spectrum)
+ // Apply synthesis window for overlap-add
const frameStart = frameIdx * hopSize;
for (let i = 0; i < dctSize; i++) {
if (frameStart + i < audioLength) {
- audioData[frameStart + i] += timeFrame[i];
+ audioData[frameStart + i] += timeFrame[i] * window[i];
}
}
}