7#include <unsupported/Eigen/FFT>
13 constexpr double k_epsilon = 1e-10;
18 [[nodiscard]] Eigen::VectorXd hann_window(uint32_t size)
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)));
32std::vector<double>
rms(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
34 std::vector<double> out(n_windows);
35 std::vector<size_t> idx(n_windows);
36 std::iota(idx.begin(), idx.end(), 0);
38 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
40 const size_t start = i * hop_size;
41 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
45 out[i] = std::sqrt(sq / static_cast<double>(w.size()));
51std::vector<double>
peak(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
53 std::vector<double> out(n_windows);
54 std::vector<size_t> idx(n_windows);
55 std::iota(idx.begin(), idx.end(), 0);
57 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
59 const size_t start = i * hop_size;
60 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
63 mx = std::max(mx, std::abs(s));
70std::vector<double>
power(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
72 std::vector<double> out(n_windows);
73 std::vector<size_t> idx(n_windows);
74 std::iota(idx.begin(), idx.end(), 0);
76 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
78 const size_t start = i * hop_size;
79 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
89std::vector<double>
dynamic_range(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
91 std::vector<double> out(n_windows);
92 std::vector<size_t> idx(n_windows);
93 std::iota(idx.begin(), idx.end(), 0);
95 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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();
102 double a = std::abs(s);
103 mn = std::min(mn, a);
104 mx = std::max(mx, a);
106 out[i] = 20.0 * std::log10(mx / std::max(mn, k_epsilon));
112std::vector<double>
zero_crossing_rate(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
114 std::vector<double> out(n_windows);
115 std::vector<size_t> idx(n_windows);
116 std::iota(idx.begin(), idx.end(), 0);
118 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
120 const size_t start = i * hop_size;
121 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
123 for (size_t j = 1; j < w.size(); ++j) {
124 if ((w[j] >= 0.0) != (w[j - 1] >= 0.0))
127 out[i] =
static_cast<double>(zc) /
static_cast<double>(w.size() - 1);
133std::vector<double>
spectral_energy(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
135 std::vector<double> out(n_windows);
136 std::vector<size_t> idx(n_windows);
137 std::iota(idx.begin(), idx.end(), 0);
139 const Eigen::VectorXd hw = hann_window(window_size);
141 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
143 const size_t start = i * hop_size;
144 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
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));
150 Eigen::FFT<double> fft;
151 Eigen::VectorXcd spec;
155 for (Eigen::Index j = 0; j < spec.size(); ++j)
156 e += std::norm(spec(j));
158 out[i] = e / static_cast<double>(window_size);
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)
166 std::vector<double> out(n_windows);
167 std::vector<size_t> idx(n_windows);
168 std::iota(idx.begin(), idx.end(), 0);
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));
173 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
175 const size_t start = i * hop_size;
176 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
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));
182 Eigen::FFT<double> fft;
183 Eigen::VectorXcd spec;
187 for (int j = 1; j < low_bins; ++j)
188 e += std::norm(spec(j));
190 out[i] = e / static_cast<double>(low_bins);
200std::vector<double>
mean(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
202 std::vector<double> out(n_windows);
203 std::vector<size_t> idx(n_windows);
204 std::iota(idx.begin(), idx.end(), 0);
206 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
208 const size_t start = i * hop_size;
209 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
213 out[i] = s / static_cast<double>(w.size());
219std::vector<double>
variance(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size,
bool sample_variance)
221 std::vector<double> out(n_windows);
222 std::vector<size_t> idx(n_windows);
223 std::iota(idx.begin(), idx.end(), 0);
225 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
227 const size_t start = i * hop_size;
228 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
236 const double m = s /
static_cast<double>(w.size());
242 const double div = sample_variance
243 ?
static_cast<double>(w.size() - 1)
244 :
static_cast<double>(w.size());
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)
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); });
259std::vector<double>
skewness(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
261 std::vector<double> out(n_windows);
262 std::vector<size_t> idx(n_windows);
263 std::iota(idx.begin(), idx.end(), 0);
265 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
267 const size_t start = i * hop_size;
268 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
276 const double m = s /
static_cast<double>(w.size());
277 double sq = 0.0, cb = 0.0;
279 const double d = v - m;
280 const double d2 = d * d;
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);
292std::vector<double>
kurtosis(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
294 std::vector<double> out(n_windows);
295 std::vector<size_t> idx(n_windows);
296 std::iota(idx.begin(), idx.end(), 0);
298 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
300 const size_t start = i * hop_size;
301 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
309 const double m = s /
static_cast<double>(w.size());
310 double sq = 0.0, fo = 0.0;
312 const double d = v - m;
313 const double d2 = d * d;
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;
325std::vector<double>
median(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
327 std::vector<double> out(n_windows);
328 std::vector<size_t> idx(n_windows);
329 std::iota(idx.begin(), idx.end(), 0);
331 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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) {
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;
350std::vector<double>
percentile(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size,
double percentile_value)
352 std::vector<double> out(n_windows);
353 std::vector<size_t> idx(n_windows);
354 std::iota(idx.begin(), idx.end(), 0);
356 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
358 const size_t start = i * hop_size;
359 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
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));
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)
377 std::vector<double> out(n_windows);
378 std::vector<size_t> idx(n_windows);
379 std::iota(idx.begin(), idx.end(), 0);
381 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
383 const size_t start = i * hop_size;
384 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
389 size_t bins = num_bins;
392 static_cast<size_t>(std::ceil(std::log2(
static_cast<double>(w.size())) + 1.0)),
393 size_t { 1 }, w.size());
395 const auto [mn, mx] = std::ranges::minmax(w);
400 const double bw = (mx - mn) /
static_cast<double>(bins);
401 std::vector<size_t> counts(bins, 0);
403 auto b =
static_cast<size_t>((v - mn) / bw);
404 counts[std::min(
b, bins - 1)]++;
407 const auto n =
static_cast<double>(w.size());
408 for (
size_t c : counts) {
410 const double p =
static_cast<double>(c) / n;
411 e -= p * std::log2(p);
420std::vector<double>
min(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
422 std::vector<double> out(n_windows);
423 std::vector<size_t> idx(n_windows);
424 std::iota(idx.begin(), idx.end(), 0);
426 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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);
436std::vector<double>
max(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
438 std::vector<double> out(n_windows);
439 std::vector<size_t> idx(n_windows);
440 std::iota(idx.begin(), idx.end(), 0);
442 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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);
452std::vector<double>
range(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
454 std::vector<double> out(n_windows);
455 std::vector<size_t> idx(n_windows);
456 std::iota(idx.begin(), idx.end(), 0);
458 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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);
469std::vector<double>
sum(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
471 std::vector<double> out(n_windows);
472 std::vector<size_t> idx(n_windows);
473 std::iota(idx.begin(), idx.end(), 0);
475 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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);
485std::vector<double>
count(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
487 std::vector<double> out(n_windows);
488 std::vector<size_t> idx(n_windows);
489 std::iota(idx.begin(), idx.end(), 0);
491 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
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);
501std::vector<double>
mad(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
503 std::vector<double> out(n_windows);
504 std::vector<size_t> idx(n_windows);
505 std::iota(idx.begin(), idx.end(), 0);
507 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
509 const size_t start = i * hop_size;
510 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
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)
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;
526 std::vector<double> dev;
527 dev.reserve(w.size());
529 dev.push_back(std::abs(v - med));
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)
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;
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)
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);
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;
559std::vector<double>
mode(std::span<const double> data,
size_t n_windows, uint32_t hop_size, uint32_t window_size)
561 std::vector<double> out(n_windows);
562 std::vector<size_t> idx(n_windows);
563 std::iota(idx.begin(), idx.end(), 0);
565 constexpr double tol = 1e-10;
567 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
569 const size_t start = i * hop_size;
570 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
575 std::map<int64_t, std::pair<double, size_t>> freq;
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);
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;
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)
592 std::vector<double> out(n_windows);
593 std::vector<size_t> idx(n_windows);
594 std::iota(idx.begin(), idx.end(), 0);
596 Parallel::for_each(Parallel::par_unseq, idx.begin(), idx.end(),
598 const size_t start = i * hop_size;
599 auto w = data.subspan(start, std::min<size_t>(window_size, data.size() - start));
607 const double m = s /
static_cast<double>(w.size());
610 const double d = v - m;
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);
624 out[i] = zs /
static_cast<double>(w.size());
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))
646std::vector<size_t>
peak_positions(std::span<const double> data,
double threshold,
size_t min_distance)
651 std::vector<size_t> pos;
652 pos.reserve(data.size() / 100);
655 for (
size_t i = 1; i + 1 < data.size(); ++i) {
656 const double a = std::abs(data[i]);
658 &&
a >= std::abs(data[i - 1])
659 &&
a >= std::abs(data[i + 1])
660 && (pos.empty() || (i - last) >= min_distance)) {
670std::vector<size_t>
onset_positions(std::span<const double> data, uint32_t window_size, uint32_t hop_size,
double threshold)
672 const size_t nw =
num_windows(data.size(), window_size, hop_size);
676 const Eigen::VectorXd hw = hann_window(window_size);
677 std::vector<double> flux(nw - 1, 0.0);
678 Eigen::VectorXcd prev_spec;
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));
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));
688 Eigen::FFT<double> fft;
689 Eigen::VectorXcd spec;
694 for (Eigen::Index j = 0; j < spec.size(); ++j) {
695 const double diff = std::abs(spec(j)) - std::abs(prev_spec(j));
704 const double mx = *std::ranges::max_element(flux);
706 for (
double& f : flux) {
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);
Discrete sequence analysis primitives for MayaFlux::Kinesis.
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.
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.
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.
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.
std::vector< size_t > zero_crossing_positions(std::span< const double > data, double threshold)
Sample indices of zero crossings in the full span.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
std::vector< double > sum(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Sum per window.
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.
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.
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.
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.
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.
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.
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.
double zero_crossing_rate(const std::vector< double > &data, size_t window_size)
Calculate zero crossing rate for single-channel data.
double std_dev(const std::vector< double > &data)
Calculate standard deviation of single-channel data.
double mean(const std::vector< double > &data)
Calculate mean of single-channel data.