MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SpectralHelper.hpp
Go to the documentation of this file.
1#pragma once
2
4
6
7#include <unsupported/Eigen/FFT>
8
9/**
10 * @file SpectralTransform.hpp
11 * @brief Spectral transformation functions leveraging existing ecosystem
12 *
13 * - Uses existing FFT infrastructure from AnalysisHelper
14 * - Leverages existing windowing functions
15 * - Preserves structural data through OperationHelper
16 * - Integrates with EnergyAnalyzer for spectral analysis
17 * - Thread-safe operations
18 */
19namespace MayaFlux::Yantra {
20
21inline uint64_t smallest_size(std::vector<std::vector<double>>& data)
22{
23 if (data.empty())
24 return 0;
25
26 auto smallest = *std::ranges::min_element(data,
27 [](const auto& a, const auto& b) {
28 return a.size() < b.size();
29 });
30
31 return static_cast<uint64_t>(smallest.size());
32}
33
34/**
35 * @brief Common spectral processing helper to eliminate code duplication
36 * @tparam ProcessorFunc Function type for spectral processing
37 * @param data Input data span
38 * @param window_size Size of analysis window
39 * @param hop_size Hop size between windows
40 * @param processor Function to process spectrum in each window
41 * @return Processed audio data
42 */
43template <typename ProcessorFunc>
44std::vector<double> process_spectral_windows(
45 std::span<double> data,
46 uint32_t window_size,
47 uint32_t hop_size,
48 ProcessorFunc&& processor)
49{
50 const size_t num_windows = (data.size() >= window_size) ? (data.size() - window_size) / hop_size + 1 : 0;
51
52 if (num_windows == 0) {
53 return { data.begin(), data.end() };
54 }
55
56 std::vector<double> output(data.size(), 0.0);
57
59
60 Eigen::FFT<double> fft;
61
62 auto window_indices = std::views::iota(size_t { 0 }, num_windows);
63
64 std::ranges::for_each(window_indices, [&](size_t win) {
65 size_t start_idx = win * hop_size;
66 auto window_data = data.subspan(start_idx,
67 std::min(static_cast<size_t>(window_size), data.size() - start_idx));
68
69 Eigen::VectorXd windowed = Eigen::VectorXd::Zero(static_cast<Eigen::Index>(window_size));
70 const size_t actual_size = std::min(window_data.size(), hann_window.size());
71
72 for (size_t j = 0; j < actual_size; ++j) {
73 windowed(static_cast<Eigen::Index>(j)) = window_data[j] * hann_window[j];
74 }
75
76 Eigen::VectorXcd spectrum;
77 fft.fwd(spectrum, windowed);
78
79 std::forward<ProcessorFunc>(processor)(spectrum, win);
80
81 Eigen::VectorXd result;
82 fft.inv(result, spectrum);
83
84 for (size_t i = 0; i < static_cast<size_t>(result.size()) && start_idx + i < output.size(); ++i) {
85 output[start_idx + i] += result[static_cast<Eigen::Index>(i)];
86 }
87 });
88
89 return output;
90}
91
92/**
93 * @brief Windowing transformation using C++20 ranges (IN-PLACE)
94 * @tparam DataType OperationReadyData type
95 * @param input Input data - WILL BE MODIFIED
96 * @param window_type Type of window to apply
97 * @param window_size Size of window (0 = full data size)
98 * @return Windowed data
99 */
100template <OperationReadyData DataType>
101DataType transform_window(DataType& input,
103 uint32_t window_size = 0)
104{
105 auto [target_data, structure_info] = OperationHelper::extract_structured_double(input);
106
107 uint32_t size = window_size > 0 ? window_size : smallest_size(target_data);
108
109 auto window = Nodes::Generator::generate_window(size, window_type);
110
111 for (auto& span : target_data) {
112 auto data_view = span | std::views::take(std::min(size, static_cast<uint32_t>(span.size())));
113 auto window_view = window | std::views::take(data_view.size());
114
115 std::ranges::transform(data_view, window_view, data_view.begin(),
116 [](double sample, double win) { return sample * win; });
117 }
118
119 auto reconstructed_data = target_data
120 | std::views::transform([](const auto& span) {
121 return std::vector<double>(span.begin(), span.end());
122 })
123 | std::ranges::to<std::vector>();
124
125 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
126}
127
128/**
129 * @brief Windowing transformation using C++20 ranges (OUT-OF-PLACE)
130 * @tparam DataType OperationReadyData type
131 * @param input Input data - will NOT be modified
132 * @param window_type Type of window to apply
133 * @param window_size Size of window (0 = full data size)
134 * @param working_buffer Buffer for operations (will be resized if needed)
135 * @return Windowed data
136 */
137template <OperationReadyData DataType>
138DataType transform_window(DataType& input,
140 uint32_t window_size,
141 std::vector<std::vector<double>>& working_buffer)
142{
143 auto [target_data, structure_info] = OperationHelper::setup_operation_buffer(input, working_buffer);
144
145 uint32_t size = window_size > 0 ? window_size : smallest_size(working_buffer);
146
147 auto window = Nodes::Generator::generate_window(size, window_type);
148
149 working_buffer.resize(target_data.size());
150 for (size_t i = 0; i < target_data.size(); ++i) {
151 auto& span = target_data[i];
152 auto& buffer = working_buffer[i];
153 buffer.resize(span.size());
154
155 auto data_view = span | std::views::take(std::min(size, static_cast<uint32_t>(span.size())));
156 auto window_view = window | std::views::take(data_view.size());
157
158 std::ranges::transform(data_view, window_view, buffer.begin(),
159 [](double sample, double win) { return sample * win; });
160
161 if (span.size() > size) {
162 std::copy(span.begin() + size, span.end(), buffer.begin() + size);
163 }
164 }
165
166 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
167}
168
169/**
170 * @brief Spectral filtering using existing FFT infrastructure with C++20 ranges (IN-PLACE)
171 * @tparam DataType OperationReadyData type
172 * @param input Input data - WILL BE MODIFIED
173 * @param low_freq Low cutoff frequency (Hz)
174 * @param high_freq High cutoff frequency (Hz)
175 * @param sample_rate Sample rate (Hz)
176 * @param window_size FFT window size
177 * @param hop_size Hop size for overlap-add
178 * @return Filtered data
179 */
180template <OperationReadyData DataType>
181DataType transform_spectral_filter(DataType& input,
182 double low_freq,
183 double high_freq,
184 double sample_rate = 48000.0,
185 uint32_t window_size = 1024,
186 uint32_t hop_size = 256)
187{
188 auto [target_data, structure_info] = OperationHelper::extract_structured_double(input);
189
190 auto processor = [low_freq, high_freq, sample_rate](Eigen::VectorXcd& spectrum, size_t) {
191 auto bin_indices = std::views::iota(0, static_cast<int>(spectrum.size()));
192 std::ranges::for_each(bin_indices, [&](int bin) {
193 double freq = (bin * sample_rate) / (2.0 * (double)spectrum.size());
194 if (freq < low_freq || freq > high_freq) {
195 spectrum[bin] = 0.0;
196 }
197 });
198 };
199
200 for (auto& span : target_data) {
201 auto result = process_spectral_windows(span, window_size, hop_size, processor);
202 std::ranges::copy(result, span.begin());
203 }
204
205 auto reconstructed_data = target_data
206 | std::views::transform([](const auto& span) {
207 return std::vector<double>(span.begin(), span.end());
208 })
209 | std::ranges::to<std::vector>();
210
211 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
212}
213
214/**
215 * @brief Spectral filtering using existing FFT infrastructure with C++20 ranges (OUT-OF-PLACE)
216 * @tparam DataType OperationReadyData type
217 * @param input Input data - will NOT be modified
218 * @param low_freq Low cutoff frequency (Hz)
219 * @param high_freq High cutoff frequency (Hz)
220 * @param sample_rate Sample rate (Hz)
221 * @param window_size FFT window size
222 * @param hop_size Hop size for overlap-add
223 * @param working_buffer Buffer for operations (will be resized if needed)
224 * @return Filtered data
225 */
226template <OperationReadyData DataType>
227DataType transform_spectral_filter(DataType& input,
228 double low_freq,
229 double high_freq,
230 double sample_rate,
231 uint32_t window_size,
232 uint32_t hop_size,
233 std::vector<std::vector<double>>& working_buffer)
234{
235 auto [target_data, structure_info] = OperationHelper::setup_operation_buffer(input, working_buffer);
236
237 auto processor = [low_freq, high_freq, sample_rate](Eigen::VectorXcd& spectrum, size_t) {
238 auto bin_indices = std::views::iota(0, static_cast<int>(spectrum.size()));
239 std::ranges::for_each(bin_indices, [&](int bin) {
240 double freq = (bin * sample_rate) / (2.0 * (double)spectrum.size());
241 if (freq < low_freq || freq > high_freq) {
242 spectrum[bin] = 0.0;
243 }
244 });
245 };
246
247 working_buffer.resize(target_data.size());
248 for (size_t i = 0; i < target_data.size(); ++i) {
249 working_buffer[i] = process_spectral_windows(target_data[i], window_size, hop_size, processor);
250 }
251
252 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
253}
254
255/**
256 * @brief Pitch shifting using existing FFT from AnalysisHelper with C++20 ranges (IN-PLACE)
257 * @tparam DataType OperationReadyData type
258 * @param input Input data - WILL BE MODIFIED
259 * @param semitones Pitch shift in semitones
260 * @param window_size FFT window size
261 * @param hop_size Hop size for overlap-add
262 * @return Pitch-shifted data
263 */
264template <OperationReadyData DataType>
265DataType transform_pitch_shift(DataType& input,
266 double semitones,
267 uint32_t window_size = 1024,
268 uint32_t hop_size = 256)
269{
270 auto [target_data, structure_info] = OperationHelper::extract_structured_double(input);
271
272 if (semitones == 0.0) {
273 return input;
274 }
275
276 double pitch_ratio = std::pow(2.0, semitones / 12.0);
277
278 auto processor = [pitch_ratio](Eigen::VectorXcd& spectrum, size_t) {
279 Eigen::VectorXcd shifted_spectrum = Eigen::VectorXcd::Zero(spectrum.size());
280 auto bin_indices = std::views::iota(0, static_cast<int>(spectrum.size()));
281
282 std::ranges::for_each(bin_indices, [&](int bin) {
283 int shifted_bin = static_cast<int>(bin * pitch_ratio);
284 if (shifted_bin < shifted_spectrum.size()) {
285 shifted_spectrum[shifted_bin] = spectrum[bin];
286 }
287 });
288
289 spectrum = std::move(shifted_spectrum);
290 };
291
292 for (auto& span : target_data) {
293 auto result = process_spectral_windows(span, window_size, hop_size, processor);
294 std::ranges::copy(result, span.begin());
295 }
296
297 auto reconstructed_data = target_data
298 | std::views::transform([](const auto& span) {
299 return std::vector<double>(span.begin(), span.end());
300 })
301 | std::ranges::to<std::vector>();
302
303 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
304}
305
306/**
307 * @brief Pitch shifting using existing FFT from AnalysisHelper with C++20 ranges (OUT-OF-PLACE)
308 * @tparam DataType OperationReadyData type
309 * @param input Input data - will NOT be modified
310 * @param semitones Pitch shift in semitones
311 * @param window_size FFT window size
312 * @param hop_size Hop size for overlap-add
313 * @param working_buffer Buffer for operations (will be resized if needed)
314 * @return Pitch-shifted data
315 */
316template <OperationReadyData DataType>
317DataType transform_pitch_shift(DataType& input,
318 double semitones,
319 uint32_t window_size,
320 uint32_t hop_size,
321 std::vector<std::vector<double>>& working_buffer)
322{
323 auto [target_data, structure_info] = OperationHelper::setup_operation_buffer(input, working_buffer);
324
325 if (semitones == 0.0) {
326 return input;
327 }
328
329 double pitch_ratio = std::pow(2.0, semitones / 12.0);
330
331 auto processor = [pitch_ratio](Eigen::VectorXcd& spectrum, size_t) {
332 Eigen::VectorXcd shifted_spectrum = Eigen::VectorXcd::Zero(spectrum.size());
333 auto bin_indices = std::views::iota(0, static_cast<int>(spectrum.size()));
334
335 std::ranges::for_each(bin_indices, [&](int bin) {
336 int shifted_bin = static_cast<int>(bin * pitch_ratio);
337 if (shifted_bin < shifted_spectrum.size()) {
338 shifted_spectrum[shifted_bin] = spectrum[bin];
339 }
340 });
341
342 spectrum = std::move(shifted_spectrum);
343 };
344
345 working_buffer.resize(target_data.size());
346 for (size_t i = 0; i < target_data.size(); ++i) {
347 working_buffer[i] = process_spectral_windows(target_data[i], window_size, hop_size, processor);
348 }
349
350 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
351}
352
353/**
354 * @brief Spectral inversion (phase inversion in frequency domain) using C++20 ranges (IN-PLACE)
355 * @tparam DataType OperationReadyData type
356 * @param input Input data - WILL BE MODIFIED
357 * @param window_size FFT window size
358 * @param hop_size Hop size for overlap-add
359 * @return Spectrally inverted data
360 */
361template <OperationReadyData DataType>
362DataType transform_spectral_invert(DataType& input,
363 uint32_t window_size = 1024,
364 uint32_t hop_size = 256)
365{
366 auto [target_data, structure_info] = OperationHelper::extract_structured_double(input);
367
368 auto processor = [](Eigen::VectorXcd& spectrum, size_t) {
369 std::ranges::transform(spectrum, spectrum.begin(),
370 [](const std::complex<double>& bin) { return std::conj(bin); });
371 };
372
373 for (auto& span : target_data) {
374 auto result = process_spectral_windows(span, window_size, hop_size, processor);
375 std::ranges::copy(result, span.begin());
376 }
377
378 auto reconstructed_data = target_data
379 | std::views::transform([](const auto& span) {
380 return std::vector<double>(span.begin(), span.end());
381 })
382 | std::ranges::to<std::vector>();
383
384 return OperationHelper::reconstruct_from_double<DataType>(reconstructed_data, structure_info);
385}
386
387/**
388 * @brief Spectral inversion (phase inversion in frequency domain) using C++20 ranges (OUT-OF-PLACE)
389 * @tparam DataType OperationReadyData type
390 * @param input Input data - will NOT be modified
391 * @param window_size FFT window size
392 * @param hop_size Hop size for overlap-add
393 * @param working_buffer Buffer for operations (will be resized if needed)
394 * @return Spectrally inverted data
395 */
396template <OperationReadyData DataType>
397DataType transform_spectral_invert(DataType& input,
398 uint32_t window_size,
399 uint32_t hop_size,
400 std::vector<std::vector<double>>& working_buffer)
401{
402 auto [target_data, structure_info] = OperationHelper::setup_operation_buffer(input, working_buffer);
403
404 auto processor = [](Eigen::VectorXcd& spectrum, size_t) {
405 std::ranges::transform(spectrum, spectrum.begin(),
406 [](const std::complex<double>& bin) { return std::conj(bin); });
407 };
408
409 working_buffer.resize(target_data.size());
410 for (size_t i = 0; i < target_data.size(); ++i) {
411 working_buffer[i] = process_spectral_windows(target_data[i], window_size, hop_size, processor);
412 }
413
414 return OperationHelper::reconstruct_from_double<DataType>(working_buffer, structure_info);
415}
416
417}
static std::tuple< std::vector< std::span< double > >, DataStructureInfo > extract_structured_double(T &compute_data)
Extract structured double data from IO container or direct ComputeData with automatic container handl...
static auto setup_operation_buffer(T &input, std::vector< std::vector< double > > &working_buffer)
Setup operation buffer from IO or ComputeData type.
std::vector< double > generate_window(uint32_t size, WindowType window_type)
Generate window coefficients using C++20 ranges.
std::vector< double > process_spectral_windows(std::span< double > data, uint32_t window_size, uint32_t hop_size, ProcessorFunc &&processor)
Common spectral processing helper to eliminate code duplication.
DataType transform_spectral_filter(DataType &input, double low_freq, double high_freq, double sample_rate=48000.0, uint32_t window_size=1024, uint32_t hop_size=256)
Spectral filtering using existing FFT infrastructure with C++20 ranges (IN-PLACE)
DataType transform_spectral_invert(DataType &input, uint32_t window_size=1024, uint32_t hop_size=256)
Spectral inversion (phase inversion in frequency domain) using C++20 ranges (IN-PLACE)
uint64_t smallest_size(std::vector< std::vector< double > > &data)
DataType transform_window(DataType &input, Nodes::Generator::WindowType window_type, uint32_t window_size=0)
Windowing transformation using C++20 ranges (IN-PLACE)
DataType transform_pitch_shift(DataType &input, double semitones, uint32_t window_size=1024, uint32_t hop_size=256)
Pitch shifting using existing FFT from AnalysisHelper with C++20 ranges (IN-PLACE)