MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
AnalysisHelper.cpp
Go to the documentation of this file.
1#include "AnalysisHelper.hpp"
2
3#include <numeric>
4
6
7#include "unsupported/Eigen/FFT"
8
9namespace MayaFlux::Yantra {
10
11std::vector<double> compute_dynamic_range_energy(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
12{
13 std::vector<double> dr_values(num_windows);
14
15 std::vector<size_t> indices(num_windows);
16 std::iota(indices.begin(), indices.end(), 0);
17
18 MayaFlux::Parallel::for_each(MayaFlux::Parallel::par_unseq, indices.begin(), indices.end(),
19 [&](size_t i) {
20 const size_t start_idx = i * hop_size;
21 const size_t end_idx = std::min(start_idx + window_size, data.size());
22 auto window = data.subspan(start_idx, end_idx - start_idx);
23
24 double min_val = std::numeric_limits<double>::max();
25 double max_val = std::numeric_limits<double>::lowest();
26
27 for (double sample : window) {
28 double abs_sample = std::abs(sample);
29 min_val = std::min(min_val, abs_sample);
30 max_val = std::max(max_val, abs_sample);
31 }
32
33 if (min_val < 1e-10)
34 min_val = 1e-10;
35 dr_values[i] = 20.0 * std::log10(max_val / min_val);
36 });
37
38 return dr_values;
39}
40
41std::vector<double> compute_zero_crossing_energy(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
42{
43 std::vector<double> zcr_values(num_windows);
44
45 std::vector<size_t> indices(num_windows);
46 std::iota(indices.begin(), indices.end(), 0);
47
48 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
49 [&](size_t i) {
50 const size_t start_idx = i * hop_size;
51 const size_t end_idx = std::min(start_idx + window_size, data.size());
52 auto window = data.subspan(start_idx, end_idx - start_idx);
53
54 int zero_crossings = 0;
55 for (size_t j = 1; j < window.size(); ++j) {
56 if ((window[j] >= 0.0) != (window[j - 1] >= 0.0)) {
57 zero_crossings++;
58 }
59 }
60 zcr_values[i] = static_cast<double>(zero_crossings) / static_cast<double>(window.size() - 1);
61 });
62
63 return zcr_values;
64}
65
66std::vector<double> compute_power_energy(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
67{
68 std::vector<double> power_values(num_windows);
69
70 std::vector<size_t> indices(num_windows);
71 std::iota(indices.begin(), indices.end(), 0);
72
73 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
74 [&](size_t i) {
75 const size_t start_idx = i * hop_size;
76 const size_t end_idx = std::min(start_idx + window_size, data.size());
77 auto window = data.subspan(start_idx, end_idx - start_idx);
78
79 double sum_squares = 0.0;
80 for (double sample : window) {
81 sum_squares += sample * sample;
82 }
83 power_values[i] = sum_squares;
84 });
85
86 return power_values;
87}
88
89std::vector<double> compute_peak_energy(std::span<const double> data, const uint32_t num_windows, const uint32_t hop_size, const uint32_t window_size)
90{
91 std::vector<double> peak_values(num_windows);
92
93 std::vector<size_t> indices(num_windows);
94 std::iota(indices.begin(), indices.end(), 0);
95
96 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
97 [&](size_t i) {
98 const size_t start_idx = i * hop_size;
99 const size_t end_idx = std::min(start_idx + window_size, data.size());
100 auto window = data.subspan(start_idx, end_idx - start_idx);
101
102 double max_val = 0.0;
103 for (double sample : window) {
104 max_val = std::max(max_val, std::abs(sample));
105 }
106 peak_values[i] = max_val;
107 });
108
109 return peak_values;
110}
111
112std::vector<double> compute_rms_energy(std::span<const double> data, const uint32_t num_windows, const uint32_t hop_size, const uint32_t window_size)
113{
114 std::vector<double> rms_values(num_windows);
115
116 std::vector<size_t> indices(num_windows);
117 std::iota(indices.begin(), indices.end(), 0);
118
119 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
120 [&](size_t i) {
121 const size_t start_idx = i * hop_size;
122 const size_t end_idx = std::min(start_idx + window_size, data.size());
123 auto window = data.subspan(start_idx, end_idx - start_idx);
124
125 double sum_squares = 0.0;
126 for (double sample : window) {
127 sum_squares += sample * sample;
128 }
129 rms_values[i] = std::sqrt(sum_squares / static_cast<double>(window.size()));
130 });
131
132 return rms_values;
133}
134
135std::vector<double> compute_spectral_energy(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
136{
137 std::vector<double> spectral_energy(num_windows);
138
139 std::vector<size_t> indices(num_windows);
140 std::iota(indices.begin(), indices.end(), 0);
141
142 Eigen::VectorXd hanning_window(window_size);
143 for (uint32_t i = 0; i < window_size; ++i) {
144 hanning_window(i) = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (window_size - 1)));
145 }
146
147 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
148 [&](size_t i) {
149 const size_t start_idx = i * hop_size;
150 const size_t end_idx = std::min(start_idx + window_size, data.size());
151 auto window = data.subspan(start_idx, end_idx - start_idx);
152
153 Eigen::VectorXd windowed_data = Eigen::VectorXd::Zero(window_size);
154 const size_t actual_size = window.size();
155
156 for (int j = 0; j < actual_size; ++j) {
157 windowed_data(j) = window[j] * hanning_window(j);
158 }
159
160 Eigen::FFT<double> fft;
161 Eigen::VectorXcd fft_result;
162 fft.fwd(fft_result, windowed_data);
163
164 double energy = 0.0;
165 for (int j = 0; j < fft_result.size(); ++j) {
166 energy += std::norm(fft_result(j));
167 }
168
169 spectral_energy[i] = energy / static_cast<double>(window_size);
170 });
171
172 return spectral_energy;
173}
174
175std::vector<double> compute_harmonic_energy(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
176{
177 std::vector<double> harmonic_energy(num_windows);
178
179 std::vector<size_t> indices(num_windows);
180 std::iota(indices.begin(), indices.end(), 0);
181
182 Eigen::VectorXd hanning_window(window_size);
183 for (uint32_t i = 0; i < window_size; ++i) {
184 hanning_window(i) = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (window_size - 1)));
185 }
186
187 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
188 [&](size_t i) {
189 const size_t start_idx = i * hop_size;
190 const size_t end_idx = std::min(start_idx + window_size, data.size());
191 auto window = data.subspan(start_idx, end_idx - start_idx);
192
193 Eigen::VectorXd windowed_data = Eigen::VectorXd::Zero(window_size);
194 const size_t actual_size = window.size();
195
196 for (int j = 0; j < actual_size; ++j) {
197 windowed_data(j) = window[j] * hanning_window(j);
198 }
199
200 Eigen::FFT<double> fft;
201 Eigen::VectorXcd fft_result;
202 fft.fwd(fft_result, windowed_data);
203
204 const int harmonic_bins = std::max(1, static_cast<int>(fft_result.size() / 8));
205 double energy = 0.0;
206
207 for (int j = 1; j < harmonic_bins; ++j) {
208 energy += std::norm(fft_result(j));
209 }
210
211 harmonic_energy[i] = energy / static_cast<double>(harmonic_bins);
212 });
213
214 return harmonic_energy;
215}
216
218 std::span<const double> data,
219 double threshold)
220{
221 std::vector<size_t> positions;
222 positions.reserve(data.size() / 4);
223
224 for (size_t i = 1; i < data.size(); ++i) {
225 bool current_above = (data[i] >= threshold);
226 bool previous_above = (data[i - 1] >= threshold);
227
228 if (current_above != previous_above) {
229 positions.push_back(i);
230 }
231 }
232
233 positions.shrink_to_fit();
234 return positions;
235}
236
237std::vector<size_t> find_peak_positions(
238 std::span<const double> data,
239 double threshold,
240 size_t min_distance)
241{
242 if (data.size() < 3) {
243 return {};
244 }
245
246 std::vector<size_t> positions;
247 positions.reserve(data.size() / 100);
248
249 size_t last_peak = 0;
250
251 for (size_t i = 1; i < data.size() - 1; ++i) {
252 double abs_val = std::abs(data[i]);
253
254 if (abs_val > threshold && abs_val >= std::abs(data[i - 1]) && abs_val >= std::abs(data[i + 1])) {
255
256 if (positions.empty() || (i - last_peak) >= min_distance) {
257 positions.push_back(i);
258 last_peak = i;
259 }
260 }
261 }
262
263 positions.shrink_to_fit();
264 return positions;
265}
266
267std::vector<size_t> find_onset_positions(
268 std::span<const double> data,
269 uint32_t window_size,
270 uint32_t hop_size,
271 double threshold)
272{
273 const size_t num_windows = (data.size() - window_size) / hop_size + 1;
274
275 if (num_windows < 2) {
276 return {};
277 }
278
279 std::vector<double> flux_values(num_windows - 1);
280
281 Eigen::VectorXd hanning_window(window_size);
282 for (uint32_t i = 0; i < window_size; ++i) {
283 hanning_window(i) = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (window_size - 1)));
284 }
285
286 Eigen::VectorXcd prev_spectrum;
287
288 for (size_t i = 0; i < num_windows; ++i) {
289 const size_t start_idx = i * hop_size;
290 const size_t end_idx = std::min(start_idx + window_size, data.size());
291 auto window = data.subspan(start_idx, end_idx - start_idx);
292
293 Eigen::VectorXd windowed_data = Eigen::VectorXd::Zero(window_size);
294 for (size_t j = 0; j < window.size(); ++j) {
295 windowed_data(j) = window[j] * hanning_window(j);
296 }
297
298 Eigen::FFT<double> fft;
299 Eigen::VectorXcd spectrum;
300 fft.fwd(spectrum, windowed_data);
301
302 if (i > 0) {
303 double flux = 0.0;
304 for (int j = 0; j < spectrum.size(); ++j) {
305 double curr_mag = std::abs(spectrum(j));
306 double prev_mag = std::abs(prev_spectrum(j));
307 double diff = curr_mag - prev_mag;
308 if (diff > 0) {
309 flux += diff;
310 }
311 }
312 flux_values[i - 1] = flux;
313 }
314
315 prev_spectrum = spectrum;
316 }
317
318 std::vector<size_t> positions;
319
320 double max_flux = *std::max_element(flux_values.begin(), flux_values.end());
321 if (max_flux > 0.0) {
322 for (size_t i = 0; i < flux_values.size(); ++i) {
323 flux_values[i] /= max_flux;
324 }
325 }
326
327 for (size_t i = 1; i < flux_values.size() - 1; ++i) {
328 if (flux_values[i] > threshold && flux_values[i] > flux_values[i - 1] && flux_values[i] >= flux_values[i + 1]) {
329
330 size_t sample_pos = (i + 1) * hop_size;
331 positions.push_back(sample_pos);
332 }
333 }
334
335 return positions;
336}
337
338std::vector<double> compute_mean_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
339{
340 std::vector<double> mean_values(num_windows);
341
342 std::vector<size_t> indices(num_windows);
343 std::iota(indices.begin(), indices.end(), 0);
344
345 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
346 [&](size_t i) {
347 const size_t start_idx = i * hop_size;
348 const size_t end_idx = std::min(start_idx + window_size, data.size());
349 auto window = data.subspan(start_idx, end_idx - start_idx);
350
351 double sum = 0.0;
352 for (double sample : window) {
353 sum += sample;
354 }
355 mean_values[i] = sum / static_cast<double>(window.size());
356 });
357
358 return mean_values;
359}
360
361std::vector<double> compute_variance_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
362{
363 std::vector<double> variance_values(num_windows);
364
365 std::vector<size_t> indices(num_windows);
366 std::iota(indices.begin(), indices.end(), 0);
367
368 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
369 [&](size_t i) {
370 const size_t start_idx = i * hop_size;
371 const size_t end_idx = std::min(start_idx + window_size, data.size());
372 auto window = data.subspan(start_idx, end_idx - start_idx);
373
374 if (window.size() <= 1) {
375 variance_values[i] = 0.0;
376 return;
377 }
378
379 double sum = 0.0;
380 for (double sample : window) {
381 sum += sample;
382 }
383 double mean = sum / static_cast<double>(window.size());
384
385 double sum_sq_diff = 0.0;
386 for (double sample : window) {
387 double diff = sample - mean;
388 sum_sq_diff += diff * diff;
389 }
390
391 double divisor = sample_variance ? static_cast<double>(window.size() - 1) : static_cast<double>(window.size());
392
393 variance_values[i] = sum_sq_diff / divisor;
394 });
395
396 return variance_values;
397}
398
399std::vector<double> compute_std_dev_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
400{
401 auto variance_values = compute_variance_statistic(data, num_windows, hop_size, window_size, sample_variance);
402
403 std::vector<double> std_dev_values(num_windows);
404 std::transform(std::execution::par_unseq, variance_values.begin(), variance_values.end(), std_dev_values.begin(),
405 [](double variance) { return std::sqrt(variance); });
406
407 return std_dev_values;
408}
409
410std::vector<double> compute_skewness_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
411{
412 std::vector<double> skewness_values(num_windows);
413
414 std::vector<size_t> indices(num_windows);
415 std::iota(indices.begin(), indices.end(), 0);
416
417 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
418 [&](size_t i) {
419 const size_t start_idx = i * hop_size;
420 const size_t end_idx = std::min(start_idx + window_size, data.size());
421 auto window = data.subspan(start_idx, end_idx - start_idx);
422
423 if (window.size() < 2) {
424 skewness_values[i] = 0.0;
425 return;
426 }
427
428 double sum = 0.0;
429 for (double sample : window) {
430 sum += sample;
431 }
432 double mean = sum / static_cast<double>(window.size());
433
434 double sum_sq_diff = 0.0;
435 double sum_cube_diff = 0.0;
436 for (double sample : window) {
437 double diff = sample - mean;
438 double sq_diff = diff * diff;
439 sum_sq_diff += sq_diff;
440 sum_cube_diff += sq_diff * diff;
441 }
442
443 double variance = sum_sq_diff / static_cast<double>(window.size());
444 if (variance <= 0.0) {
445 skewness_values[i] = 0.0;
446 return;
447 }
448
449 double std_dev = std::sqrt(variance);
450 double third_moment = sum_cube_diff / static_cast<double>(window.size());
451
452 skewness_values[i] = third_moment / (std_dev * std_dev * std_dev);
453 });
454
455 return skewness_values;
456}
457
458std::vector<double> compute_kurtosis_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
459{
460 std::vector<double> kurtosis_values(num_windows);
461
462 std::vector<size_t> indices(num_windows);
463 std::iota(indices.begin(), indices.end(), 0);
464
465 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
466 [&](size_t i) {
467 const size_t start_idx = i * hop_size;
468 const size_t end_idx = std::min(start_idx + window_size, data.size());
469 auto window = data.subspan(start_idx, end_idx - start_idx);
470
471 if (window.size() < 2) {
472 kurtosis_values[i] = 0.0;
473 return;
474 }
475
476 double sum = 0.0;
477 for (double sample : window) {
478 sum += sample;
479 }
480 double mean = sum / static_cast<double>(window.size());
481
482 double sum_sq_diff = 0.0;
483 double sum_fourth_diff = 0.0;
484 for (double sample : window) {
485 double diff = sample - mean;
486 double sq_diff = diff * diff;
487 sum_sq_diff += sq_diff;
488 sum_fourth_diff += sq_diff * sq_diff;
489 }
490
491 double variance = sum_sq_diff / static_cast<double>(window.size());
492 if (variance <= 0.0) {
493 kurtosis_values[i] = 0.0;
494 return;
495 }
496
497 double fourth_moment = sum_fourth_diff / static_cast<double>(window.size());
498
499 kurtosis_values[i] = (fourth_moment / (variance * variance)) - 3.0;
500 });
501
502 return kurtosis_values;
503}
504
505std::vector<double> compute_median_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
506{
507 std::vector<double> median_values(num_windows);
508
509 std::vector<size_t> indices(num_windows);
510 std::iota(indices.begin(), indices.end(), 0);
511
512 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
513 [&](size_t i) {
514 const size_t start_idx = i * hop_size;
515 const size_t end_idx = std::min(start_idx + window_size, data.size());
516 auto window = data.subspan(start_idx, end_idx - start_idx);
517
518 if (window.empty()) {
519 median_values[i] = 0.0;
520 return;
521 }
522
523 std::vector<double> sorted_window(window.begin(), window.end());
524 std::ranges::sort(sorted_window);
525
526 size_t n = sorted_window.size();
527 if (n % 2 == 0) {
528 median_values[i] = (sorted_window[n / 2 - 1] + sorted_window[n / 2]) / 2.0;
529 } else {
530 median_values[i] = sorted_window[n / 2];
531 }
532 });
533
534 return median_values;
535}
536
537std::vector<double> compute_percentile_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, double percentile)
538{
539 std::vector<double> percentile_values(num_windows);
540
541 if (percentile < 0.0)
542 percentile = 0.0;
543 if (percentile > 100.0)
544 percentile = 100.0;
545
546 std::vector<size_t> indices(num_windows);
547 std::iota(indices.begin(), indices.end(), 0);
548
549 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
550 [&](size_t i) {
551 const size_t start_idx = i * hop_size;
552 const size_t end_idx = std::min(start_idx + window_size, data.size());
553 auto window = data.subspan(start_idx, end_idx - start_idx);
554
555 if (window.empty()) {
556 percentile_values[i] = 0.0;
557 return;
558 }
559
560 std::vector<double> sorted_window(window.begin(), window.end());
561 std::ranges::sort(sorted_window);
562
563 if (percentile == 0.0) {
564 percentile_values[i] = sorted_window.front();
565 return;
566 }
567 if (percentile == 100.0) {
568 percentile_values[i] = sorted_window.back();
569 return;
570 }
571
572 double index = (percentile / 100.0) * static_cast<double>(sorted_window.size() - 1);
573 auto lower_idx = static_cast<size_t>(std::floor(index));
574 auto upper_idx = static_cast<size_t>(std::ceil(index));
575
576 if (lower_idx == upper_idx) {
577 percentile_values[i] = sorted_window[lower_idx];
578 } else {
579 double weight = index - static_cast<double>(lower_idx);
580 percentile_values[i] = sorted_window[lower_idx] * (1.0 - weight) + sorted_window[upper_idx] * weight;
581 }
582 });
583
584 return percentile_values;
585}
586
587std::vector<double> compute_entropy_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, size_t num_bins)
588{
589 std::vector<double> entropy_values(num_windows);
590
591 std::vector<size_t> indices(num_windows);
592 std::iota(indices.begin(), indices.end(), 0);
593
594 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
595 [&](size_t i) {
596 const size_t start_idx = i * hop_size;
597 const size_t end_idx = std::min(start_idx + window_size, data.size());
598 auto window = data.subspan(start_idx, end_idx - start_idx);
599
600 if (window.empty()) {
601 entropy_values[i] = 0.0;
602 return;
603 }
604
605 size_t bins = num_bins;
606 if (bins == 0) {
607 bins = static_cast<size_t>(std::ceil(std::log2(window.size()) + 1));
608 bins = std::max(bins, static_cast<size_t>(1));
609 bins = std::min(bins, window.size());
610 }
611
612 auto [min_it, max_it] = std::ranges::minmax_element(window);
613 double min_val = *min_it;
614 double max_val = *max_it;
615
616 if (max_val <= min_val) {
617 entropy_values[i] = 0.0;
618 return;
619 }
620
621 double bin_width = (max_val - min_val) / static_cast<double>(bins);
622
623 std::vector<size_t> bin_counts(bins, 0);
624 for (double value : window) {
625 auto bin_idx = static_cast<size_t>((value - min_val) / bin_width);
626 if (bin_idx >= bins)
627 bin_idx = bins - 1;
628 bin_counts[bin_idx]++;
629 }
630
631 double entropy = 0.0;
632 size_t total_count = window.size();
633
634 for (size_t count : bin_counts) {
635 if (count > 0) {
636 double probability = static_cast<double>(count) / static_cast<double>(total_count);
637 entropy -= probability * std::log2(probability);
638 }
639 }
640
641 entropy_values[i] = entropy;
642 });
643
644 return entropy_values;
645}
646
647std::vector<double> compute_min_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
648{
649 std::vector<double> min_values(num_windows);
650
651 std::vector<size_t> indices(num_windows);
652 std::iota(indices.begin(), indices.end(), 0);
653
654 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
655 [&](size_t i) {
656 const size_t start_idx = i * hop_size;
657 const size_t end_idx = std::min(start_idx + window_size, data.size());
658 auto window = data.subspan(start_idx, end_idx - start_idx);
659
660 min_values[i] = *std::ranges::min_element(window);
661 });
662
663 return min_values;
664}
665
666std::vector<double> compute_max_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
667{
668 std::vector<double> max_values(num_windows);
669
670 std::vector<size_t> indices(num_windows);
671 std::iota(indices.begin(), indices.end(), 0);
672
673 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
674 [&](size_t i) {
675 const size_t start_idx = i * hop_size;
676 const size_t end_idx = std::min(start_idx + window_size, data.size());
677 auto window = data.subspan(start_idx, end_idx - start_idx);
678
679 max_values[i] = *std::ranges::max_element(window);
680 });
681
682 return max_values;
683}
684
685std::vector<double> compute_range_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
686{
687 std::vector<double> range_values(num_windows);
688
689 std::vector<size_t> indices(num_windows);
690 std::iota(indices.begin(), indices.end(), 0);
691
692 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
693 [&](size_t i) {
694 const size_t start_idx = i * hop_size;
695 const size_t end_idx = std::min(start_idx + window_size, data.size());
696 auto window = data.subspan(start_idx, end_idx - start_idx);
697
698 auto [min_it, max_it] = std::ranges::minmax_element(window);
699 range_values[i] = *max_it - *min_it;
700 });
701
702 return range_values;
703}
704
705std::vector<double> compute_sum_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
706{
707 std::vector<double> sum_values(num_windows);
708
709 std::vector<size_t> indices(num_windows);
710 std::iota(indices.begin(), indices.end(), 0);
711
712 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
713 [&](size_t i) {
714 const size_t start_idx = i * hop_size;
715 const size_t end_idx = std::min(start_idx + window_size, data.size());
716 auto window = data.subspan(start_idx, end_idx - start_idx);
717
718 sum_values[i] = std::accumulate(window.begin(), window.end(), 0.0);
719 });
720
721 return sum_values;
722}
723
724std::vector<double> compute_count_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
725{
726 std::vector<double> count_values(num_windows);
727
728 std::vector<size_t> indices(num_windows);
729 std::iota(indices.begin(), indices.end(), 0);
730
731 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
732 [&](size_t i) {
733 const size_t start_idx = i * hop_size;
734 const size_t end_idx = std::min(start_idx + window_size, data.size());
735
736 count_values[i] = static_cast<double>(end_idx - start_idx);
737 });
738
739 return count_values;
740}
741
742std::vector<double> compute_mad_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
743{
744 std::vector<double> mad_values(num_windows);
745
746 std::vector<size_t> indices(num_windows);
747 std::iota(indices.begin(), indices.end(), 0);
748
749 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
750 [&](size_t i) {
751 const size_t start_idx = i * hop_size;
752 const size_t end_idx = std::min(start_idx + window_size, data.size());
753 auto window = data.subspan(start_idx, end_idx - start_idx);
754
755 if (window.empty()) {
756 mad_values[i] = 0.0;
757 return;
758 }
759
760 std::vector<double> sorted_window(window.begin(), window.end());
761 std::ranges::sort(sorted_window);
762
763 double median {};
764 size_t n = sorted_window.size();
765 if (n % 2 == 0) {
766 median = (sorted_window[n / 2 - 1] + sorted_window[n / 2]) / 2.0;
767 } else {
768 median = sorted_window[n / 2];
769 }
770
771 std::vector<double> abs_deviations;
772 abs_deviations.reserve(window.size());
773 for (double val : window) {
774 abs_deviations.push_back(std::abs(val - median));
775 }
776
777 std::ranges::sort(abs_deviations);
778 size_t mad_n = abs_deviations.size();
779 if (mad_n % 2 == 0) {
780 mad_values[i] = (abs_deviations[mad_n / 2 - 1] + abs_deviations[mad_n / 2]) / 2.0;
781 } else {
782 mad_values[i] = abs_deviations[mad_n / 2];
783 }
784 });
785
786 return mad_values;
787}
788
789std::vector<double> compute_cv_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
790{
791 auto mean_vals = compute_mean_statistic(data, num_windows, hop_size, window_size);
792 auto std_vals = compute_std_dev_statistic(data, num_windows, hop_size, window_size, sample_variance);
793
794 std::vector<double> cv_values(num_windows);
795 std::transform(std::execution::par_unseq, mean_vals.begin(), mean_vals.end(), std_vals.begin(), cv_values.begin(),
796 [](double mean, double std_dev) {
797 return (std::abs(mean) > 1e-15) ? std_dev / mean : 0.0;
798 });
799
800 return cv_values;
801}
802
803std::vector<double> compute_mode_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
804{
805 std::vector<double> mode_values(num_windows);
806 constexpr double tolerance = 1e-10;
807
808 std::vector<size_t> indices(num_windows);
809 std::iota(indices.begin(), indices.end(), 0);
810
811 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
812 [&](size_t i) {
813 const size_t start_idx = i * hop_size;
814 const size_t end_idx = std::min(start_idx + window_size, data.size());
815 auto window = data.subspan(start_idx, end_idx - start_idx);
816
817 if (window.empty()) {
818 mode_values[i] = 0.0;
819 return;
820 }
821
822 std::map<int64_t, std::pair<double, size_t>> frequency_map;
823
824 for (double value : window) {
825 auto bucket = static_cast<int64_t>(std::round(value / tolerance));
826 if (frequency_map.find(bucket) == frequency_map.end()) {
827 frequency_map[bucket] = { value, 1 };
828 } else {
829 frequency_map[bucket].second++;
830 frequency_map[bucket].first = (frequency_map[bucket].first * static_cast<double>(frequency_map[bucket].second - 1) + value) / static_cast<double>(frequency_map[bucket].second);
831 }
832 }
833
834 auto max_count_it = std::ranges::max_element(frequency_map,
835 [](const auto& a, const auto& b) { return a.second.second < b.second.second; });
836
837 mode_values[i] = max_count_it != frequency_map.end() ? max_count_it->second.first : window[0];
838 });
839
840 return mode_values;
841}
842
843std::vector<double> compute_zscore_statistic(std::span<const double> data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
844{
845 std::vector<double> zscore_values(num_windows);
846
847 std::vector<size_t> indices(num_windows);
848 std::iota(indices.begin(), indices.end(), 0);
849
850 std::for_each(std::execution::par_unseq, indices.begin(), indices.end(),
851 [&](size_t i) {
852 const size_t start_idx = i * hop_size;
853 const size_t end_idx = std::min(start_idx + window_size, data.size());
854 auto window = data.subspan(start_idx, end_idx - start_idx);
855
856 if (window.empty()) {
857 zscore_values[i] = 0.0;
858 return;
859 }
860
861 double sum = 0.0;
862 for (double sample : window) {
863 sum += sample;
864 }
865 double mean = sum / static_cast<double>(window.size());
866
867 double sum_sq_diff = 0.0;
868 for (double sample : window) {
869 double diff = sample - mean;
870 sum_sq_diff += diff * diff;
871 }
872
873 double divisor = sample_variance ? static_cast<double>(window.size() - 1) : static_cast<double>(window.size());
874
875 double variance = sum_sq_diff / divisor;
876 double std_dev = std::sqrt(variance);
877
878 if (std_dev > 0.0) {
879 double sum_zscore = 0.0;
880 for (double val : window) {
881 sum_zscore += (val - mean) / std_dev;
882 }
883 zscore_values[i] = sum_zscore / static_cast<double>(window.size());
884 } else {
885 zscore_values[i] = 0.0;
886 }
887 });
888
889 return zscore_values;
890}
891
892}
std::vector< double > compute_zscore_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
Compute z-score statistic using zero-copy processing.
std::vector< double > compute_entropy_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, size_t num_bins)
Compute entropy statistic using zero-copy processing.
std::vector< double > compute_skewness_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute skewness statistic using zero-copy processing.
std::vector< double > compute_dynamic_range_energy(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute dynamic range energy using zero-copy processing.
std::vector< double > compute_variance_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
Compute variance statistic using zero-copy processing.
std::vector< double > compute_mad_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute MAD (Median Absolute Deviation) statistic using zero-copy processing.
std::vector< size_t > find_onset_positions(std::span< const double > data, uint32_t window_size, uint32_t hop_size, double threshold)
Find onset positions using spectral flux.
std::vector< double > compute_harmonic_energy(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute harmonic energy using low-frequency FFT analysis.
std::vector< double > compute_peak_energy(std::span< const double > data, const uint32_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute peak energy using zero-copy processing.
std::vector< double > compute_power_energy(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute power energy using zero-copy processing.
std::vector< double > compute_std_dev_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
Compute standard deviation statistic using zero-copy processing.
std::vector< double > compute_sum_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute sum statistic using zero-copy processing.
std::vector< double > compute_percentile_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, double percentile)
Compute percentile statistic using zero-copy processing.
std::vector< size_t > find_peak_positions(std::span< const double > data, double threshold, size_t min_distance)
Find actual peak positions in the signal.
std::vector< double > compute_mode_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute mode statistic using zero-copy processing.
std::vector< double > compute_rms_energy(std::span< const double > data, const uint32_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute RMS energy using zero-copy processing.
std::vector< double > compute_min_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute min statistic using zero-copy processing.
std::vector< double > compute_mean_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute mean statistic using zero-copy processing.
std::vector< double > compute_range_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute range statistic using zero-copy processing.
std::vector< double > compute_zero_crossing_energy(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute zero-crossing energy using zero-copy processing.
std::vector< double > compute_cv_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size, bool sample_variance)
Compute CV (Coefficient of Variation) statistic using zero-copy processing.
std::vector< size_t > find_zero_crossing_positions(std::span< const double > data, double threshold)
Find actual zero-crossing positions in the signal.
std::vector< double > compute_median_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute median statistic using zero-copy processing.
std::vector< double > compute_spectral_energy(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute spectral energy using FFT-based analysis.
std::vector< double > compute_kurtosis_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute kurtosis statistic using zero-copy processing.
std::vector< double > compute_max_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute max statistic using zero-copy processing.
std::vector< double > compute_count_statistic(std::span< const double > data, const size_t num_windows, const uint32_t hop_size, const uint32_t window_size)
Compute count statistic using zero-copy processing.
std::vector< size_t > zero_crossings(const std::vector< double > &data, double threshold)
Detect zero crossings in single-channel signal.
Definition Yantra.cpp:266
double std_dev(const std::vector< double > &data)
Calculate standard deviation of single-channel data.
Definition Yantra.cpp:126
double mean(const std::vector< double > &data)
Calculate mean of single-channel data.
Definition Yantra.cpp:39