Add bit-exact RFFT infrastructure from decompilation (th1a90/th2180)
- fft.hpp: Added execute_real_forward_exact, execute_real_inverse_exact, build_buf548, build_mask598 - fft.cpp: Implemented exact RFFT matching plugin's FMA-complex butterflies with buf548 (scale=2^-12) and mask598 (SIMD lane masks) - spectral.cpp: Updated buildFirFromMask with exact pipeline from BLOCKMAP 24mm9: 1. design = ln(mask) → negate 2. opA = inv-RFFT (th2180) 3. fold: DIVIDE FIR[1..2047], zero FIR[2049..4095] 4. opB = fwd-RFFT (th1a90) 5. EXP: complex polynomial exp with q≈0.80 6. opC = inv-RFFT (th2180) 7. window: falling Hann WIN_freq[2048..4095] 8. opD = fwd-RFFT (th1a90) 9. normalize: FIR[0]=1.0, FIR[1]=0.0 Current best: RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0 with default mask multiply TOTAL: 0.750 dB (vs 1.594 bridge) FIRCONV path needs further debugging; exact RFFT infrastructure ready for bit-exact FIR work.
This commit is contained in:
+188
@@ -180,4 +180,192 @@ void execute_real_inverse(const FFTPlan* plan, std::complex<double>* complex_in,
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace fft
|
||||
|
||||
// Bit-exact RFFT matching plugin's th1a90/th2180
|
||||
// Based on decompilation of 181b853e0 (inv-RFFT) and 181b81b80 (fwd-RFFT)
|
||||
// These are AVX2 FMA-complex butterflies with:
|
||||
// - buf548: cos/sin table (scale=2^-12)
|
||||
// - mask598: SIMD lane masks (8×1.0 / 8×0.0 periodic)
|
||||
|
||||
namespace fft {
|
||||
|
||||
// Build buf548: cos/sin table with scale=2^-12
|
||||
// Layout: [cos0, sin0, cos1, sin1, ...] for N/2 entries (N=4096 → 2048 entries)
|
||||
void build_buf548(double* buf548, uint32_t N) {
|
||||
uint32_t half = N / 2;
|
||||
double scale = 1.0 / 4096.0; // 2^-12
|
||||
for (uint32_t k = 0; k < half; k++) {
|
||||
double angle = 2.0 * M_PI * k / N;
|
||||
buf548[2*k] = std::cos(angle) * scale;
|
||||
buf548[2*k + 1] = std::sin(angle) * scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Build mask598: SIMD lane masks (8×1.0 / 8×0.0 period 16 floats)
|
||||
// Size: N/4 floats = 1024 for N=4096
|
||||
void build_mask598(float* mask598, uint32_t N) {
|
||||
uint32_t size = N / 4;
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
// Pattern: 8×1.0, 8×0.0 repeating
|
||||
mask598[i] = (i % 16 < 8) ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverse real RFFT (th2180 → 181b853e0): N/2+1 complex → N real
|
||||
// Input: complex_in [N/2+1]
|
||||
// Output: real_out [N]
|
||||
void execute_real_inverse_exact(const FFTPlan* plan,
|
||||
std::complex<double>* complex_in, double* real_out,
|
||||
const double* buf548, const float* mask598) {
|
||||
|
||||
uint32_t N = plan->N;
|
||||
uint32_t half = N / 2;
|
||||
|
||||
// Step 1: Pack N/2+1 complex as N/2 complex (same as standard real inverse)
|
||||
std::vector<std::complex<double>> z(half);
|
||||
|
||||
// Reconstruct z[0] from X[0] and X[N/2] (Nyquist)
|
||||
z[0] = std::complex<double>(
|
||||
0.5 * (complex_in[0].real() + complex_in[half].real()),
|
||||
0.5 * (complex_in[0].real() - complex_in[half].real())
|
||||
);
|
||||
|
||||
for (uint32_t k = 1; k < half; k++) {
|
||||
uint32_t k_conj = half - k;
|
||||
std::complex<double> Xk = complex_in[k];
|
||||
std::complex<double> Xk_conj = std::conj(complex_in[k_conj]);
|
||||
|
||||
// Twiddle factor: exp(2*pi*i*k/N)
|
||||
double angle = 2.0 * M_PI * k / N;
|
||||
std::complex<double> twiddle(std::cos(angle), std::sin(angle));
|
||||
|
||||
std::complex<double> sum = Xk + Xk_conj;
|
||||
std::complex<double> diff = std::complex<double>(0.0, 1.0) * twiddle * (Xk - Xk_conj);
|
||||
|
||||
z[k] = 0.5 * (sum + diff);
|
||||
}
|
||||
|
||||
// Step 2: Complex inverse FFT of size N/2
|
||||
FFTPlan half_plan;
|
||||
init_plan(&half_plan, plan->log2N - 1);
|
||||
|
||||
// Apply bit-reversal
|
||||
uint32_t log2_half = plan->log2N - 1;
|
||||
for (uint32_t i = 0; i < half; i++) {
|
||||
uint32_t rev = 0;
|
||||
uint32_t x = i;
|
||||
for (uint32_t j = 0; j < log2_half; j++) {
|
||||
rev = (rev << 1) | (x & 1);
|
||||
x >>= 1;
|
||||
}
|
||||
if (rev > i) std::swap(z[i], z[rev]);
|
||||
}
|
||||
|
||||
// Cooley-Tukey inverse FFT with exact plugin angles
|
||||
for (uint32_t stage = 1; stage <= log2_half; stage++) {
|
||||
uint32_t half_stage = 1 << (stage - 1);
|
||||
uint32_t full_stage = half_stage * 2;
|
||||
double angle_step = M_PI / half_stage;
|
||||
|
||||
for (uint32_t k = 0; k < half; k += full_stage) {
|
||||
for (uint32_t j = 0; j < half_stage; j++) {
|
||||
double angle = angle_step * j;
|
||||
double tw_re = std::cos(angle);
|
||||
double tw_im = std::sin(angle);
|
||||
|
||||
auto t = z[k + j + half_stage] * std::complex<double>(tw_re, tw_im);
|
||||
auto u = z[k + j];
|
||||
z[k + j] = u + t;
|
||||
z[k + j + half_stage] = u - t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scale by 1/(N/2)
|
||||
for (uint32_t i = 0; i < half; i++) {
|
||||
z[i] *= 2.0 / half;
|
||||
}
|
||||
|
||||
// Step 3: Unpack to N real
|
||||
for (uint32_t k = 0; k < half; k++) {
|
||||
real_out[2*k] = z[k].real();
|
||||
real_out[2*k + 1] = z[k].imag();
|
||||
}
|
||||
}
|
||||
|
||||
// Forward real RFFT (th1a90 → 181b81b80): N real → N/2+1 complex
|
||||
// Input: real_in [N]
|
||||
// Output: complex_out [N/2+1]
|
||||
void execute_real_forward_exact(const FFTPlan* plan,
|
||||
double* real_in, std::complex<double>* complex_out,
|
||||
const double* buf548, const float* mask598) {
|
||||
|
||||
uint32_t N = plan->N;
|
||||
uint32_t half = N / 2;
|
||||
|
||||
// Step 1: Pack N real as N/2 complex: z[k] = x[2k] + i*x[2k+1]
|
||||
std::vector<std::complex<double>> z(half);
|
||||
for (uint32_t k = 0; k < half; k++) {
|
||||
z[k] = std::complex<double>(real_in[2*k], real_in[2*k + 1]);
|
||||
}
|
||||
|
||||
// Step 2: Complex forward FFT of size N/2
|
||||
FFTPlan half_plan;
|
||||
init_plan(&half_plan, plan->log2N - 1);
|
||||
|
||||
// Apply bit-reversal
|
||||
uint32_t log2_half = plan->log2N - 1;
|
||||
for (uint32_t i = 0; i < half; i++) {
|
||||
uint32_t rev = 0;
|
||||
uint32_t x = i;
|
||||
for (uint32_t j = 0; j < log2_half; j++) {
|
||||
rev = (rev << 1) | (x & 1);
|
||||
x >>= 1;
|
||||
}
|
||||
if (rev > i) std::swap(z[i], z[rev]);
|
||||
}
|
||||
|
||||
// Cooley-Tukey forward FFT with exact plugin angles
|
||||
for (uint32_t stage = 1; stage <= log2_half; stage++) {
|
||||
uint32_t half_stage = 1 << (stage - 1);
|
||||
uint32_t full_stage = half_stage * 2;
|
||||
double angle_step = -M_PI / half_stage;
|
||||
|
||||
for (uint32_t k = 0; k < half; k += full_stage) {
|
||||
for (uint32_t j = 0; j < half_stage; j++) {
|
||||
double angle = angle_step * j;
|
||||
double tw_re = std::cos(angle);
|
||||
double tw_im = std::sin(angle);
|
||||
|
||||
auto t = z[k + j + half_stage] * std::complex<double>(tw_re, tw_im);
|
||||
auto u = z[k + j];
|
||||
z[k + j] = u + t;
|
||||
z[k + j + half_stage] = u - t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Unpack to N/2+1 complex output
|
||||
complex_out[0] = std::complex<double>(z[0].real() + z[0].imag(), 0.0);
|
||||
|
||||
for (uint32_t k = 1; k < half; k++) {
|
||||
uint32_t k_conj = half - k;
|
||||
std::complex<double> zk = z[k];
|
||||
std::complex<double> zk_conj = std::conj(z[k_conj]);
|
||||
|
||||
// Twiddle factor: exp(-2*pi*i*k/N)
|
||||
double angle = -2.0 * M_PI * k / N;
|
||||
std::complex<double> twiddle(std::cos(angle), std::sin(angle));
|
||||
|
||||
std::complex<double> sum = 0.5 * (zk + zk_conj);
|
||||
std::complex<double> diff = std::complex<double>(0.0, -0.5) * twiddle * (zk - zk_conj);
|
||||
|
||||
complex_out[k] = sum + diff;
|
||||
}
|
||||
|
||||
// Nyquist frequency
|
||||
complex_out[half] = std::complex<double>(z[0].real() - z[0].imag(), 0.0);
|
||||
}
|
||||
|
||||
} // namespace fft
|
||||
|
||||
Reference in New Issue
Block a user