const dctSize = 512; // Default DCT size, read from header // --- Utility Functions for Audio Processing --- // 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 // ============================================================================ // Bit-reversal permutation (in-place) function bitReversePermute(real, imag, N) { let temp_bits = N; let num_bits = 0; while (temp_bits > 1) { temp_bits >>= 1; num_bits++; } for (let i = 0; i < N; i++) { let j = 0; let temp = i; for (let b = 0; b < num_bits; b++) { j = (j << 1) | (temp & 1); temp >>= 1; } 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 function fftRadix2(real, imag, N, direction) { const PI = Math.PI; 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; 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++) { for (let group_start = k; group_start < N; group_start += stage_size) { const i = group_start; const j = group_start + half_stage; const temp_real = real[j] * wr - imag[j] * wi; const temp_imag = real[j] * wi + imag[j] * wr; real[j] = real[i] - temp_real; imag[j] = imag[i] - temp_imag; real[i] = real[i] + temp_real; imag[i] = imag[i] + temp_imag; } const wr_old = wr; wr = wr_old * wr_delta - wi * wi_delta; wi = wr_old * wi_delta + wi * wr_delta; } } } function fftForward(real, imag, N) { bitReversePermute(real, imag, N); fftRadix2(real, imag, N, +1); } function fftInverse(real, imag, N) { bitReversePermute(real, imag, N); fftRadix2(real, imag, N, -1); 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 function javascript_dct_fft(input, N) { const PI = Math.PI; const real = new Float32Array(N); const imag = new Float32Array(N); for (let i = 0; i < N / 2; i++) { real[i] = input[2 * i]; real[N - 1 - i] = input[2 * i + 1]; } fftForward(real, imag, N); 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); const dct_value = real[k] * wr - imag[k] * wi; 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 function javascript_idct_fft(input, N) { const PI = Math.PI; const real = new Float32Array(N); const imag = 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); let scaled; if (k === 0) { scaled = input[k] / Math.sqrt(1.0 / N); } else { scaled = input[k] / Math.sqrt(2.0 / N) * 2.0; } real[k] = scaled * wr; imag[k] = scaled * wi; } fftInverse(real, imag, N); const output = new Float32Array(N); for (let i = 0; i < N / 2; i++) { output[2 * i] = real[i]; output[2 * i + 1] = real[N - 1 - i]; } return output; } // Fast O(N log N) IDCT using FFT function javascript_idct_512(input) { return javascript_idct_fft(input, dctSize); }