EchoMap 2026-07-25 6d3977c
An experimental cross-platform digital signal processing application for sound-source localisation.
Loading...
Searching...
No Matches
FrequencySpectrumFactory.cpp
Go to the documentation of this file.
1
9
11
12#include <fftw3.h>
13
14#ifdef __EMSCRIPTEN__
16#else
17#include <cmath>
18#endif
19
21#include "../Signal.hpp"
22#include "FFTWBuffers.hpp"
23
24namespace echomap
25{
26
28 const Signal& signal,
29 const WindowFunctions::AllFunctions window_function,
30 const std::size_t transform_size
31)
32{
33 if (!signal.is_uniformly_sampled())
34 throw std::runtime_error(std::format("Attempted to transform variably sampled {}.", signal.get_name()));
35
36 const auto display_name = std::format(
37 "{} ({} DFT @ {})",
38 signal.get_name(),
40 transform_size
41 );
42
43 if (transform_size == 0)
44 return std::unique_ptr<FrequencySpectrum>(new FrequencySpectrum(window_function, display_name));
45
46 // Create FFTW buffers, making a separate input buffer if and only if we're using a non-constant input transform.
47 const FFTWBuffers context(
48 transform_size,
49 std::holds_alternative<WindowFunctions::Constant>(window_function) ? std::make_optional(signal.amplitudes())
50 : std::nullopt
51 );
52
53 // Pre-process input as advised by the window function.
54 const auto scale_divisor = prepare_input(context, window_function, signal.amplitudes());
55
56 // Create a plan, do the FFT, and clean up. (Buffers are RAII scoped to this function.)
57 const fftwf_plan plan = // NOLINT(*-misplaced-const) - Declaration is correct.
58 fftwf_plan_dft_r2c_1d(static_cast<int>(transform_size), context.input, context.coefficients, FFTW_ESTIMATE);
59
60 if (plan == nullptr)
61 throw std::runtime_error("Failed to create an FFTW plan.");
62
63 fftwf_execute(plan);
64 fftwf_destroy_plan(plan);
65
66 // Construct the FrequencySpectrum from the coefficients.
67 const auto bin_count = transform_size / 2 + 1;
68 auto spectrum = std::unique_ptr<FrequencySpectrum>(new FrequencySpectrum(window_function, display_name));
69 spectrum->reserve_bins(bin_count);
70
71 for (std::size_t bin_idx = 0; bin_idx < bin_count; ++bin_idx) {
72 const auto is_dc = bin_idx == 0;
73 const auto is_nyquist = transform_size % 2 == 0 && bin_idx == transform_size / 2;
74 const auto scale = (is_dc || is_nyquist ? 1.0f : 2.0f) / scale_divisor;
75
76 const auto real = context.coefficients[bin_idx][0];
77 const auto imag = context.coefficients[bin_idx][1];
78 const auto linear_amplitude = std::sqrt(real * real + imag * imag) * scale;
79
80 spectrum->emplace_bin(
81 static_cast<float>(bin_idx) * static_cast<float>(signal.get_sample_rate()) /
82 static_cast<float>(transform_size),
83 amplitude_to_dbfs(linear_amplitude),
84 std::atan2(imag, real)
85 );
86 }
87
88 return spectrum;
89}
90
92 const FFTWBuffers& buffers,
93 const WindowFunctions::AllFunctions window_function,
95)
96{
97 assert(input.size() >= buffers.input_size);
98
99 return std::visit(
100 [&buffers, &input]<WindowFunction WindowT>(WindowT) {
101 return WindowFunctions::apply_window<WindowT>(buffers, input);
102 },
103 window_function
104 );
105}
106
108 const Signal::Sample::AmplitudeT amplitude
109) noexcept
110{
111#if defined(__EMSCRIPTEN__) || defined(__DOXYGEN__)
112 constexpr auto full = std::max(web::abs(Signal::normalised_range.first), web::abs(Signal::normalised_range.second));
113#else
114 constexpr auto full = std::max(std::abs(Signal::normalised_range.first), std::abs(Signal::normalised_range.second));
115#endif
116 assert(full > 0.0f);
117
118 constexpr auto maximum_ratio = 1.0e-6f; // 20log_{10}(1e-6) = -120dB.
119 return 20.0f * std::log10(std::max(std::abs(amplitude) / full, maximum_ratio));
120}
121
122} // namespace echomap
EmscriptenExtra specification.
static constexpr auto abs(T const &x) noexcept
Constexpr absolute value function for pre-C++23 Emscripten compilers.
FFTWBuffers specification.
FrequencySpectrumFactory specification.
FrequencySpectrum specification.
Audio signal class specification.
T atan2(T... args)
Simple RAII wrapper for commonly used C types from FFTW(f).
std::size_t input_size
Size of the input vector.
fftwf_complex_fwd *const coefficients
Complex coefficients (output of real-to-complex FFT).
float * input
Real-valued inputs, either managed by us or aliased by the user.
static Signal::Sample::AmplitudeT amplitude_to_dbfs(Signal::Sample::AmplitudeT amplitude) noexcept
Convert an amplitude to a dBfs (decibels relative to full-scale) quantity.
static float prepare_input(const FFTWBuffers &buffers, WindowFunctions::AllFunctions window_function, std::span< const Signal::Sample::AmplitudeT > input)
Copies and prepare the input amplitude time-series for FFT with the given window function preference.
static std::unique_ptr< FrequencySpectrum > create_frequency_spectrum(const Signal &signal, WindowFunctions::AllFunctions window_function, std::size_t transform_size)
Construct a FrequencySpectrum of the given uniformly sampled Signal.
A Signal sampled in the frequency domain.
A single channel of discretely sampled audio data.
Definition Signal.hpp:40
static constexpr std::pair< Sample::AmplitudeT, Sample::AmplitudeT > normalised_range
The range within which the amplitude values are normalised.
Definition Signal.hpp:74
static constexpr std::string_view get_window_name()
Retrieve a human-readable name for the templated window function.
T format(T... args)
T holds_alternative(T... args)
T log10(T... args)
T make_optional(T... args)
T max(T... args)
The main EchoMap outermost namespace for all non-exported symbols.
T size(T... args)
T sqrt(T... args)
float AmplitudeT
Type for sample amplitudes.
Definition Signal.hpp:48
T visit(T... args)