Files
libpasada/pasada-lib/pd_signal.cpp
David Madl 90f8943930 feat: iterate on SsfStepDetector
* use SSF signal instead of accelerometer signal
* use higher BEAT_CORR_THR_{12} for SSF signal
* add absolute SSF_THRESHOLD to ignore small accelero bumps
* compute ssf_threshold according to detected SSF peaks, not the mean (more robust vs. noise)
2026-03-11 20:47:53 +01:00

143 lines
4.4 KiB
C++

//
// Created by david on 04.03.2026.
//
#include "include/pd_signal.h"
#include <stdexcept>
#include <algorithm>
#include <iostream>
namespace pd_signal {
static void throwIfNotAscending(std::vector<double>& xp) {
size_t N = xp.size();
for(int i = 0; i < N - 1; i++)
if((xp[i+1] - xp[i]) <= 0.0)
throw std::invalid_argument("xp is not strictly monotonically ascending");
}
static int binarySearch(std::vector<double>& data, double x) {
auto p = std::equal_range(data.begin(), data.end(), x);
if(p.first == data.end()) return -((int) data.size()) - 1; // out of range on upper end
int insertion_point = (int) std::distance(data.begin(), p.first);
if(p.first != p.second) return insertion_point; // element found
return -(insertion_point) - 1; // no element found directly
}
void linspace(std::vector<double>& data, double start, double stop, int num) {
linspace(data, start, stop, num, true);
}
// `num` evenly spaced numbers over interval [start,stop] with endpoint=true or [start,stop) with endpoint=false
void linspace(std::vector<double>& data, double start, double stop, int num, bool endpoint) {
if(num < 0) throw std::invalid_argument("num must be >= 0");
int end = endpoint ? num : (num-1);
double step = (stop - start) / (double) end;
double d = start;
data.resize(num);
for(int i = 0; i < num; i++) {
data[i] = d;
d += step;
}
}
// Evaluate at points x the function given by the samples fp[xp[n]].
void interp(std::vector<double>& y, std::vector<double>& x, std::vector<double>& xp, std::vector<double>& fp) {
if (xp.size() != fp.size()) throw std::invalid_argument("xp.size() != fp.size()");
size_t N = xp.size();
size_t M = x.size();
if (N < 2) throw std::invalid_argument("N < 2");
throwIfNotAscending(xp);
std::vector<double> slope, intercept;
y.resize(M);
slope.resize(N-1);
intercept.resize(N-1);
for(int i = 0; i < N-1; i++) {
double dxp = xp[i+1] - xp[i];
double dfp = fp[i+1] - fp[i];
slope[i] = dfp / dxp;
intercept[i] = fp[i] - slope[i] * xp[i];
}
for(int i = 0; i < M; i++) {
if(x[i] < xp[0]) {
y[i] = fp[0];
} else if(x[i] > xp[N-1]) {
y[i] = fp[N-1];
} else {
int idx = binarySearch(xp, x[i]);
if (idx <= -1) {
// not found precisely. interpolate
idx = -idx - 1; // convert back to array index (insertion point)
// idx==0 or idx==M cannot happen, handled above.
//assert(idx > 0 && idx < N);
idx = idx - 1; // one below insertion point (left point)
y[i] = slope[idx] * x[i] + intercept[idx];
} else {
// found precisely
y[i] = fp[idx];
}
}
}
}
// resample to BEAT_LEN
void resample(std::vector<double> &out, std::vector<double> &x, int beat_len) {
std::vector<double> t;
std::vector<double> i;
linspace(t, 0, (double) x.size(), beat_len, false);
linspace(i, 0, (int) (x.size()-1), (int) x.size(), false);
interp(out, t, i, x);
}
// normalized cross-correlation of the two signals of same length
double crossCorr(std::vector<double> &x, std::vector<double> &y) {
if (x.size() != y.size()) throw std::invalid_argument("x.size() != y.size()");
double xs = 0.0, ys = 0.0, cs = 0.0;
for (size_t i = 0; i < x.size(); i++) {
xs += x[i] * x[i];
ys += y[i] * y[i];
cs += x[i] * y[i];
}
return cs / sqrt(xs * ys);
}
// clip 'val' to between 'a_min' and 'a_max'.
double clip(double val, double a_min, double a_max) {
return std::min(std::max(val, a_min), a_max);
}
// two-dimensional mean of a collection of signals
template<class T> void mean_tpl(std::vector<double> &out, T& m) {
if (m.empty()) {
out.resize(0);
return;
}
const size_t sz = m[0].size();
out.resize(sz);
out.assign(sz, 0.0);
const size_t N = m.size();
for (size_t i = 0; i < N; i++) {
for (size_t j = 0; j < sz; j++) {
out[j] += m[i][j];
}
}
for (size_t j = 0; j < sz; j++) {
out[j] /= static_cast<double>(N);
}
}
void mean(std::vector<double> &out, std::vector<std::vector<double> >& m) {
mean_tpl(out, m);
}
void mean(std::vector<double> &out, std::deque<std::vector<double> >& m) {
mean_tpl(out, m);
}
}