Apply clang-format across the whole tree
Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs, Allman functions) over every source file so the tree is formatter-clean. Whitespace only -- no behavior change; full x64 + x86 suites pass. Also set SortIncludes: false in .clang-format. Windows include order is load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h / dinput.h; winsock2.h must precede windows.h), and the default alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build break. Leaving order alone keeps the manual, correct grouping.
This commit is contained in:
@@ -21,8 +21,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
// WAVE_FORMAT_* tags we decode (kept local to avoid an mmreg.h dependency, matching audio_mix.hpp).
|
||||
inline constexpr std::uint32_t kToneFormatPcm = 1;
|
||||
@@ -30,37 +29,35 @@ inline constexpr std::uint32_t kToneFormatFloat = 3;
|
||||
|
||||
// One channel's worth of measured fidelity. Fields are NaN/0 when not applicable
|
||||
// (e.g. pitch metrics need a known expected_hz > 0).
|
||||
struct ToneReport
|
||||
{
|
||||
bool valid = false; // enough samples to analyze
|
||||
unsigned sample_rate = 0; // the rate the samples are interpreted at (the *declared* rate)
|
||||
std::size_t frames = 0; // mono frames analyzed
|
||||
struct ToneReport {
|
||||
bool valid = false; // enough samples to analyze
|
||||
unsigned sample_rate = 0; // the rate the samples are interpreted at (the *declared* rate)
|
||||
std::size_t frames = 0; // mono frames analyzed
|
||||
double duration_sec = 0.0;
|
||||
|
||||
// --- Level ---
|
||||
double rms = 0.0; // 0..1
|
||||
double peak = 0.0; // 0..1
|
||||
double clipped_fraction = 0.0; // fraction of samples at >= 0.999 full-scale
|
||||
double rms = 0.0; // 0..1
|
||||
double peak = 0.0; // 0..1
|
||||
double clipped_fraction = 0.0; // fraction of samples at >= 0.999 full-scale
|
||||
|
||||
// --- Pitch (needs a known input tone frequency) ---
|
||||
double expected_hz = 0.0; // the tone frequency that was played
|
||||
double dominant_hz = 0.0; // the fundamental we recovered
|
||||
double pitch_error_ratio = 0.0; // dominant / expected (1.0 = perfect)
|
||||
double pitch_error_cents = 0.0; // 1200*log2(ratio); +/- ~10 cents starts to be audible
|
||||
double expected_hz = 0.0; // the tone frequency that was played
|
||||
double dominant_hz = 0.0; // the fundamental we recovered
|
||||
double pitch_error_ratio = 0.0; // dominant / expected (1.0 = perfect)
|
||||
double pitch_error_cents = 0.0; // 1200*log2(ratio); +/- ~10 cents starts to be audible
|
||||
|
||||
// --- Spectral purity (tone mode) ---
|
||||
double snr_db = 0.0; // fundamental power vs everything else (DC + harmonics excluded from "signal")
|
||||
double thd_percent = 0.0; // harmonics 2..6 vs fundamental
|
||||
double snr_db = 0.0; // fundamental power vs everything else (DC + harmonics excluded from "signal")
|
||||
double thd_percent = 0.0; // harmonics 2..6 vs fundamental
|
||||
|
||||
// --- Time-domain defects (content-agnostic) ---
|
||||
unsigned glitch_count = 0; // discontinuity events (clicks): big isolated sample jumps
|
||||
unsigned glitch_count = 0; // discontinuity events (clicks): big isolated sample jumps
|
||||
double glitch_rate_per_sec = 0.0;
|
||||
unsigned dropout_count = 0; // gaps: stretches that fall near-silent mid-signal
|
||||
double dropout_ms = 0.0; // total duration of those gaps
|
||||
unsigned dropout_count = 0; // gaps: stretches that fall near-silent mid-signal
|
||||
double dropout_ms = 0.0; // total duration of those gaps
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
namespace detail {
|
||||
|
||||
inline constexpr double kPi = 3.14159265358979323846;
|
||||
|
||||
@@ -68,28 +65,22 @@ inline constexpr double kPi = 3.14159265358979323846;
|
||||
inline void fft(std::vector<std::complex<double>>& a)
|
||||
{
|
||||
const std::size_t n = a.size();
|
||||
for (std::size_t i = 1, j = 0; i < n; ++i)
|
||||
{
|
||||
for (std::size_t i = 1, j = 0; i < n; ++i) {
|
||||
std::size_t bit = n >> 1;
|
||||
for (; (j & bit) != 0; bit >>= 1)
|
||||
{
|
||||
for (; (j & bit) != 0; bit >>= 1) {
|
||||
j ^= bit;
|
||||
}
|
||||
j ^= bit;
|
||||
if (i < j)
|
||||
{
|
||||
if (i < j) {
|
||||
std::swap(a[i], a[j]);
|
||||
}
|
||||
}
|
||||
for (std::size_t len = 2; len <= n; len <<= 1)
|
||||
{
|
||||
for (std::size_t len = 2; len <= n; len <<= 1) {
|
||||
const double ang = -2.0 * kPi / static_cast<double>(len);
|
||||
const std::complex<double> wlen(std::cos(ang), std::sin(ang));
|
||||
for (std::size_t i = 0; i < n; i += len)
|
||||
{
|
||||
for (std::size_t i = 0; i < n; i += len) {
|
||||
std::complex<double> w(1.0, 0.0);
|
||||
for (std::size_t k = 0; k < len / 2; ++k)
|
||||
{
|
||||
for (std::size_t k = 0; k < len / 2; ++k) {
|
||||
const std::complex<double> u = a[i + k];
|
||||
const std::complex<double> v = a[i + k + len / 2] * w;
|
||||
a[i + k] = u + v;
|
||||
@@ -104,8 +95,7 @@ inline void fft(std::vector<std::complex<double>>& a)
|
||||
inline std::size_t floor_pow2(std::size_t n)
|
||||
{
|
||||
std::size_t p = 1;
|
||||
while ((p << 1) != 0 && (p << 1) <= n)
|
||||
{
|
||||
while ((p << 1) != 0 && (p << 1) <= n) {
|
||||
p <<= 1;
|
||||
}
|
||||
return n == 0 ? 0 : p;
|
||||
@@ -120,27 +110,21 @@ inline std::vector<float> decode_channel(const std::uint8_t* pcm, std::size_t by
|
||||
std::uint32_t bits, std::uint32_t channels, std::uint32_t channel = 0)
|
||||
{
|
||||
std::vector<float> out;
|
||||
if (pcm == nullptr || channels == 0 || channel >= channels)
|
||||
{
|
||||
if (pcm == nullptr || channels == 0 || channel >= channels) {
|
||||
return out;
|
||||
}
|
||||
if (format_tag == kToneFormatFloat && bits == 32)
|
||||
{
|
||||
if (format_tag == kToneFormatFloat && bits == 32) {
|
||||
const std::size_t frames = bytes / (channels * 4);
|
||||
out.reserve(frames);
|
||||
const auto* f = reinterpret_cast<const float*>(pcm);
|
||||
for (std::size_t i = 0; i < frames; ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < frames; ++i) {
|
||||
out.push_back(f[i * channels + channel]);
|
||||
}
|
||||
}
|
||||
else if (format_tag == kToneFormatPcm && bits == 16)
|
||||
{
|
||||
} else if (format_tag == kToneFormatPcm && bits == 16) {
|
||||
const std::size_t frames = bytes / (channels * 2);
|
||||
out.reserve(frames);
|
||||
const auto* s = reinterpret_cast<const std::int16_t*>(pcm);
|
||||
for (std::size_t i = 0; i < frames; ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < frames; ++i) {
|
||||
out.push_back(static_cast<float>(s[i * channels + channel]) / 32768.0f);
|
||||
}
|
||||
}
|
||||
@@ -150,15 +134,13 @@ inline std::vector<float> decode_channel(const std::uint8_t* pcm, std::size_t by
|
||||
// Analyze a single channel of normalized float samples. `expected_hz` is the known
|
||||
// input tone frequency (pass 0 to skip the pitch/SNR/THD metrics for non-tone audio;
|
||||
// the click/dropout/level metrics still apply).
|
||||
inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigned sample_rate,
|
||||
double expected_hz)
|
||||
inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigned sample_rate, double expected_hz)
|
||||
{
|
||||
ToneReport r;
|
||||
r.sample_rate = sample_rate;
|
||||
r.frames = frames;
|
||||
r.expected_hz = expected_hz;
|
||||
if (samples == nullptr || frames < 64 || sample_rate == 0)
|
||||
{
|
||||
if (samples == nullptr || frames < 64 || sample_rate == 0) {
|
||||
return r;
|
||||
}
|
||||
r.valid = true;
|
||||
@@ -168,14 +150,12 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
double sumsq = 0.0;
|
||||
double peak = 0.0;
|
||||
std::size_t clipped = 0;
|
||||
for (std::size_t i = 0; i < frames; ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < frames; ++i) {
|
||||
const double x = samples[i];
|
||||
sumsq += x * x;
|
||||
const double a = std::fabs(x);
|
||||
peak = std::max(peak, a);
|
||||
if (a >= 0.999)
|
||||
{
|
||||
if (a >= 0.999) {
|
||||
++clipped;
|
||||
}
|
||||
}
|
||||
@@ -188,11 +168,9 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
// step. Use the median |first difference| as a robust scale (immune to the tone's own
|
||||
// slope and to a few outliers), and flag steps beyond 8x it. Group samples within a
|
||||
// short refractory window into one event so a single click isn't counted many times.
|
||||
if (frames >= 3)
|
||||
{
|
||||
if (frames >= 3) {
|
||||
std::vector<float> diff(frames - 1);
|
||||
for (std::size_t i = 1; i < frames; ++i)
|
||||
{
|
||||
for (std::size_t i = 1; i < frames; ++i) {
|
||||
diff[i - 1] = std::fabs(samples[i] - samples[i - 1]);
|
||||
}
|
||||
std::vector<float> sorted(diff);
|
||||
@@ -202,12 +180,9 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
const std::size_t refractory = std::max<std::size_t>(sample_rate / 1000, 8); // ~1 ms
|
||||
std::size_t last_event_end = 0;
|
||||
bool have_event = false;
|
||||
for (std::size_t i = 0; i < diff.size(); ++i)
|
||||
{
|
||||
if (diff[i] > thresh)
|
||||
{
|
||||
if (!have_event || i > last_event_end)
|
||||
{
|
||||
for (std::size_t i = 0; i < diff.size(); ++i) {
|
||||
if (diff[i] > thresh) {
|
||||
if (!have_event || i > last_event_end) {
|
||||
++r.glitch_count;
|
||||
}
|
||||
have_event = true;
|
||||
@@ -220,43 +195,35 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
// --- Dropout detection: stretches that fall near-silent in an otherwise active signal ---
|
||||
// Slide a ~5 ms window; flag windows whose RMS drops below 8% of the global RMS. Only
|
||||
// meaningful when the signal is actually present (global RMS above a small floor).
|
||||
if (r.rms > 1e-4)
|
||||
{
|
||||
if (r.rms > 1e-4) {
|
||||
const std::size_t win = std::max<std::size_t>(sample_rate * 5 / 1000, 16); // ~5 ms
|
||||
const std::size_t hop = std::max<std::size_t>(win / 2, 1);
|
||||
const double silence_thresh = 0.08 * r.rms;
|
||||
bool in_gap = false;
|
||||
std::size_t gap_first = 0; // first silent window's start sample
|
||||
std::size_t gap_last = 0; // last silent window's end sample
|
||||
std::size_t gap_first = 0; // first silent window's start sample
|
||||
std::size_t gap_last = 0; // last silent window's end sample
|
||||
std::size_t total_silent_samples = 0;
|
||||
auto close_gap = [&]() {
|
||||
if (in_gap)
|
||||
{
|
||||
if (in_gap) {
|
||||
total_silent_samples += (gap_last - gap_first);
|
||||
in_gap = false;
|
||||
}
|
||||
};
|
||||
for (std::size_t start = 0; start + win <= frames; start += hop)
|
||||
{
|
||||
for (std::size_t start = 0; start + win <= frames; start += hop) {
|
||||
double ws = 0.0;
|
||||
for (std::size_t i = 0; i < win; ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < win; ++i) {
|
||||
const double x = samples[start + i];
|
||||
ws += x * x;
|
||||
}
|
||||
const double wr = std::sqrt(ws / static_cast<double>(win));
|
||||
if (wr < silence_thresh)
|
||||
{
|
||||
if (!in_gap)
|
||||
{
|
||||
if (wr < silence_thresh) {
|
||||
if (!in_gap) {
|
||||
++r.dropout_count;
|
||||
gap_first = start;
|
||||
in_gap = true;
|
||||
}
|
||||
gap_last = start + win;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
close_gap();
|
||||
}
|
||||
}
|
||||
@@ -267,23 +234,19 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
}
|
||||
|
||||
// --- Spectral analysis (pitch / SNR / THD), Hann-windowed FFT ---
|
||||
if (expected_hz > 0.0)
|
||||
{
|
||||
if (expected_hz > 0.0) {
|
||||
std::size_t n = detail::floor_pow2(frames);
|
||||
n = std::min<std::size_t>(n, std::size_t(1) << 18); // cap cost (~5 s @ 48k)
|
||||
if (n >= 1024)
|
||||
{
|
||||
if (n >= 1024) {
|
||||
std::vector<std::complex<double>> buf(n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < n; ++i) {
|
||||
const double w = 0.5 - 0.5 * std::cos(2.0 * detail::kPi * i / (n - 1)); // Hann
|
||||
buf[i] = std::complex<double>(samples[i] * w, 0.0);
|
||||
}
|
||||
detail::fft(buf);
|
||||
const std::size_t half = n / 2;
|
||||
std::vector<double> mag(half);
|
||||
for (std::size_t i = 0; i < half; ++i)
|
||||
{
|
||||
for (std::size_t i = 0; i < half; ++i) {
|
||||
mag[i] = std::abs(buf[i]);
|
||||
}
|
||||
const double bin_hz = static_cast<double>(sample_rate) / static_cast<double>(n);
|
||||
@@ -291,31 +254,26 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
// Peak bin, ignoring DC/very low bins (skip < 20 Hz).
|
||||
std::size_t lo = std::max<std::size_t>(static_cast<std::size_t>(20.0 / bin_hz), 1);
|
||||
std::size_t peak_bin = lo;
|
||||
for (std::size_t i = lo; i < half; ++i)
|
||||
{
|
||||
if (mag[i] > mag[peak_bin])
|
||||
{
|
||||
for (std::size_t i = lo; i < half; ++i) {
|
||||
if (mag[i] > mag[peak_bin]) {
|
||||
peak_bin = i;
|
||||
}
|
||||
}
|
||||
// Quadratic (parabolic) interpolation on log-magnitude for a sub-bin estimate
|
||||
// (accurate for a Hann-windowed peak).
|
||||
double delta = 0.0;
|
||||
if (peak_bin > 0 && peak_bin + 1 < half)
|
||||
{
|
||||
if (peak_bin > 0 && peak_bin + 1 < half) {
|
||||
const double a = std::log(mag[peak_bin - 1] + 1e-30);
|
||||
const double b = std::log(mag[peak_bin] + 1e-30);
|
||||
const double c = std::log(mag[peak_bin + 1] + 1e-30);
|
||||
const double denom = (a - 2.0 * b + c);
|
||||
if (std::fabs(denom) > 1e-30)
|
||||
{
|
||||
if (std::fabs(denom) > 1e-30) {
|
||||
delta = 0.5 * (a - c) / denom;
|
||||
delta = std::max(-0.5, std::min(0.5, delta));
|
||||
}
|
||||
}
|
||||
r.dominant_hz = (static_cast<double>(peak_bin) + delta) * bin_hz;
|
||||
if (r.dominant_hz > 0.0)
|
||||
{
|
||||
if (r.dominant_hz > 0.0) {
|
||||
r.pitch_error_ratio = r.dominant_hz / expected_hz;
|
||||
r.pitch_error_cents = 1200.0 * std::log2(r.pitch_error_ratio);
|
||||
}
|
||||
@@ -328,10 +286,8 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
auto lobe_power = [&](double hz, long half_w) {
|
||||
const long center = static_cast<long>(std::lround(hz / bin_hz));
|
||||
double p = 0.0;
|
||||
for (long k = center - half_w; k <= center + half_w; ++k)
|
||||
{
|
||||
if (k >= 0 && static_cast<std::size_t>(k) < half)
|
||||
{
|
||||
for (long k = center - half_w; k <= center + half_w; ++k) {
|
||||
if (k >= 0 && static_cast<std::size_t>(k) < half) {
|
||||
p += mag[k] * mag[k];
|
||||
}
|
||||
}
|
||||
@@ -339,8 +295,7 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
};
|
||||
|
||||
double total_power = 0.0;
|
||||
for (std::size_t i = lo; i < half; ++i)
|
||||
{
|
||||
for (std::size_t i = lo; i < half; ++i) {
|
||||
total_power += mag[i] * mag[i];
|
||||
}
|
||||
const double fund_power = lobe_power(r.dominant_hz, 8);
|
||||
@@ -348,11 +303,9 @@ inline ToneReport analyze_tone(const float* samples, std::size_t frames, unsigne
|
||||
r.snr_db = 10.0 * std::log10(std::max(fund_power, 1e-30) / residual);
|
||||
|
||||
double harm_power = 0.0;
|
||||
for (int h = 2; h <= 6; ++h)
|
||||
{
|
||||
for (int h = 2; h <= 6; ++h) {
|
||||
const double hz = r.dominant_hz * h;
|
||||
if (hz < (sample_rate / 2.0))
|
||||
{
|
||||
if (hz < (sample_rate / 2.0)) {
|
||||
harm_power += lobe_power(hz, 3);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user