Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
fft_example.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
42# include <algorithm>
43# include <cmath>
44# include <complex>
45# include <iomanip>
46# include <iostream>
47# include <numbers>
48# include <vector>
49
50# include <fft.H>
51# include <print_rule.H>
52# include <tpl_array.H>
53
54namespace
55{
56 using std::cout;
57 using std::fixed;
58 using std::setprecision;
59 using std::setw;
60 using std::size_t;
61 using Aleph::FFT;
62 using Aleph::Array;
64
65 // Alias for common types to make the code more readable.
66 using FFTD = FFT<double>;
67 using Complex = FFTD::Complex;
68
72 void print_complex(const Complex & z)
73 {
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";
77 }
78
82 template <typename T>
83 void print_real_sequence(const char * title, const Array<T> & values)
84 {
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";
89 cout << "\n";
90 }
91
95 void print_complex_sequence(const char * title, const Array<Complex> & values)
96 {
97 cout << title << "\n";
98 for (size_t i = 0; i < values.size(); ++i)
99 {
100 cout << " [" << setw(2) << i << "] = ";
101 print_complex(values[i]);
102 cout << "\n";
103 }
104 cout << "\n";
105 }
106
116 {
117 cout << "[1] Spectral analysis of a real signal\n";
118 cout << "Goal: Identify frequency components of a composite signal.\n";
119 print_rule();
120
121 constexpr size_t n = 10; // Arbitrary sizes are now supported directly.
122 Array<double> signal;
123 signal.reserve(n);
124
125 // Step 1: Generate the signal in the time domain.
126 for (size_t t = 0; t < n; ++t)
127 {
128 // Calculate the base angle for index t
129 const double angle = 2.0 * std::numbers::pi * static_cast<double>(t)
130 / static_cast<double>(n);
131
132 // Sum of a fundamental frequency (k=1) and its third harmonic (k=3)
133 const double sample = 1.50 * std::cos(angle)
134 + 0.75 * std::sin(3.0 * angle);
135 signal.append(sample);
136 }
137
138 // Step 2: Perform the FFT to move to the frequency domain.
139 // The result is an array of complex numbers where X[k] represents the
140 // amplitude and phase of the frequency corresponding to index k.
141 const auto spectrum = FFTD::spectrum(signal);
142 const auto compact_spectrum = FFTD::rfft(signal);
143 const auto restored_from_compact = FFTD::irfft(compact_spectrum, signal.size());
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()));
147 const auto hann_magnitudes = FFTD::magnitude_spectrum(hann_windowed);
148
149 print_real_sequence("Time-domain samples x[t]:", signal);
150
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)
153 {
154 cout << " k=" << k << " X[k] = ";
155 print_complex(spectrum[k]);
156 cout << " |X[k]| = " << fixed << setprecision(4)
157 << magnitudes[k]
158 << " phase = " << phases[k] << "\n";
159 }
160
161 cout << "\nCompact real spectrum R[k] (only 0..floor(N/2)):\n";
162 for (size_t k = 0; k < compact_spectrum.size(); ++k)
163 {
164 cout << " k=" << k << " R[k] = ";
166 cout << "\n";
167 }
168
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";
176 cout << " Compact round-trip sample 0 = " << restored_from_compact[0] << ".\n";
177 cout << "- Applying FFT<Real>::windowed_spectrum(..., hann_window(N)) reduces\n";
178 cout << " edge discontinuity; here Hann-windowed |X[1]| = "
179 << hann_magnitudes[1] << ".\n";
180 print_rule();
181 cout << "\n";
182 }
183
193 {
194 cout << "[2] Real convolution via FFT\n";
195 cout << "Goal: Apply a smoothing filter to a sequence of samples.\n";
196 print_rule();
197
198 // The signal samples
199 Array<double> samples = {3.0, 1.0, 4.0, 1.0, 5.0, 9.0};
200
201 // A smoothing kernel (3-point moving average weights: 0.25, 0.5, 0.25)
202 Array<double> kernel = {0.25, 0.50, 0.25};
203
204 // FFT::multiply automatically:
205 // 1. Pads both arrays to the nearest power-of-two size (N >= size1 + size2 - 1).
206 // 2. Computes FFT for both.
207 // 3. Multiplies them element-wise.
208 // 4. Computes IFFT to return the result to the time domain.
209 const auto filtered = FFTD::multiply(samples, kernel);
210
211 print_real_sequence("Original Samples:", samples);
212 print_real_sequence("Smoothing kernel (Filter):", kernel);
213 print_real_sequence("Filtered Result (Linear Convolution):", filtered);
214
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";
220 print_rule();
221 cout << "\n";
222 }
223
234 {
235 cout << "[3] Complex polynomial multiplication\n";
236 cout << "Goal: Multiply two polynomials with complex coefficients.\n";
237 print_rule();
238
239 // A(x) = (1 + 0i) + (2 - 1i)x + (0 + 3i)x^2
240 Array<Complex> a = {
241 Complex(1.0, 0.0),
242 Complex(2.0, -1.0),
243 Complex(0.0, 3.0)
244 };
245
246 // B(x) = (2 + 0i) + (-1 + 0i)x + (0.5 + 2i)x^2
247 Array<Complex> b = {
248 Complex(2.0, 0.0),
249 Complex(-1.0, 0.0),
250 Complex(0.5, 2.0)
251 };
252
253 // Compute C(x) = A(x) * B(x)
254 const auto c = FFTD::multiply(a, b);
255
256 print_complex_sequence("A(x) coefficients:", a);
257 print_complex_sequence("B(x) coefficients:", b);
258 print_complex_sequence("C(x) = A(x) * B(x):", c);
259
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";
262 print_rule();
263 cout << "\n";
264 }
265
267 {
268 cout << "[4] Generic iterable containers\n";
269 cout << "Goal: Use FFT directly with std::vector inputs.\n";
270 print_rule();
271
272 std::vector<double> real_signal = {1.0, 2.0, -1.0, 0.5, 3.0, -2.0, 4.0, -0.75};
273 const auto real_spectrum = FFTD::transform(real_signal);
274
275 std::vector<Complex> poly_a = {
276 Complex(1.0, 0.0),
277 Complex(2.0, -1.0),
278 Complex(0.0, 3.0)
279 };
280 std::vector<Complex> poly_b = {
281 Complex(2.0, 0.0),
282 Complex(-1.0, 0.0),
283 Complex(0.5, 2.0)
284 };
285 const auto product = FFTD::multiply(poly_a, poly_b);
286
287 cout << "Input std::vector<double> size: " << real_signal.size() << "\n";
288 cout << "FFT(real_signal) returned an Aleph Array with "
289 << real_spectrum.size() << " frequency bins.\n\n";
290
291 print_complex_sequence("FFT(std::vector<double>) spectrum:", real_spectrum);
292 print_complex_sequence("FFT::multiply(std::vector<Complex>, std::vector<Complex>):",
293 product);
294
295 cout << "No manual conversion to Array is needed: compatible iterable\n"
296 << "containers are accepted directly by FFT<Real>.\n";
297 print_rule();
298 cout << "\n";
299 }
300
302 {
303 cout << "[5] Concurrent FFT with ThreadPool\n";
304 cout << "Goal: Keep the sequential and concurrent APIs explicitly separated.\n";
305 print_rule();
306
307 ThreadPool pool(4);
308
309 Array<double> signal = {0.5, -1.25, 3.5, 2.0, -0.75, 4.25, 1.0, -2.0};
310 Array<double> kernel = {0.25, 0.50, 0.25};
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)})
318 };
319
320 const auto seq_spectrum = FFTD::transform(signal);
321 const auto par_spectrum = FFTD::ptransform(pool, signal);
322 const auto par_filtered = FFTD::pmultiply(pool, signal, kernel);
323 FFTD::Plan plan(complex_batch[0].size());
324 const auto batch_spectra = plan.ptransformed_batch(pool, complex_batch, false);
326 Array<Complex>({Complex(1.0, 0.0), Complex(2.0, -0.5),
327 Complex(-1.0, 0.25), Complex(0.5, 1.0)}),
328 Array<Complex>({Complex(-0.75, 1.5), Complex(1.25, -1.0),
329 Complex(0.0, 0.5), Complex(-2.0, 0.0)}),
330 Array<Complex>({Complex(0.5, -0.25), Complex(-1.5, 0.75),
331 Complex(2.5, 0.0), Complex(1.0, -1.5)})
332 };
333 const auto image_spectrum = FFTD::transformed2d(image);
334 FFTD::OverlapAddBank overlap_bank(2, kernel, 4);
336 signal,
337 Array<double>({1.0, -0.5, 0.75, 2.0, -1.25, 0.5, 0.25, -0.75})
338 };
339 const auto overlap_batch = overlap_bank.pconvolve(pool, overlap_signals);
340
341 print_real_sequence("Input signal:", signal);
342 print_complex_sequence("Sequential FFT(signal):", seq_spectrum);
343 print_complex_sequence("Concurrent FFT(signal):", par_spectrum);
344 print_real_sequence("Concurrent convolution result:", par_filtered);
345 print_complex_sequence("Concurrent batch FFT(signal 0):", batch_spectra[0]);
346 print_complex_sequence("2-D FFT(image row 0):", image_spectrum[0]);
347 print_real_sequence("Concurrent overlap-add bank (channel 0):", overlap_batch[0]);
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";
359
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"
370 << "channel.\n";
371 print_rule();
372 cout << "\n";
373 }
374
376 {
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";
380 print_rule();
381
382 ThreadPool pool(4);
383 Array<double> signal = {
384 0.0, 0.8, 1.0, 0.2, -0.7, -1.0, -0.3, 0.6,
385 1.1, 0.4, -0.5, -0.9
386 };
387 Array<double> analysis_window = {1.0, 0.75, 0.5, 1.0};
388 Array<double> fir = {0.2, 0.6, 0.2};
389 FFTD::BiquadSection iir = {0.075, 0.15, 0.075, 1.0, -0.9, 0.2};
390
391 FFTD::STFTOptions stft_options;
392 stft_options.hop_size = 2;
393 stft_options.centered = true;
394 stft_options.pad_end = true;
395 stft_options.fft_size = 8;
396 stft_options.validate_nola = true;
397
398 FFTD::ISTFTOptions istft_options;
399 istft_options.hop_size = 2;
400 istft_options.centered = true;
401 istft_options.signal_length = signal.size();
402 istft_options.validate_nola = true;
403
404 const auto frames = FFTD::frame_signal(signal, analysis_window.size(), 2, true);
405 const auto spectrogram = FFTD::stft(signal, analysis_window, stft_options);
406 const auto parallel_spectrogram = FFTD::pstft(pool,
407 signal,
410 const auto reconstructed = FFTD::istft(spectrogram, analysis_window, istft_options);
411 const auto zero_phase_fir = FFTD::filtfilt(signal, fir, 4);
412 const auto zero_phase_iir = FFTD::filtfilt(signal, iir);
413 const auto designed_fir = FFTD::firwin_lowpass(33, 1200.0, 8000.0, 70.0);
414 Array<double> firls_bands = {0.0, 900.0, 1200.0, 4000.0};
415 Array<double> firls_desired = {1.0, 1.0, 0.0, 0.0};
416 Array<double> firls_weights = {1.0, 12.0};
417 const auto designed_firls =
418 FFTD::firls(33, firls_bands, firls_desired, 8000.0, firls_weights);
419 Array<double> remez_weights = {1.0, 16.0};
420 const auto designed_remez =
421 FFTD::remez(33, firls_bands, firls_desired, 8000.0, remez_weights);
422 const auto designed_bandpass =
423 FFTD::chebyshev2_bandpass(4, 40.0, 700.0, 1500.0, 8000.0);
424 const auto designed_elliptic =
425 FFTD::elliptic_bandstop(4, 1.0, 40.0, 700.0, 1500.0, 8000.0);
426 const auto response = FFTD::freqz(iir, 5);
427 const auto designed_firls_response = FFTD::freqz(designed_firls, 5, false);
428 const auto designed_remez_response = FFTD::freqz(designed_remez, 5, false);
429 const auto designed_bandpass_response = FFTD::freqz(designed_bandpass, 5);
430 const auto designed_elliptic_response = FFTD::freqz(designed_elliptic, 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);
436 const auto min_pair_distance = FFTD::minimum_pole_zero_distance(iir);
437
438 FFTD::STFTProcessor processor(analysis_window, stft_options);
439 FFTD::ISTFTProcessor inverse_processor(spectrogram[0].size(),
444
445 FFTD::LFilter causal_iir(iir);
448 for (size_t i = 0; i < signal.size() / 2; ++i)
449 first_half.append(signal[i]);
450 for (size_t i = signal.size() / 2; i < signal.size(); ++i)
451 second_half.append(signal[i]);
452 const auto streamed_first = processor.process_block(first_half);
453 const auto streamed_second = processor.process_block(second_half);
454 const auto streamed_tail = processor.flush();
455 const auto causal_first = causal_iir.filter(first_half);
456 const auto causal_second = causal_iir.filter(second_half);
458 for (size_t i = 0; i < causal_first.size(); ++i)
460 for (size_t i = 0; i < causal_second.size(); ++i)
462 for (size_t i = 0; i < streamed_first.size(); ++i)
464 for (size_t i = 0; i < streamed_second.size(); ++i)
466 for (size_t i = 0; i < streamed_tail.size(); ++i)
468
471 for (size_t i = 0; i < spectrogram.size() / 2; ++i)
473 for (size_t i = spectrogram.size() / 2; i < spectrogram.size(); ++i)
474 second_frames.append(spectrogram[i]);
475 const auto reconstructed_first = inverse_processor.process_block(first_frames);
476 const auto reconstructed_second = inverse_processor.process_block(second_frames);
477 const auto reconstructed_tail = inverse_processor.flush();
478 for (size_t i = 0; i < reconstructed_first.size(); ++i)
480 for (size_t i = 0; i < reconstructed_second.size(); ++i)
482 for (size_t i = 0; i < reconstructed_tail.size(); ++i)
484
486 const auto batched = FFTD::batched_stft(batch_signals,
489 const auto frame_major =
490 FFTD::multichannel_stft(batch_signals,
493 FFTD::SpectrogramLayout::frame_channel_bin);
494 const auto multichannel_reconstructed =
495 FFTD::multichannel_istft(frame_major,
499 Array<size_t>({signal.size(), zero_phase_fir.size()}),
500 FFTD::SpectrogramLayout::frame_channel_bin);
502 for (size_t i = 0; i < std::min<size_t>(6, designed_fir.size()); ++i)
505 for (size_t i = 0; i < std::min<size_t>(6, designed_firls.size()); ++i)
508 for (size_t i = 0; i < std::min<size_t>(6, designed_remez.size()); ++i)
510
511 print_real_sequence("Input signal:", signal);
512 cout << "Raw frame_signal frames: " << frames.size()
513 << ", centered STFT frames: " << spectrogram.size()
514 << ", FFT bins per frame: " << spectrogram[0].size() << "\n";
515 cout << "Parallel STFT frames: " << parallel_spectrogram.size()
516 << ", streamed STFT frames: " << streamed_spectrogram.size()
517 << ", streamed ISTFT samples: " << streamed_reconstruction.size()
518 << ", batched STFT items: " << batched.size()
519 << ", frame-major multichannel frames: " << frame_major.size() << "\n";
520 print_real_sequence("ISTFT reconstruction:", reconstructed);
521 print_real_sequence("Streamed ISTFT reconstruction:", streamed_reconstruction);
522 print_real_sequence("Causal streamed IIR result:", causal_streamed);
523 print_real_sequence("Zero-phase FIR result:", zero_phase_fir);
524 print_real_sequence("Zero-phase IIR result:", zero_phase_iir);
525 print_real_sequence("firwin_lowpass taps (first 6):", designed_fir_preview);
526 print_real_sequence("firls low-pass taps (first 6):", designed_firls_preview);
527 print_real_sequence("remez low-pass taps (first 6):", designed_remez_preview);
528 print_real_sequence("Frame-major multichannel ISTFT(channel 0):",
530 print_complex_sequence("STFT frame 0:", spectrogram[0]);
531 print_complex_sequence("STFT frame 1:", spectrogram[1]);
532 print_complex_sequence("IIR poles:", poles);
533 print_complex_sequence("Chebyshev-II band-pass response samples:",
535 print_complex_sequence("Elliptic band-stop response samples:",
537 print_complex_sequence("firls low-pass response samples:",
538 designed_firls_response.response);
539 print_complex_sequence("remez low-pass response samples:",
540 designed_remez_response.response);
541 cout << "Pole/zero pairs tracked: " << pairs.size() << "\n";
542 print_real_sequence("Estimated group delay:", group);
543 print_real_sequence("Estimated phase delay:", phase);
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
548 << ", min pole/zero distance = " << min_pair_distance
549 << "\n";
550
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";
562 print_rule();
563 cout << "\n";
564 }
565
567 {
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";
571 print_rule();
572
573 constexpr double sample_rate = 8000.0;
574 Array<double> signal;
575 signal.reserve(256);
578 for (size_t i = 0; i < 256; ++i)
579 {
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));
585 }
586
587 const auto window = FFTD::hann_window(64);
588 FFTD::WelchOptions welch_options;
589 welch_options.hop_size = 32;
590 welch_options.fft_size = 128;
591
592 const auto psd = FFTD::welch(signal, window, sample_rate, welch_options);
593 const auto cross = FFTD::csd(signal, shifted_signal, window, sample_rate, welch_options);
594 const auto coh = FFTD::coherence(signal, shifted_signal, window, sample_rate, welch_options);
595
596 size_t peak = 0;
597 for (size_t i = 1; i < psd.density.size(); ++i)
598 if (psd.density[i] > psd.density[peak])
599 peak = i;
600
601 Array<double> prototype = {1.0, -1.0, 2.0, 0.5};
602 Array<double> kernel = {0.25, 0.5, 0.25};
603 const auto upfirdn_result = FFTD::upfirdn(prototype, kernel, 3, 2);
604 const auto resampled = FFTD::resample_poly(signal, 3, 2);
605
606 FFTD::OverlapSave overlap_save(kernel, 16);
608 const auto overlap_first =
609 overlap_save.process_block(Array<double>({1.0, -1.0, 0.5}));
610 const auto overlap_second =
611 overlap_save.process_block(Array<double>({2.0, 0.25, -0.5, 1.0}));
612 const auto overlap_tail = overlap_save.flush();
613 for (size_t i = 0; i < overlap_first.size(); ++i)
615 for (size_t i = 0; i < overlap_second.size(); ++i)
617 for (size_t i = 0; i < overlap_tail.size(); ++i)
619
620 FFTD::PartitionedConvolver partitioned(kernel, 8);
622 const auto partitioned_first =
623 partitioned.process_block(Array<double>({1.0, -1.0, 0.5}));
624 const auto partitioned_second =
625 partitioned.process_block(Array<double>({2.0, 0.25, -0.5, 1.0}));
626 const auto partitioned_tail = partitioned.flush();
627 for (size_t i = 0; i < partitioned_first.size(); ++i)
629 for (size_t i = 0; i < partitioned_second.size(); ++i)
631 for (size_t i = 0; i < partitioned_tail.size(); ++i)
633
635 for (size_t i = 0; i < std::min<size_t>(10, resampled.size()); ++i)
638 for (size_t i = 0; i < std::min<size_t>(10, upfirdn_result.size()); ++i)
640
641 print_real_sequence("upfirdn prototype result (first 10):", upfirdn_preview);
642 print_real_sequence("resample_poly(signal, 3/2) (first 10):", resampled_preview);
643 print_real_sequence("Overlap-save streaming result:", overlap_save_streamed);
644 print_real_sequence("Partitioned streaming result:", partitioned_streamed);
645
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()
655 << "\n";
656
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";
661 print_rule();
662 cout << "\n";
663 }
664}
665
669int main()
670{
671 std::cout << "\n=== Aleph-w: Fast Fourier Transform (FFT) Demonstration ===\n\n";
672
673 // Case 1: Decomposing a signal into frequency components.
675
676 // Case 2: Using convolution for filtering real data.
678
679 // Case 3: Multiplying polynomials with complex coefficients.
681
682 // Case 4: Working directly with compatible iterable containers.
684
685 // Case 5: Using the separate concurrent API with ThreadPool.
687
688 // Case 6: Short-time analysis and zero-phase FIR filtering.
690
691 // Case 7: Production DSP helpers on top of the FFT core.
693
694 std::cout << "All demonstrations completed successfully.\n";
695 return 0;
696}
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Fast Fourier Transform (FFT) and DSP Toolkit.
Definition fft.H:154
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.
Definition print_rule.H:39
static int * k
Dynamic array container with automatic resizing.