diff options
Diffstat (limited to 'tools/spectral_editor/dct.js')
| -rw-r--r-- | tools/spectral_editor/dct.js | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/tools/spectral_editor/dct.js b/tools/spectral_editor/dct.js new file mode 100644 index 0000000..435a7e8 --- /dev/null +++ b/tools/spectral_editor/dct.js @@ -0,0 +1,217 @@ +const dctSize = 512; // Default DCT size, read from header + +// --- Utility Functions for Audio Processing --- +// Fast O(N log N) IDCT using FFT +// JavaScript equivalent of C++ idct_512 +function javascript_idct_512(input) { + return javascript_idct_512_fft(input); +} + +// 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 + +// ============================================================================ +// FFT-based DCT/IDCT Implementation +// ============================================================================ +// Fast Fourier Transform using Radix-2 Cooley-Tukey algorithm +// This implementation MUST match the C++ version in src/audio/fft.cc exactly + +// Bit-reversal permutation (in-place) +// Reorders array elements by reversing their binary indices +function bitReversePermute(real, imag, N) { + // Calculate number of bits needed + let temp_bits = N; + let num_bits = 0; + while (temp_bits > 1) { + temp_bits >>= 1; + num_bits++; + } + + for (let i = 0; i < N; i++) { + // Compute bit-reversed index + let j = 0; + let temp = i; + for (let b = 0; b < num_bits; b++) { + j = (j << 1) | (temp & 1); + temp >>= 1; + } + + // Swap if j > i (to avoid swapping twice) + if (j > i) { + const tmp_real = real[i]; + const tmp_imag = imag[i]; + real[i] = real[j]; + imag[i] = imag[j]; + real[j] = tmp_real; + imag[j] = tmp_imag; + } + } +} + +// In-place radix-2 FFT (after bit-reversal) +// direction: +1 for forward FFT, -1 for inverse FFT +function fftRadix2(real, imag, N, direction) { + const PI = Math.PI; + + // Butterfly operations + for (let stage_size = 2; stage_size <= N; stage_size *= 2) { + const half_stage = stage_size / 2; + const angle = direction * 2.0 * PI / stage_size; + + // Precompute twiddle factors for this stage + let wr = 1.0; + let wi = 0.0; + const wr_delta = Math.cos(angle); + const wi_delta = Math.sin(angle); + + for (let k = 0; k < half_stage; k++) { + // Apply butterfly to all groups at this stage + for (let group_start = k; group_start < N; group_start += stage_size) { + const i = group_start; + const j = group_start + half_stage; + + // Complex multiplication: (real[j] + i*imag[j]) * (wr + i*wi) + const temp_real = real[j] * wr - imag[j] * wi; + const temp_imag = real[j] * wi + imag[j] * wr; + + // Butterfly operation + real[j] = real[i] - temp_real; + imag[j] = imag[i] - temp_imag; + real[i] = real[i] + temp_real; + imag[i] = imag[i] + temp_imag; + } + + // Update twiddle factor for next k (rotation) + const wr_old = wr; + wr = wr_old * wr_delta - wi * wi_delta; + wi = wr_old * wi_delta + wi * wr_delta; + } + } +} + +// Forward FFT: Time domain → Frequency domain +function fftForward(real, imag, N) { + bitReversePermute(real, imag, N); + fftRadix2(real, imag, N, +1); +} + +// Inverse FFT: Frequency domain → Time domain +function fftInverse(real, imag, N) { + bitReversePermute(real, imag, N); + fftRadix2(real, imag, N, -1); + + // Scale by 1/N + const scale = 1.0 / N; + for (let i = 0; i < N; i++) { + real[i] *= scale; + imag[i] *= scale; + } +} + +// DCT-II via FFT using reordering method (matches C++ dct_fft) +// Reference: Numerical Recipes Chapter 12.3 +function javascript_dct_fft(input, N) { + const PI = Math.PI; + + // Allocate arrays for N-point FFT + const real = new Float32Array(N); + const imag = new Float32Array(N); + + // Reorder input: even indices first, then odd indices reversed + // [x[0], x[2], x[4], ...] followed by [x[N-1], x[N-3], x[N-5], ...] + for (let i = 0; i < N / 2; i++) { + real[i] = input[2 * i]; // Even indices: 0, 2, 4, ... + real[N - 1 - i] = input[2 * i + 1]; // Odd indices reversed: N-1, N-3, ... + } + // imag is already zeros (Float32Array default) + + // Apply N-point FFT + fftForward(real, imag, N); + + // Extract DCT coefficients with phase correction + // DCT[k] = Re{FFT[k] * exp(-j*pi*k/(2*N))} * normalization + const output = new Float32Array(N); + for (let k = 0; k < N; k++) { + const angle = -PI * k / (2.0 * N); + const wr = Math.cos(angle); + const wi = Math.sin(angle); + + // Complex multiplication: (real[k] + j*imag[k]) * (wr + j*wi) + // Real part: real*wr - imag*wi + const dct_value = real[k] * wr - imag[k] * wi; + + // Apply DCT-II normalization + if (k === 0) { + output[k] = dct_value * Math.sqrt(1.0 / N); + } else { + output[k] = dct_value * Math.sqrt(2.0 / N); + } + } + + return output; +} + +// IDCT (DCT-III) via FFT using reordering method (matches C++ idct_fft) +// Reference: Numerical Recipes Chapter 12.3 +function javascript_idct_fft(input, N) { + const PI = Math.PI; + + // Allocate arrays for N-point FFT + const real = new Float32Array(N); + const imag = new Float32Array(N); + + // Prepare FFT input with inverse phase correction + // FFT[k] = DCT[k] * exp(+j*pi*k/(2*N)) / normalization + // Note: DCT-III needs factor of 2 for AC terms + for (let k = 0; k < N; k++) { + const angle = PI * k / (2.0 * N); // Positive angle for inverse + const wr = Math.cos(angle); + const wi = Math.sin(angle); + + // Inverse of DCT-II normalization with correct DCT-III scaling + let scaled; + if (k === 0) { + scaled = input[k] / Math.sqrt(1.0 / N); + } else { + // DCT-III needs factor of 2 for AC terms + scaled = input[k] / Math.sqrt(2.0 / N) * 2.0; + } + + // Complex multiplication: scaled * (wr + j*wi) + real[k] = scaled * wr; + imag[k] = scaled * wi; + } + + // Apply inverse FFT + fftInverse(real, imag, N); + + // Unpack: reverse the reordering from DCT + // Even output indices come from first half of FFT output + // Odd output indices come from second half (reversed) + const output = new Float32Array(N); + for (let i = 0; i < N / 2; i++) { + output[2 * i] = real[i]; // Even positions + output[2 * i + 1] = real[N - 1 - i]; // Odd positions (reversed) + } + + return output; +} + +// Convenience wrappers for dctSize = 512 (backward compatible) +function javascript_dct_512_fft(input) { + return javascript_dct_fft(input, dctSize); +} + +function javascript_idct_512_fft(input) { + return javascript_idct_fft(input, dctSize); +} + |
