#pragma once // Out-of-band spectral energy metric: the same period-grid, Hann-windowed direct-evaluation // approach as test_preserve_low_frequency.cpp's reportSpectrum. Chosen over zero-crossing // counting because splice debris adds spurious crossings that make that estimator // anti-correlated with severity (a render can read a badly wrong PERIOD while this metric // shows it is mostly clean, or vice versa). Grid/segment sizes are smaller than the hand-run // harness's — this one runs inside the gated suite. #include #include #include namespace reasampler::test_support { // Percentage (0..100) of the segment [from, from+len)'s spectral energy that falls outside // +/- 6% of `wantPeriod` (frames). 0 = a clean single tone at that period; higher values mean // harmonics, splice-cadence sidebands, or crossfade cancellation debris are present. inline double energyOutsideFundamentalPercent(const std::vector& v, std::size_t from, std::size_t len, double wantPeriod) { constexpr double kPi = 3.14159265358979323846; constexpr int kGrid = 400; const double pLo = 30.0, pHi = 8000.0; std::vector mag(static_cast(kGrid)); std::vector per(static_cast(kGrid)); for (int g = 0; g < kGrid; ++g) { // Geometric grid: constant relative resolution across the swept period range. const double p = pLo * std::pow(pHi / pLo, static_cast(g) / (kGrid - 1)); per[static_cast(g)] = p; double re = 0.0, im = 0.0; const double w = 2.0 * kPi / p; for (std::size_t k = 0; k < len && from + k < v.size(); ++k) { const double hann = 0.5 * (1.0 - std::cos(2.0 * kPi * static_cast(k) / static_cast(len))); const double x = v[from + k] * hann; re += x * std::cos(w * static_cast(k)); im += x * std::sin(w * static_cast(k)); } mag[static_cast(g)] = std::sqrt(re * re + im * im); } double eTotal = 0.0, eFund = 0.0; for (int g = 0; g < kGrid; ++g) { const std::size_t i = static_cast(g); const double e = mag[i] * mag[i]; eTotal += e; if (std::fabs(per[i] - wantPeriod) / wantPeriod < 0.06) eFund += e; } return eTotal > 0.0 ? 100.0 * (1.0 - eFund / eTotal) : 0.0; } } // namespace reasampler::test_support