| e8555c89 Andy McCall |
1 |
#ifndef FFTREAL_HH |
| 2 |
#define FFTREAL_HH |
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
#include "cstd.hh" |
| 12 |
#include "narrow.hh" |
| 13 |
#include "xrange.hh" |
| 14 |
|
| 15 |
#include <array> |
| 16 |
#include <cassert> |
| 17 |
#include <cmath> |
| 18 |
#include <cstdint> |
| 19 |
#include <numbers> |
| 20 |
#include <span> |
| 21 |
|
| 22 |
|
| 23 |
enum class FFTInputMode { |
| 24 |
Full, |
| 25 |
|
| 26 |
HalfZeroPadded, |
| 27 |
|
| 28 |
|
| 29 |
Symmetric |
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
}; |
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
template<unsigned FFT_LEN_L2, FFTInputMode MODE = FFTInputMode::Full> |
| 53 |
class FFTReal |
| 54 |
{ |
| 55 |
public: |
| 56 |
static_assert(FFT_LEN_L2 >= 4); |
| 57 |
static_assert(FFT_LEN_L2 <= 16); |
| 58 |
static constexpr unsigned FFT_LEN = 1 << FFT_LEN_L2; |
| 59 |
static constexpr unsigned INPUT_LEN = |
| 60 |
(MODE == FFTInputMode::HalfZeroPadded) ? (FFT_LEN / 2) : |
| 61 |
(MODE == FFTInputMode::Symmetric) ? (FFT_LEN / 2 + 1) : |
| 62 |
FFT_LEN; |
| 63 |
static constexpr unsigned OUTPUT_LEN = |
| 64 |
(MODE == FFTInputMode::Symmetric) ? (FFT_LEN / 2 + 1) : |
| 65 |
FFT_LEN; |
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
static std::span<float, OUTPUT_LEN> execute(std::span<const float, INPUT_LEN> input, |
| 71 |
std::span<float, FFT_LEN> output, |
| 72 |
std::span<float, FFT_LEN> tmpBuf) |
| 73 |
{ |
| 74 |
pass<FFT_LEN_L2 - 1>(output, tmpBuf, input); |
| 75 |
#ifndef NDEBUG |
| 76 |
|
| 77 |
if constexpr (MODE == FFTInputMode::Symmetric) { |
| 78 |
for (unsigned i = FFT_LEN / 2 + 1; i < FFT_LEN; ++i) { |
| 79 |
assert(std::abs(output[i]) < 1e-4f && "Symmetric mode: imaginary outputs should be ~0"); |
| 80 |
} |
| 81 |
} |
| 82 |
#endif |
| 83 |
return std::span<float, OUTPUT_LEN>(output.data(), OUTPUT_LEN); |
| 84 |
} |
| 85 |
|
| 86 |
private: |
| 87 |
|
| 88 |
template<auto> static inline constexpr bool always_false = false; |
| 89 |
|
| 90 |
template<int PASS> |
| 91 |
static void pass(std::span<float, FFT_LEN> dst, |
| 92 |
std::span<float, FFT_LEN> src, |
| 93 |
std::span<const float, INPUT_LEN> input) |
| 94 |
{ |
| 95 |
if constexpr (PASS == 1) { |
| 96 |
|
| 97 |
static constexpr auto len4 = FFT_LEN >> 2; |
| 98 |
|
| 99 |
if constexpr (MODE == FFTInputMode::Full) { |
| 100 |
|
| 101 |
for (unsigned idx = 0; idx < FFT_LEN; idx += 4) { |
| 102 |
const unsigned ri_0 = bitRevBuf[idx >> 2]; |
| 103 |
const unsigned ri_1 = ri_0 + 2 * len4; |
| 104 |
const unsigned ri_2 = ri_0 + 1 * len4; |
| 105 |
const unsigned ri_3 = ri_0 + 3 * len4; |
| 106 |
|
| 107 |
dst[idx + 1] = input[ri_0] - input[ri_1]; |
| 108 |
dst[idx + 3] = input[ri_2] - input[ri_3]; |
| 109 |
|
| 110 |
const float sf_0 = input[ri_0] + input[ri_1]; |
| 111 |
const float sf_2 = input[ri_2] + input[ri_3]; |
| 112 |
|
| 113 |
dst[idx + 0] = sf_0 + sf_2; |
| 114 |
dst[idx + 2] = sf_0 - sf_2; |
| 115 |
} |
| 116 |
} else if constexpr (MODE == FFTInputMode::HalfZeroPadded) { |
| 117 |
|
| 118 |
|
| 119 |
|
| 120 |
for (unsigned idx = 0; idx < FFT_LEN; idx += 4) { |
| 121 |
const unsigned ri_0 = bitRevBuf[idx >> 2]; |
| 122 |
const unsigned ri_2 = ri_0 + 1 * len4; |
| 123 |
|
| 124 |
dst[idx + 1] = input[ri_0]; |
| 125 |
dst[idx + 3] = input[ri_2]; |
| 126 |
dst[idx + 0] = input[ri_0] + input[ri_2]; |
| 127 |
dst[idx + 2] = input[ri_0] - input[ri_2]; |
| 128 |
} |
| 129 |
} else if constexpr (MODE == FFTInputMode::Symmetric) { |
| 130 |
|
| 131 |
|
| 132 |
|
| 133 |
for (unsigned idx = 0; idx < FFT_LEN; idx += 4) { |
| 134 |
const unsigned ri_0 = bitRevBuf[idx >> 2]; |
| 135 |
const unsigned ri_1 = ri_0 + 2 * len4; |
| 136 |
const unsigned ri_2 = ri_0 + 1 * len4; |
| 137 |
const unsigned ri_3 = ri_0 + 3 * len4; |
| 138 |
|
| 139 |
const float in_0 = input[ri_0]; |
| 140 |
const float in_1 = input[FFT_LEN - ri_1]; |
| 141 |
const float in_2 = input[ri_2]; |
| 142 |
const float in_3 = input[FFT_LEN - ri_3]; |
| 143 |
|
| 144 |
dst[idx + 1] = in_0 - in_1; |
| 145 |
dst[idx + 3] = in_2 - in_3; |
| 146 |
|
| 147 |
const float sf_0 = in_0 + in_1; |
| 148 |
const float sf_2 = in_2 + in_3; |
| 149 |
|
| 150 |
dst[idx + 0] = sf_0 + sf_2; |
| 151 |
dst[idx + 2] = sf_0 - sf_2; |
| 152 |
} |
| 153 |
} else { |
| 154 |
static_assert(always_false<MODE>, "Unsupported FFTInputMode"); |
| 155 |
} |
| 156 |
|
| 157 |
} else if constexpr (PASS == 2) { |
| 158 |
|
| 159 |
pass<1>(src, dst, input); |
| 160 |
|
| 161 |
|
| 162 |
static constexpr float sqrt2_2 = std::numbers::sqrt2_v<float> * 0.5f; |
| 163 |
for (unsigned idx = 0; idx < FFT_LEN; idx += 8) { |
| 164 |
dst[idx + 0] = src[idx] + src[idx + 4]; |
| 165 |
dst[idx + 4] = src[idx] - src[idx + 4]; |
| 166 |
dst[idx + 2] = src[idx + 2]; |
| 167 |
dst[idx + 6] = src[idx + 6]; |
| 168 |
|
| 169 |
float v1 = (src[idx + 5] - src[idx + 7]) * sqrt2_2; |
| 170 |
dst[idx + 1] = src[idx + 1] + v1; |
| 171 |
dst[idx + 3] = src[idx + 1] - v1; |
| 172 |
|
| 173 |
float v2 = (src[idx + 5] + src[idx + 7]) * sqrt2_2; |
| 174 |
dst[idx + 5] = v2 + src[idx + 3]; |
| 175 |
dst[idx + 7] = v2 - src[idx + 3]; |
| 176 |
} |
| 177 |
|
| 178 |
} else { |
| 179 |
|
| 180 |
pass<PASS - 1>(src, dst, input); |
| 181 |
|
| 182 |
static constexpr unsigned dist = 1 << (PASS - 1); |
| 183 |
static constexpr unsigned c1_r = 0; |
| 184 |
static constexpr unsigned c1_i = dist * 1; |
| 185 |
static constexpr unsigned c2_r = dist * 2; |
| 186 |
static constexpr unsigned c2_i = dist * 3; |
| 187 |
static constexpr unsigned cend = dist * 4; |
| 188 |
static constexpr unsigned table_step = COS_ARR_SIZE >> (PASS - 1); |
| 189 |
|
| 190 |
|
| 191 |
|
| 192 |
|
| 193 |
[[maybe_unused]] static constexpr bool isFinalPass = (PASS == FFT_LEN_L2 - 1); |
| 194 |
#ifdef NDEBUG |
| 195 |
static constexpr bool skipImaginary = (MODE == FFTInputMode::Symmetric) && isFinalPass; |
| 196 |
#else |
| 197 |
static constexpr bool skipImaginary = false; |
| 198 |
#endif |
| 199 |
|
| 200 |
for (unsigned idx = 0; idx < FFT_LEN; idx += cend) { |
| 201 |
std::span<const float> sf = src.subspan(idx); |
| 202 |
std::span< float> df = dst.subspan(idx); |
| 203 |
|
| 204 |
|
| 205 |
df[c1_r] = sf[c1_r] + sf[c2_r]; |
| 206 |
df[c2_r] = sf[c1_r] - sf[c2_r]; |
| 207 |
df[c1_i] = sf[c1_i]; |
| 208 |
if constexpr (!skipImaginary) { |
| 209 |
df[c2_i] = sf[c2_i]; |
| 210 |
} |
| 211 |
|
| 212 |
|
| 213 |
for (unsigned i = 1; i < dist; ++ i) { |
| 214 |
const float c = cosBuf[ i * table_step]; |
| 215 |
const float s = cosBuf[(dist - i) * table_step]; |
| 216 |
|
| 217 |
const float sf_r_i = sf[c1_r + i]; |
| 218 |
const float sf_i_i = sf[c1_i + i]; |
| 219 |
|
| 220 |
const float v1 = sf[c2_r + i] * c - sf[c2_i + i] * s; |
| 221 |
df[c1_r + i] = sf_r_i + v1; |
| 222 |
df[c2_r - i] = sf_r_i - v1; |
| 223 |
|
| 224 |
if constexpr (!skipImaginary) { |
| 225 |
const float v2 = sf[c2_r + i] * s + sf[c2_i + i] * c; |
| 226 |
df[c2_r + i] = v2 + sf_i_i; |
| 227 |
df[cend - i] = v2 - sf_i_i; |
| 228 |
} |
| 229 |
} |
| 230 |
} |
| 231 |
} |
| 232 |
} |
| 233 |
|
| 234 |
static constexpr auto bitRevBuf = []{ |
| 235 |
constexpr int BR_ARR_SIZE = FFT_LEN / 4; |
| 236 |
std::array<uint16_t, BR_ARR_SIZE> result = {}; |
| 237 |
for (auto cnt : xrange(narrow<unsigned>(result.size()))) { |
| 238 |
unsigned index = cnt << 2; |
| 239 |
unsigned res = 0; |
| 240 |
for (int bit_cnt = FFT_LEN_L2; bit_cnt > 0; --bit_cnt) { |
| 241 |
res <<= 1; |
| 242 |
res += (index & 1); |
| 243 |
index >>= 1; |
| 244 |
} |
| 245 |
result[cnt] = narrow<uint16_t>(res); |
| 246 |
} |
| 247 |
return result; |
| 248 |
}(); |
| 249 |
|
| 250 |
static constexpr int COS_ARR_SIZE = FFT_LEN / 4; |
| 251 |
static constexpr auto cosBuf = []{ |
| 252 |
std::array<float, COS_ARR_SIZE> result = {}; |
| 253 |
const double mul = (0.5 * std::numbers::pi) / COS_ARR_SIZE; |
| 254 |
for (auto i : xrange(narrow<unsigned>(result.size()))) { |
| 255 |
result[i] = float(cstd::cos<4>(i * mul)); |
| 256 |
} |
| 257 |
return result; |
| 258 |
}(); |
| 259 |
}; |
| 260 |
|
| 261 |
#endif |