diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-06 16:53:41 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-06 16:53:41 +0100 |
| commit | f998bfcd7a6167ae6bdf5ad7f8685b2cdf1fe811 (patch) | |
| tree | 6dc288b12ed30914007ea54eaa8b2cc2d778b71b | |
| parent | 6ed5952afe5c7a03f82ea02d261c3be2d56bd6a1 (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.cc | 22 | ||||
| -rw-r--r-- | tools/editor/script.js | 13 | ||||
| -rw-r--r-- | tools/spectral_editor/script.js | 8 |
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]; } } } |
