58 using std::setprecision;
67 using Complex = FFTD::Complex;
74 cout << fixed << setprecision(4) << setw(9) << z.real();
75 cout << (z.imag() < 0 ?
" - " :
" + ");
76 cout << fixed << setprecision(4) << setw(8) << std::abs(z.imag()) <<
"i";
85 cout << title <<
"\n";
86 for (
size_t i = 0; i < values.
size(); ++i)
87 cout <<
" [" << setw(2) << i <<
"] = "
88 << fixed << setprecision(4) << values[i] <<
"\n";
97 cout << title <<
"\n";
98 for (
size_t i = 0; i < values.
size(); ++i)
100 cout <<
" [" << setw(2) << i <<
"] = ";
117 cout <<
"[1] Spectral analysis of a real signal\n";
118 cout <<
"Goal: Identify frequency components of a composite signal.\n";
121 constexpr size_t n = 10;
126 for (
size_t t = 0; t < n; ++t)
129 const double angle = 2.0 * std::numbers::pi *
static_cast<double>(t)
130 /
static_cast<double>(n);
133 const double sample = 1.50 * std::cos(
angle)
134 + 0.75 * std::sin(3.0 *
angle);
141 const auto spectrum = FFTD::spectrum(signal);
144 const auto magnitudes = FFTD::magnitude_spectrum(spectrum);
145 const auto phases = FFTD::phase_spectrum(spectrum);
146 const auto hann_windowed = FFTD::windowed_spectrum(signal, FFTD::hann_window(signal.
size()));
151 cout <<
"Frequency bins X[k], magnitudes |X[k]| and phase arg(X[k]):\n";
152 for (
size_t k = 0;
k < spectrum.size(); ++
k)
154 cout <<
" k=" <<
k <<
" X[k] = ";
156 cout <<
" |X[k]| = " << fixed << setprecision(4)
158 <<
" phase = " <<
phases[
k] <<
"\n";
161 cout <<
"\nCompact real spectrum R[k] (only 0..floor(N/2)):\n";
164 cout <<
" k=" <<
k <<
" R[k] = ";
169 cout <<
"\nAnalysis:\n";
170 cout <<
"- Bin k=1 shows energy from 1.5 * cos(theta).\n";
171 cout <<
"- Bin k=3 shows energy from 0.75 * sin(3*theta).\n";
172 cout <<
"- For real signals, the spectrum is Hermitian symmetric:\n";
173 cout <<
" X[k] is the conjugate of X[N-k]. This is why |X[1]| = |X[9]|.\n";
174 cout <<
"- FFT<Real>::rfft() exposes only the non-redundant bins and\n";
175 cout <<
" FFT<Real>::irfft() reconstructs the original arbitrary-length signal.\n";
177 cout <<
"- Applying FFT<Real>::windowed_spectrum(..., hann_window(N)) reduces\n";
178 cout <<
" edge discontinuity; here Hann-windowed |X[1]| = "
194 cout <<
"[2] Real convolution via FFT\n";
195 cout <<
"Goal: Apply a smoothing filter to a sequence of samples.\n";
209 const auto filtered = FFTD::multiply(samples, kernel);
215 cout <<
"This overload uses the optimized sequential path for real-valued inputs.\n";
216 cout <<
"For long streams, FFT<Real>::OverlapAdd or overlap_add_convolution()\n";
217 cout <<
"lets you reuse the filter spectrum block-by-block.\n";
218 cout <<
"Observation: The convolution result has size N + M - 1 = 6 + 3 - 1 = 8.\n";
219 cout <<
"The values at the edges reflect the ramp-up and ramp-down of the filter.\n";
235 cout <<
"[3] Complex polynomial multiplication\n";
236 cout <<
"Goal: Multiply two polynomials with complex coefficients.\n";
254 const auto c = FFTD::multiply(a, b);
260 cout <<
"The result coefficients represent the new polynomial C(x).\n";
261 cout <<
"This shows how the FFT gracefully handles complex values natively.\n";
268 cout <<
"[4] Generic iterable containers\n";
269 cout <<
"Goal: Use FFT directly with std::vector inputs.\n";
272 std::vector<double>
real_signal = {1.0, 2.0, -1.0, 0.5, 3.0, -2.0, 4.0, -0.75};
275 std::vector<Complex>
poly_a = {
280 std::vector<Complex>
poly_b = {
287 cout <<
"Input std::vector<double> size: " <<
real_signal.size() <<
"\n";
288 cout <<
"FFT(real_signal) returned an Aleph Array with "
295 cout <<
"No manual conversion to Array is needed: compatible iterable\n"
296 <<
"containers are accepted directly by FFT<Real>.\n";
303 cout <<
"[5] Concurrent FFT with ThreadPool\n";
304 cout <<
"Goal: Keep the sequential and concurrent APIs explicitly separated.\n";
309 Array<double> signal = {0.5, -1.25, 3.5, 2.0, -0.75, 4.25, 1.0, -2.0};
312 Array<Complex>({Complex(0.5, 0.0), Complex(-1.25, 0.0), Complex(3.5, 0.0),
313 Complex(2.0, 0.0), Complex(-0.75, 0.0), Complex(4.25, 0.0),
314 Complex(1.0, 0.0), Complex(-2.0, 0.0)}),
315 Array<Complex>({Complex(1.0, 0.5), Complex(-0.5, 0.25), Complex(0.75, -1.0),
316 Complex(2.0, 0.0), Complex(-1.0, 0.5), Complex(0.25, -0.25),
317 Complex(1.5, 0.75), Complex(-0.75, -0.5)})
321 const auto par_spectrum = FFTD::ptransform(pool, signal);
322 const auto par_filtered = FFTD::pmultiply(pool, signal, kernel);
327 Complex(-1.0, 0.25), Complex(0.5, 1.0)}),
329 Complex(0.0, 0.5), Complex(-2.0, 0.0)}),
331 Complex(2.5, 0.0), Complex(1.0, -1.5)})
337 Array<double>({1.0, -0.5, 0.75, 2.0, -1.25, 0.5, 0.25, -0.75})
348 cout <<
"SIMD backend for FFT<double>: " << FFTD::simd_backend_name()
349 <<
" (batch-plan backend: " << FFTD::batched_plan_simd_backend_name()
350 <<
", detected hardware backend: "
351 << FFTD::detected_simd_backend_name()
352 <<
", policy: " << FFTD::simd_preference_name()
353 <<
", avx2 compiled/runtime: "
354 << (FFTD::avx2_kernel_compiled() ?
"yes" :
"no") <<
"/"
355 << (FFTD::avx2_runtime_available() ?
"yes" :
"no")
356 <<
", neon compiled/runtime: "
357 << (FFTD::neon_kernel_compiled() ?
"yes" :
"no") <<
"/"
358 << (FFTD::neon_runtime_available() ?
"yes" :
"no") <<
")\n";
360 cout <<
"Use FFT<Real>::transform()/multiply() for the sequential path and\n"
361 <<
"FFT<Real>::ptransform()/pmultiply() when you want explicit ThreadPool\n"
362 <<
"parallelism under Aleph's concurrency style. When many complex inputs\n"
363 <<
"share the same length, FFT<Real>::Plan::ptransformed_batch() reuses one\n"
364 <<
"plan and parallelizes across the batch instead of across a single FFT.\n"
365 <<
"For image-like data, transformed2d()/ptransformed2d() remove the need to\n"
366 <<
"manually loop over rows and columns, while transform_axes() targets flat\n"
367 <<
"buffers with explicit shape/stride metadata.\n"
368 <<
"For multichannel FIR streaming, FFT<Real>::OverlapAddBank shares the\n"
369 <<
"kernel spectrum across channels instead of rebuilding one convolver per\n"
377 cout <<
"[6] STFT, ISTFT, and zero-phase filtering\n";
378 cout <<
"Goal: Analyze by frames, reconstruct by overlap-add, inspect IIR response,\n"
379 <<
"stream STFT in chunks, and compare causal vs zero-phase filtering.\n";
384 0.0, 0.8, 1.0, 0.2, -0.7, -1.0, -0.3, 0.6,
389 FFTD::BiquadSection
iir = {0.075, 0.15, 0.075, 1.0, -0.9, 0.2};
413 const auto designed_fir = FFTD::firwin_lowpass(33, 1200.0, 8000.0, 70.0);
423 FFTD::chebyshev2_bandpass(4, 40.0, 700.0, 1500.0, 8000.0);
425 FFTD::elliptic_bandstop(4, 1.0, 40.0, 700.0, 1500.0, 8000.0);
426 const auto response = FFTD::freqz(
iir, 5);
431 const auto poles = FFTD::poles(
iir);
432 const auto pairs = FFTD::pair_poles_and_zeros(
iir);
433 const auto group = FFTD::group_delay(
iir, 5);
434 const auto phase = FFTD::phase_delay(
iir, 5);
435 const auto stability_margin = FFTD::stability_margin(
iir);
448 for (
size_t i = 0; i < signal.
size() / 2; ++i)
450 for (
size_t i = signal.
size() / 2; i < signal.
size(); ++i)
471 for (
size_t i = 0; i <
spectrogram.size() / 2; ++i)
493 FFTD::SpectrogramLayout::frame_channel_bin);
500 FFTD::SpectrogramLayout::frame_channel_bin);
512 cout <<
"Raw frame_signal frames: " <<
frames.size()
513 <<
", centered STFT frames: " <<
spectrogram.size()
514 <<
", FFT bins per frame: " <<
spectrogram[0].size() <<
"\n";
518 <<
", batched STFT items: " <<
batched.size()
519 <<
", frame-major multichannel frames: " <<
frame_major.size() <<
"\n";
541 cout <<
"Pole/zero pairs tracked: " << pairs.size() <<
"\n";
544 cout <<
"IIR |H(0)| = " << std::abs(response.response[0])
545 <<
", |H(pi)| = " << std::abs(response.response[response.response.size() - 1])
546 <<
", stable = " << (FFTD::is_stable(
iir) ?
"yes" :
"no")
547 <<
", stability margin = " << stability_margin
551 cout <<
"frame_signal() exposes the hop-size layout explicitly, stft() applies\n"
552 <<
"windowing + zero-padding per frame, pstft() parallelizes the same analysis,\n"
553 <<
"STFTProcessor and ISTFTProcessor keep chunked analysis/synthesis aligned\n"
554 <<
"with the offline result, the centered STFT/ISTFT option pair reconstructs\n"
555 <<
"the original signal exactly under NOLA, batched_stft() scales the API to\n"
556 <<
"multiple signals, multichannel_stft()/multichannel_istft() switch between\n"
557 <<
"channel-major and frame-major layouts without manual transposes, and\n"
558 <<
"freqz() plus poles()/pair_poles_and_zeros(),\n"
559 <<
"stability_margin(), group_delay()/phase_delay() expose filter behavior.\n"
560 <<
"The same header also covers FIR design through firwin_*(), firls(), remez(),\n"
561 <<
"and reusable Chebyshev-II plus elliptic/Cauer SOS designs.\n";
568 cout <<
"[7] Production DSP utilities\n";
569 cout <<
"Goal: Estimate PSD/coherence, resample by rational factors, and\n"
570 <<
"compare overlap-save with low-latency partitioned convolution.\n";
578 for (
size_t i = 0; i < 256; ++i)
580 const double t =
static_cast<double>(i) /
sample_rate;
581 signal.
append(std::sin(2.0 * std::numbers::pi * 1000.0 * t)
582 + 0.35 * std::sin(2.0 * std::numbers::pi * 1800.0 * t));
583 shifted_signal.append(1.5 * std::sin(2.0 * std::numbers::pi * 1000.0 * t + 0.25)
584 + 0.35 * std::sin(2.0 * std::numbers::pi * 1800.0 * t));
587 const auto window = FFTD::hann_window(64);
597 for (
size_t i = 1; i <
psd.density.size(); ++i)
604 const auto resampled = FFTD::resample_poly(signal, 3, 2);
646 cout <<
"Welch peak frequency: " <<
psd.frequency[
peak]
647 <<
" Hz, PSD = " <<
psd.density[
peak] <<
"\n";
648 cout <<
"Cross-spectrum magnitude at that bin: "
649 << std::abs(cross.density[
peak])
650 <<
", coherence = " <<
coh.magnitude_squared[
peak] <<
"\n";
651 cout <<
"Window coherent gain = " << FFTD::window_coherent_gain(window)
652 <<
", ENBW = " << FFTD::window_enbw(window)
653 <<
", frame count (exact) = "
654 << FFTD::frame_offsets(signal.
size(), window.size(),
welch_options.hop_size,
false).size()
657 cout <<
"welch()/csd()/coherence() provide one-sided spectral estimators,\n"
658 <<
"upfirdn()/resample_poly() cover rational-rate sample conversion,\n"
659 <<
"overlap_save_convolution() targets block FIR filtering, and\n"
660 <<
"PartitionedConvolver keeps latency tied to the configured partition size.\n";
671 std::cout <<
"\n=== Aleph-w: Fast Fourier Transform (FFT) Demonstration ===\n\n";
694 std::cout <<
"All demonstrations completed successfully.\n";
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
T & append(const T &data)
Append a copy of data
void reserve(size_t cap)
Reserves cap cells into the array.
Fast Fourier Transform (FFT) and DSP Toolkit.
A reusable thread pool for efficient parallel task execution.
Fast Fourier Transform (FFT) and Digital Signal Processing (DSP) toolkit.
int main()
Main entry point.
size_t size(Node *root) noexcept
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
T product(const Container &container, const T &init=T{1})
Compute product of all elements.
void print_rule()
Prints a horizontal rule for example output separation.
Dynamic array container with automatic resizing.