19#ifndef SBL_DSP_ANALYSIS_MEASURE_HPP_
20#define SBL_DSP_ANALYSIS_MEASURE_HPP_
28inline constexpr double PI = 3.14159265358979323846;
39 const double w = 2.0 *
PI * hz / fs;
40 const double coeff = 2.0 * std::cos(w);
41 double s1 = 0.0, s2 = 0.0;
42 for (
int i = 0; i < n; ++i) {
43 const double weight = window ==
Window::Hann ? 0.5 - 0.5 * std::cos(2.0 *
PI * i / n) : 1.0;
44 const double s0 = weight * x[i] + coeff * s1 - s2;
48 return s1 * s1 + s2 * s2 - coeff * s1 * s2;
54inline double bin_energy(
const T* x,
int n,
double hz,
double fs) {
61inline double bin_power(
const T* x,
int n,
double hz,
double fs) {
68 for (
int i = 0; i < n; ++i) e += static_cast<double>(x[i]) * x[i];
69 return n > 0 ? e / n : 0.0;
86 std::vector<double> y(x, x + n);
88 for (
double v : y) mean += v;
89 mean /= n > 0 ? n : 1;
90 for (
double& v : y) v -= mean;
92 int lo =
static_cast<int>(expected * (1.0 - tolerance));
93 int hi =
static_cast<int>(expected * (1.0 + tolerance)) + 2;
95 if (hi > n - 2) hi = n - 2;
96 const int span = n - hi - 1;
97 auto corr = [&](
int lag) {
99 for (
int i = 0; i < span; ++i) acc += y[i] * y[i + lag];
102 Period p{expected, 0.0, lo};
103 if (span <= 0 || hi < lo)
return p;
105 double best = corr(lo);
106 for (
int lag = lo + 1; lag <= hi; ++lag) {
107 const double v = corr(lag);
108 if (v > best) { best = v; p.lag = lag; }
110 const double r0 = corr(0);
111 p.periodicity = r0 > 0.0 ? best / r0 : 0.0;
112 const double ym1 = corr(p.lag - 1), y0 = best, yp1 = corr(p.lag + 1);
113 const double den = ym1 - 2.0 * y0 + yp1;
114 p.samples = p.lag + (den != 0.0 ? 0.5 * (ym1 - yp1) / den : 0.0);
135 pr.rms_db = energy > 0.0 ? 10.0 * std::log10(energy) : -120.0;
137 double strongest = 0.0, weighted = 0.0, total = 0.0;
138 for (
int h = 1; h <= harmonics; ++h) {
139 if (h * f0 >= fs * 0.45)
break;
140 const double e =
bin_energy(x, n, h * f0, fs);
142 strongest = strongest > e ? strongest : e;
143 weighted += h * f0 * e;
146 pr.centroid_hz = total > 0.0 ? weighted / total : 0.0;
148 pr.db[h] = (amp[h] > 0.0 && strongest > 0.0) ? 10.0 * std::log10(amp[h] / strongest) : -120.0;
173 for (
int i = 0; i < n; ++i) {
175 const double a = std::fabs(
static_cast<double>(x[i]));
176 m.peak = a > m.peak ? a : m.peak;
178 mean /= n > 0 ? n : 1;
180 for (
int i = 0; i < n; ++i) x[i] -=
static_cast<T
>(mean);
181 m.stick_percent = n > 0 ? stick * 100 / n : 0;
186 m.slips_per_period = n > 0 ? slips * m.measured_period / n : 0.0;
188 const double f0 = fs / m.measured_period;
189 double total = 0.0, fundamental = 0.0;
190 for (
int h = 1; h <= harmonics && h * f0 < fs * 0.45; ++h) {
191 const double e =
bin_energy(x, n, h * f0, fs);
192 if (h == 1) fundamental = e;
195 m.h1_percent = total > 0.0 ? 100.0 * fundamental / total : 0.0;
199 for (
int i = 2; i < n; ++i) {
200 const double d2 =
static_cast<double>(x[i]) - 2.0 * x[i - 1] + x[i - 2];
201 hf += d2 * d2 * 0.0625;
203 m.hf_percent = r0 > 0.0 ? 100.0 * hf / r0 : 0.0;
217inline double magnitude_at(
double hz,
double fs, F&& process,
int settle = 4000,
int n = 20000) {
218 const double w = 2.0 *
PI * hz / fs;
219 double re = 0.0, im = 0.0;
220 for (
int i = 0; i < settle; ++i) process(static_cast<float>(std::sin(w * i)));
221 for (
int i = settle; i < settle + n; ++i) {
222 const double y = process(
static_cast<float>(std::sin(w * i)));
223 re += y * std::sin(w * i);
224 im += y * std::cos(w * i);
226 return std::sqrt(re * re + im * im) * 2.0 / n;
237 if (settle < 0) settle =
static_cast<int>(fs * 0.2);
238 if (n < 0) n =
static_cast<int>(fs * 0.5);
239 const double w = 2.0 *
PI * hz / fs;
240 for (
int i = 0; i < settle; ++i) reflect(static_cast<float>(std::sin(w * i)));
241 double re = 0.0, im = 0.0;
242 for (
int i = settle; i < settle + n; ++i) {
243 const double r = reflect(
static_cast<float>(std::sin(w * i)));
244 re += r * std::cos(w * i);
245 im += r * std::sin(w * i);
247 const double a = 2.0 * im / n, b = 2.0 * re / n;
249 double extra = std::atan2(b, a) -
PI;
250 while (extra >
PI) extra -= 2.0 *
PI;
251 while (extra < -
PI) extra += 2.0 *
PI;
264inline Decay decay_fit(
const T* x,
int n,
double hz,
int window,
double fs,
double floor = 1e-12) {
266 const int windows = window > 0 ? n / window : 0;
267 double first_db = 0.0, sx = 0, sy = 0, sxx = 0, sxy = 0;
268 for (
int w = 0; w < windows; ++w) {
269 const double e =
bin_energy(x + w * window, window, hz, fs);
270 if (e < floor)
break;
271 const double db = 10.0 * std::log10(e);
272 if (w == 0) first_db = db;
273 const double t = (w + 0.5) * window / fs;
274 sx += t; sy += db; sxx += t * t; sxy += t * db;
276 if (d.windows_fit >= 2 && db < first_db - 60.0)
break;
278 const int k = d.windows_fit;
279 d.db_per_s = k >= 2 ? (k * sxy - sx * sy) / (k * sxx - sx * sx) : 0.0;
constexpr int MAX_HARMONICS
double bin_energy(const T *x, int n, double hz, double fs)
double bin_power(const T *x, int n, double hz, double fs)
Measure measure_window(T *x, int n, double nominal_period, double fs, int stick, int slips, int harmonics)
Period autocorr_period(const T *x, int n, double expected, double tolerance)
double projection_power(const T *x, int n, double hz, double fs, Window window)
double mean_power(const T *x, int n)
Decay decay_fit(const T *x, int n, double hz, int window, double fs, double floor=1e-12)
Profile harmonic_profile(const T *x, int n, double f0, double fs, int harmonics)
double magnitude_at(double hz, double fs, F &&process, int settle=4000, int n=20000)
Reflectance reflectance(double hz, double fs, F &&reflect, int settle=-1, int n=-1)
A termination's reflection for a unit sine arriving at hz.
bool helmholtz(const Measure &m, double min_slips=0.9, double max_slips=1.1)
One release per period, give or take a window edge.
int windows_fit
windows above the floor that the line was fitted through
double slips_per_period
releases per period: 1 is Helmholtz motion, 2 an octave regime
double h1_percent
the fundamental's share of the first harmonics harmonics' energy
double measured_period
samples, from the autocorrelation peak near the nominal
double periodicity
normalised autocorrelation at the played period (1 = periodic there)
double hf_percent
second-difference energy share: the harsh high band
double periodicity
autocorrelation at the period over the energy: 1 is periodic there
int lag
the integer lag the peak sat on
double samples
the interpolated period
double db[MAX_HARMONICS]
each harmonic in dB below the strongest; −120 when absent
double phase_delay_samples
beyond a wall's π: positive when a loop runs longer for it