MayaFlux 0.3.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Analysis.cpp
Go to the documentation of this file.
1
2#include "Analysis.hpp"
3
5
6#include <Eigen/Dense>
7#include <unsupported/Eigen/FFT>
8
10
11namespace {
12
13 constexpr double k_epsilon = 1e-10;
14
15 /**
16 * @brief Pre-computed Hann window coefficients for a given size
17 */
18 [[nodiscard]] Eigen::VectorXd hann_window(uint32_t size)
19 {
20 Eigen::VectorXd w(size);
21 for (uint32_t i = 0; i < size; ++i)
22 w(i) = 0.5 * (1.0 - std::cos(2.0 * std::numbers::pi * i / (size - 1)));
23 return w;
24 }
25
26} // namespace
27
28// ============================================================================
29// Energy
30// ============================================================================
31
32std::vector<double> rms(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
33{
34 std::vector<double> out(n_windows);
35 std::vector<size_t> idx(n_windows);
36 std::iota(idx.begin(), idx.end(), 0);
37
38 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
39 [&](size_t i) {
40 const size_t start = i * hop_size;
41 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
42 double sq = 0.0;
43 for (double s : w)
44 sq += s * s;
45 out[i] = std::sqrt(sq / static_cast<double>(w.size()));
46 });
47
48 return out;
49}
50
51std::vector<double> peak(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
52{
53 std::vector<double> out(n_windows);
54 std::vector<size_t> idx(n_windows);
55 std::iota(idx.begin(), idx.end(), 0);
56
57 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
58 [&](size_t i) {
59 const size_t start = i * hop_size;
60 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
61 double mx = 0.0;
62 for (double s : w)
63 mx = std::max(mx, std::abs(s));
64 out[i] = mx;
65 });
66
67 return out;
68}
69
70std::vector<double> power(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
71{
72 std::vector<double> out(n_windows);
73 std::vector<size_t> idx(n_windows);
74 std::iota(idx.begin(), idx.end(), 0);
75
76 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
77 [&](size_t i) {
78 const size_t start = i * hop_size;
79 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
80 double sq = 0.0;
81 for (double s : w)
82 sq += s * s;
83 out[i] = sq;
84 });
85
86 return out;
87}
88
89std::vector<double> dynamic_range(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
90{
91 std::vector<double> out(n_windows);
92 std::vector<size_t> idx(n_windows);
93 std::iota(idx.begin(), idx.end(), 0);
94
95 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
96 [&](size_t i) {
97 const size_t start = i * hop_size;
98 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
99 double mn = std::numeric_limits<double>::max();
100 double mx = std::numeric_limits<double>::lowest();
101 for (double s : w) {
102 double a = std::abs(s);
103 mn = std::min(mn, a);
104 mx = std::max(mx, a);
105 }
106 out[i] = 20.0 * std::log10(mx / std::max(mn, k_epsilon));
107 });
108
109 return out;
110}
111
112std::vector<double> zero_crossing_rate(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
113{
114 std::vector<double> out(n_windows);
115 std::vector<size_t> idx(n_windows);
116 std::iota(idx.begin(), idx.end(), 0);
117
118 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
119 [&](size_t i) {
120 const size_t start = i * hop_size;
121 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
122 int zc = 0;
123 for (size_t j = 1; j < w.size(); ++j) {
124 if ((w[j] >= 0.0) != (w[j - 1] >= 0.0))
125 ++zc;
126 }
127 out[i] = static_cast<double>(zc) / static_cast<double>(w.size() - 1);
128 });
129
130 return out;
131}
132
133std::vector<double> spectral_energy(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
134{
135 std::vector<double> out(n_windows);
136 std::vector<size_t> idx(n_windows);
137 std::iota(idx.begin(), idx.end(), 0);
138
139 const Eigen::VectorXd hw = hann_window(window_size);
140
141 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
142 [&](size_t i) {
143 const size_t start = i * hop_size;
144 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
145
146 Eigen::VectorXd buf = Eigen::VectorXd::Zero(window_size);
147 for (size_t j = 0; j < w.size(); ++j)
148 buf(static_cast<Eigen::Index>(j)) = w[j] * hw(static_cast<Eigen::Index>(j));
149
150 Eigen::FFT<double> fft;
151 Eigen::VectorXcd spec;
152 fft.fwd(spec, buf);
153
154 double e = 0.0;
155 for (Eigen::Index j = 0; j < spec.size(); ++j)
156 e += std::norm(spec(j));
157
158 out[i] = e / static_cast<double>(window_size);
159 });
160
161 return out;
162}
163
164std::vector<double> low_frequency_energy(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, double low_bin_fraction)
165{
166 std::vector<double> out(n_windows);
167 std::vector<size_t> idx(n_windows);
168 std::iota(idx.begin(), idx.end(), 0);
169
170 const Eigen::VectorXd hw = hann_window(window_size);
171 const int low_bins = std::max(1, static_cast<int>(static_cast<double>((double)window_size / 2) * low_bin_fraction));
172
173 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
174 [&](size_t i) {
175 const size_t start = i * hop_size;
176 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
177
178 Eigen::VectorXd buf = Eigen::VectorXd::Zero(window_size);
179 for (size_t j = 0; j < w.size(); ++j)
180 buf(static_cast<Eigen::Index>(j)) = w[j] * hw(static_cast<Eigen::Index>(j));
181
182 Eigen::FFT<double> fft;
183 Eigen::VectorXcd spec;
184 fft.fwd(spec, buf);
185
186 double e = 0.0;
187 for (int j = 1; j < low_bins; ++j)
188 e += std::norm(spec(j));
189
190 out[i] = e / static_cast<double>(low_bins);
191 });
192
193 return out;
194}
195
196// ============================================================================
197// Statistics
198// ============================================================================
199
200std::vector<double> mean(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
201{
202 std::vector<double> out(n_windows);
203 std::vector<size_t> idx(n_windows);
204 std::iota(idx.begin(), idx.end(), 0);
205
206 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
207 [&](size_t i) {
208 const size_t start = i * hop_size;
209 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
210 double s = 0.0;
211 for (double v : w)
212 s += v;
213 out[i] = s / static_cast<double>(w.size());
214 });
215
216 return out;
217}
218
219std::vector<double> variance(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
220{
221 std::vector<double> out(n_windows);
222 std::vector<size_t> idx(n_windows);
223 std::iota(idx.begin(), idx.end(), 0);
224
225 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
226 [&](size_t i) {
227 const size_t start = i * hop_size;
228 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
229 if (w.size() <= 1) {
230 out[i] = 0.0;
231 return;
232 }
233 double s = 0.0;
234 for (double v : w)
235 s += v;
236 const double m = s / static_cast<double>(w.size());
237 double sq = 0.0;
238 for (double v : w) {
239 double d = v - m;
240 sq += d * d;
241 }
242 const double div = sample_variance
243 ? static_cast<double>(w.size() - 1)
244 : static_cast<double>(w.size());
245 out[i] = sq / div;
246 });
247
248 return out;
249}
250
251std::vector<double> std_dev(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
252{
253 auto v = variance(data, n_windows, hop_size, window_size, sample_variance);
254 Parallel::transform(Parallel::par_unseq, v.begin(), v.end(), v.begin(),
255 [](double x) { return std::sqrt(x); });
256 return v;
257}
258
259std::vector<double> skewness(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
260{
261 std::vector<double> out(n_windows);
262 std::vector<size_t> idx(n_windows);
263 std::iota(idx.begin(), idx.end(), 0);
264
265 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
266 [&](size_t i) {
267 const size_t start = i * hop_size;
268 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
269 if (w.size() < 2) {
270 out[i] = 0.0;
271 return;
272 }
273 double s = 0.0;
274 for (double v : w)
275 s += v;
276 const double m = s / static_cast<double>(w.size());
277 double sq = 0.0, cb = 0.0;
278 for (double v : w) {
279 const double d = v - m;
280 const double d2 = d * d;
281 sq += d2;
282 cb += d2 * d;
283 }
284 const double var = sq / static_cast<double>(w.size());
285 const double sd = std::sqrt(std::max(var, k_epsilon));
286 out[i] = (cb / static_cast<double>(w.size())) / (sd * sd * sd);
287 });
288
289 return out;
290}
291
292std::vector<double> kurtosis(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
293{
294 std::vector<double> out(n_windows);
295 std::vector<size_t> idx(n_windows);
296 std::iota(idx.begin(), idx.end(), 0);
297
298 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
299 [&](size_t i) {
300 const size_t start = i * hop_size;
301 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
302 if (w.size() < 2) {
303 out[i] = 0.0;
304 return;
305 }
306 double s = 0.0;
307 for (double v : w)
308 s += v;
309 const double m = s / static_cast<double>(w.size());
310 double sq = 0.0, fo = 0.0;
311 for (double v : w) {
312 const double d = v - m;
313 const double d2 = d * d;
314 sq += d2;
315 fo += d2 * d2;
316 }
317 const double var = sq / static_cast<double>(w.size());
318 const double var2 = std::max(var * var, k_epsilon);
319 out[i] = (fo / static_cast<double>(w.size())) / var2 - 3.0;
320 });
321
322 return out;
323}
324
325std::vector<double> median(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
326{
327 std::vector<double> out(n_windows);
328 std::vector<size_t> idx(n_windows);
329 std::iota(idx.begin(), idx.end(), 0);
330
331 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
332 [&](size_t i) {
333 const size_t start = i * hop_size;
334 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
335 std::vector<double> buf(w.begin(), w.end());
336 const size_t mid = buf.size() / 2;
337 std::nth_element(buf.begin(), buf.begin() + static_cast<ptrdiff_t>(mid), buf.end());
338 if (buf.size() % 2 != 0) {
339 out[i] = buf[mid];
340 } else {
341 const double upper = buf[mid];
342 std::nth_element(buf.begin(), buf.begin() + static_cast<ptrdiff_t>(mid - 1), buf.begin() + static_cast<ptrdiff_t>(mid));
343 out[i] = (buf[mid - 1] + upper) / 2.0;
344 }
345 });
346
347 return out;
348}
349
350std::vector<double> percentile(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, double percentile_value)
351{
352 std::vector<double> out(n_windows);
353 std::vector<size_t> idx(n_windows);
354 std::iota(idx.begin(), idx.end(), 0);
355
356 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
357 [&](size_t i) {
358 const size_t start = i * hop_size;
359 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
360 if (w.empty()) {
361 out[i] = 0.0;
362 return;
363 }
364 std::vector<double> buf(w.begin(), w.end());
365 std::ranges::sort(buf);
366 const double pos = (percentile_value / 100.0) * static_cast<double>(buf.size() - 1);
367 const auto lo = static_cast<size_t>(std::floor(pos));
368 const auto hi = static_cast<size_t>(std::ceil(pos));
369 out[i] = (lo == hi) ? buf[lo] : buf[lo] * (1.0 - (pos - static_cast<double>(lo))) + buf[hi] * (pos - static_cast<double>(lo));
370 });
371
372 return out;
373}
374
375std::vector<double> entropy(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, size_t num_bins)
376{
377 std::vector<double> out(n_windows);
378 std::vector<size_t> idx(n_windows);
379 std::iota(idx.begin(), idx.end(), 0);
380
381 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
382 [&](size_t i) {
383 const size_t start = i * hop_size;
384 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
385 if (w.empty()) {
386 out[i] = 0.0;
387 return;
388 }
389 size_t bins = num_bins;
390 if (bins == 0) {
391 bins = std::clamp(
392 static_cast<size_t>(std::ceil(std::log2(static_cast<double>(w.size())) + 1.0)),
393 size_t { 1 }, w.size());
394 }
395 const auto [mn, mx] = std::ranges::minmax(w);
396 if (mx <= mn) {
397 out[i] = 0.0;
398 return;
399 }
400 const double bw = (mx - mn) / static_cast<double>(bins);
401 std::vector<size_t> counts(bins, 0);
402 for (double v : w) {
403 auto b = static_cast<size_t>((v - mn) / bw);
404 counts[std::min(b, bins - 1)]++;
405 }
406 double e = 0.0;
407 const auto n = static_cast<double>(w.size());
408 for (size_t c : counts) {
409 if (c > 0) {
410 const double p = static_cast<double>(c) / n;
411 e -= p * std::log2(p);
412 }
413 }
414 out[i] = e;
415 });
416
417 return out;
418}
419
420std::vector<double> min(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
421{
422 std::vector<double> out(n_windows);
423 std::vector<size_t> idx(n_windows);
424 std::iota(idx.begin(), idx.end(), 0);
425
426 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
427 [&](size_t i) {
428 const size_t start = i * hop_size;
429 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
430 out[i] = *std::ranges::min_element(w);
431 });
432
433 return out;
434}
435
436std::vector<double> max(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
437{
438 std::vector<double> out(n_windows);
439 std::vector<size_t> idx(n_windows);
440 std::iota(idx.begin(), idx.end(), 0);
441
442 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
443 [&](size_t i) {
444 const size_t start = i * hop_size;
445 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
446 out[i] = *std::ranges::max_element(w);
447 });
448
449 return out;
450}
451
452std::vector<double> range(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
453{
454 std::vector<double> out(n_windows);
455 std::vector<size_t> idx(n_windows);
456 std::iota(idx.begin(), idx.end(), 0);
457
458 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
459 [&](size_t i) {
460 const size_t start = i * hop_size;
461 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
462 const auto [mn, mx] = std::ranges::minmax(w);
463 out[i] = mx - mn;
464 });
465
466 return out;
467}
468
469std::vector<double> sum(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
470{
471 std::vector<double> out(n_windows);
472 std::vector<size_t> idx(n_windows);
473 std::iota(idx.begin(), idx.end(), 0);
474
475 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
476 [&](size_t i) {
477 const size_t start = i * hop_size;
478 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
479 out[i] = std::accumulate(w.begin(), w.end(), 0.0);
480 });
481
482 return out;
483}
484
485std::vector<double> count(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
486{
487 std::vector<double> out(n_windows);
488 std::vector<size_t> idx(n_windows);
489 std::iota(idx.begin(), idx.end(), 0);
490
491 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
492 [&](size_t i) {
493 const size_t start = i * hop_size;
494 const size_t end = std::min(start + window_size, data.size());
495 out[i] = static_cast<double>(end - start);
496 });
497
498 return out;
499}
500
501std::vector<double> mad(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
502{
503 std::vector<double> out(n_windows);
504 std::vector<size_t> idx(n_windows);
505 std::iota(idx.begin(), idx.end(), 0);
506
507 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
508 [&](size_t i) {
509 const size_t start = i * hop_size;
510 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
511 if (w.empty()) {
512 out[i] = 0.0;
513 return;
514 }
515 std::vector<double> buf(w.begin(), w.end());
516 const size_t mid = buf.size() / 2;
517 std::nth_element(buf.begin(), buf.begin() + static_cast<ptrdiff_t>(mid), buf.end());
518 const double med = (buf.size() % 2 != 0)
519 ? buf[mid]
520 : [&] {
521 const double upper = buf[mid];
522 std::nth_element(buf.begin(), buf.begin() + static_cast<ptrdiff_t>(mid - 1), buf.begin() + static_cast<ptrdiff_t>(mid));
523 return (buf[mid - 1] + upper) / 2.0;
524 }();
525
526 std::vector<double> dev;
527 dev.reserve(w.size());
528 for (double v : w)
529 dev.push_back(std::abs(v - med));
530
531 const size_t dm = dev.size() / 2;
532 std::nth_element(dev.begin(), dev.begin() + static_cast<ptrdiff_t>(dm), dev.end());
533 out[i] = (dev.size() % 2 != 0)
534 ? dev[dm]
535 : [&] {
536 const double upper = dev[dm];
537 std::nth_element(dev.begin(), dev.begin() + static_cast<ptrdiff_t>(dm - 1), dev.begin() + static_cast<ptrdiff_t>(dm));
538 return (dev[dm - 1] + upper) / 2.0;
539 }();
540 });
541
542 return out;
543}
544
545std::vector<double> coefficient_of_variation(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
546{
547 auto m = mean(data, n_windows, hop_size, window_size);
548 auto s = std_dev(data, n_windows, hop_size, window_size, sample_variance);
549
550 std::vector<double> out(n_windows);
551 Parallel::transform(Parallel::par_unseq, m.begin(), m.end(), s.begin(), out.begin(),
552 [](double mv, double sv) {
553 return (std::abs(mv) > 1e-15) ? sv / mv : 0.0;
554 });
555
556 return out;
557}
558
559std::vector<double> mode(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
560{
561 std::vector<double> out(n_windows);
562 std::vector<size_t> idx(n_windows);
563 std::iota(idx.begin(), idx.end(), 0);
564
565 constexpr double tol = 1e-10;
566
567 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
568 [&](size_t i) {
569 const size_t start = i * hop_size;
570 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
571 if (w.empty()) {
572 out[i] = 0.0;
573 return;
574 }
575 std::map<int64_t, std::pair<double, size_t>> freq;
576 for (double v : w) {
577 const auto bucket = static_cast<int64_t>(std::round(v / tol));
578 auto& [sum_v, cnt] = freq[bucket];
579 sum_v = (sum_v * static_cast<double>(cnt) + v) / static_cast<double>(cnt + 1);
580 ++cnt;
581 }
582 const auto it = std::ranges::max_element(freq,
583 [](const auto& a, const auto& b) { return a.second.second < b.second.second; });
584 out[i] = it->second.first;
585 });
586
587 return out;
588}
589
590std::vector<double> mean_zscore(std::span<const double> data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
591{
592 std::vector<double> out(n_windows);
593 std::vector<size_t> idx(n_windows);
594 std::iota(idx.begin(), idx.end(), 0);
595
596 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
597 [&](size_t i) {
598 const size_t start = i * hop_size;
599 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
600 if (w.empty()) {
601 out[i] = 0.0;
602 return;
603 }
604 double s = 0.0;
605 for (double v : w)
606 s += v;
607 const double m = s / static_cast<double>(w.size());
608 double sq = 0.0;
609 for (double v : w) {
610 const double d = v - m;
611 sq += d * d;
612 }
613 const double div = sample_variance
614 ? static_cast<double>(w.size() - 1)
615 : static_cast<double>(w.size());
616 const double sd = std::sqrt(sq / div);
617 if (sd <= 0.0) {
618 out[i] = 0.0;
619 return;
620 }
621 double zs = 0.0;
622 for (double v : w)
623 zs += (v - m) / sd;
624 out[i] = zs / static_cast<double>(w.size());
625 });
626
627 return out;
628}
629
630// ============================================================================
631// Position finders
632// ============================================================================
633
634std::vector<size_t> zero_crossing_positions(std::span<const double> data, double threshold)
635{
636 std::vector<size_t> pos;
637 pos.reserve(data.size() / 4);
638 for (size_t i = 1; i < data.size(); ++i) {
639 if ((data[i] >= threshold) != (data[i - 1] >= threshold))
640 pos.push_back(i);
641 }
642 pos.shrink_to_fit();
643 return pos;
644}
645
646std::vector<size_t> peak_positions(std::span<const double> data, double threshold, size_t min_distance)
647{
648 if (data.size() < 3)
649 return {};
650
651 std::vector<size_t> pos;
652 pos.reserve(data.size() / 100);
653 size_t last = 0;
654
655 for (size_t i = 1; i + 1 < data.size(); ++i) {
656 const double a = std::abs(data[i]);
657 if (a > threshold
658 && a >= std::abs(data[i - 1])
659 && a >= std::abs(data[i + 1])
660 && (pos.empty() || (i - last) >= min_distance)) {
661 pos.push_back(i);
662 last = i;
663 }
664 }
665
666 pos.shrink_to_fit();
667 return pos;
668}
669
670std::vector<size_t> onset_positions(std::span<const double> data, uint32_t window_size, uint32_t hop_size, double threshold)
671{
672 const size_t nw = num_windows(data.size(), window_size, hop_size);
673 if (nw < 2)
674 return {};
675
676 const Eigen::VectorXd hw = hann_window(window_size);
677 std::vector<double> flux(nw - 1, 0.0);
678 Eigen::VectorXcd prev_spec;
679
680 for (size_t i = 0; i < nw; ++i) {
681 const size_t start = i * hop_size;
682 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
683
684 Eigen::VectorXd buf = Eigen::VectorXd::Zero(window_size);
685 for (size_t j = 0; j < w.size(); ++j)
686 buf(static_cast<Eigen::Index>(j)) = w[j] * hw(static_cast<Eigen::Index>(j));
687
688 Eigen::FFT<double> fft;
689 Eigen::VectorXcd spec;
690 fft.fwd(spec, buf);
691
692 if (i > 0) {
693 double f = 0.0;
694 for (Eigen::Index j = 0; j < spec.size(); ++j) {
695 const double diff = std::abs(spec(j)) - std::abs(prev_spec(j));
696 if (diff > 0.0)
697 f += diff;
698 }
699 flux[i - 1] = f;
700 }
701 prev_spec = spec;
702 }
703
704 const double mx = *std::ranges::max_element(flux);
705 if (mx > 0.0) {
706 for (double& f : flux) {
707 f /= mx;
708 }
709 }
710
711 std::vector<size_t> pos;
712 for (size_t i = 1; i + 1 < flux.size(); ++i) {
713 if (flux[i] > threshold && flux[i] > flux[i - 1] && flux[i] >= flux[i + 1])
714 pos.push_back((i + 1) * hop_size);
715 }
716
717 return pos;
718}
719
720} // namespace MayaFlux::Kinesis::Discrete
Discrete sequence analysis primitives for MayaFlux::Kinesis.
Eigen::Index count
size_t a
size_t b
std::vector< double > dynamic_range(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Dynamic range in dB per window.
Definition Analysis.cpp:89
std::vector< double > spectral_energy(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Spectral energy per window using Hann-windowed FFT.
Definition Analysis.cpp:133
std::vector< double > peak(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Peak amplitude per window.
Definition Analysis.cpp:51
std::vector< double > range(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Value range (max - min) per window.
Definition Analysis.cpp:452
std::vector< size_t > zero_crossing_positions(std::span< const double > data, double threshold)
Sample indices of zero crossings in the full span.
Definition Analysis.cpp:634
std::vector< double > entropy(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size, size_t num_bins)
Shannon entropy per window using Sturges-rule histogram.
Definition Analysis.cpp:375
std::vector< double > median(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Median per window via nth_element partial sort.
Definition Analysis.cpp:325
std::vector< double > variance(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
Variance per window.
Definition Analysis.cpp:219
std::vector< size_t > peak_positions(std::span< const double > data, double threshold, size_t min_distance)
Sample indices of local peak maxima in the full span.
Definition Analysis.cpp:646
std::vector< double > min(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Minimum value per window.
Definition Analysis.cpp:420
std::vector< size_t > onset_positions(std::span< const double > data, uint32_t window_size, uint32_t hop_size, double threshold)
Sample indices of onsets detected via spectral flux.
Definition Analysis.cpp:670
std::vector< double > max(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Maximum value per window.
Definition Analysis.cpp:436
std::vector< double > skewness(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Skewness (standardised third central moment) per window.
Definition Analysis.cpp:259
std::vector< double > mad(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Median absolute deviation per window.
Definition Analysis.cpp:501
std::vector< double > mean_zscore(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
Mean z-score per window.
Definition Analysis.cpp:590
std::vector< double > low_frequency_energy(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size, double low_bin_fraction)
Low-frequency spectral energy per window.
Definition Analysis.cpp:164
std::vector< double > sum(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Sum per window.
Definition Analysis.cpp:469
size_t num_windows(size_t data_size, uint32_t window_size, uint32_t hop_size) noexcept
Compute the number of analysis windows for a given data size.
Definition Analysis.hpp:39
std::vector< double > coefficient_of_variation(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size, bool sample_variance)
Coefficient of variation (std_dev / mean) per window.
Definition Analysis.cpp:545
std::vector< double > mode(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Mode per window via tolerance-bucketed frequency count.
Definition Analysis.cpp:559
std::vector< double > kurtosis(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Excess kurtosis (fourth central moment - 3) per window.
Definition Analysis.cpp:292
std::vector< double > rms(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
RMS energy per window.
Definition Analysis.cpp:32
std::vector< double > power(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Power (sum of squares) per window.
Definition Analysis.cpp:70
std::vector< double > percentile(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size, double percentile_value)
Arbitrary percentile per window via linear interpolation.
Definition Analysis.cpp:350
double zero_crossing_rate(const std::vector< double > &data, size_t window_size)
Calculate zero crossing rate for single-channel data.
Definition Yantra.cpp:351
double std_dev(const std::vector< double > &data)
Calculate standard deviation of single-channel data.
Definition Yantra.cpp:128
double mean(const std::vector< double > &data)
Calculate mean of single-channel data.
Definition Yantra.cpp:41