Accurate fundamental-frequency estimation for harmonically-dense, bandlimited oscillator output, plus an f0(t) trajectory extractor over a render. More...
#include "audio_metrics.hpp"#include "audio_spectrum.hpp"#include <pulp/audio/buffer.hpp>#include <span>#include <vector>Go to the source code of this file.
Classes | |
| struct | pulp::test::audio::PitchEstimate |
| A single fundamental-frequency estimate. More... | |
| struct | pulp::test::audio::PitchOptions |
| Options for estimate_pitch. More... | |
| struct | pulp::test::audio::PitchTrackPoint |
| One point on an f0(t) trajectory: the frame's center time and its estimate. More... | |
| struct | pulp::test::audio::PitchTrackOptions |
| Options for track_pitch. More... | |
| struct | pulp::test::audio::PitchTrack |
| An f0(t) trajectory extracted from a render — the demodulator output the drift/jitter statistics consume. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::test |
| namespace | pulp::test::audio |
Functions | |
| double | pulp::test::audio::cents_between (double hz, double reference_hz) |
| Musical interval between two frequencies in cents: 1200·log2(hz / reference). | |
| PitchEstimate | pulp::test::audio::estimate_pitch (const pulp::audio::BufferView< const float > &signal, double sample_rate, const PitchOptions &options={}) |
| Estimate the fundamental of one channel of an already-rendered buffer. | |
| PitchEstimate | pulp::test::audio::estimate_pitch (std::span< const float > samples, double sample_rate, const PitchOptions &options={}) |
| Span overload — analyzes a single already-extracted channel. | |
| PitchTrack | pulp::test::audio::track_pitch (std::span< const float > samples, double sample_rate, const PitchTrackOptions &options={}) |
| Slide a window across samples, estimating the fundamental at each hop. | |
Accurate fundamental-frequency estimation for harmonically-dense, bandlimited oscillator output, plus an f0(t) trajectory extractor over a render.
Why this exists: audio_metrics.hpp's estimate_frequency is a positive-going zero-crossing detector, and its own contract disclaims harmonically-dense material. A saw, pulse, or any real oscillator IS dense — intra-period wiggle and Gibbs ripple add spurious crossings, so zero-crossing returns a harmonic multiple or noise. The oscillator suite cannot gate VCO drift/jitter or a DCO pitch-quantization error without a fundamental estimate accurate to a fraction of a cent; that is what estimate_pitch provides, and track_pitch turns it into the f0(t) demodulator the drift/jitter statistics (Allan deviation, Theil-Sen slope — owned by the Quality Lab, not here) consume.
Method (two shipped primitives, no new FFT):
Fail-closed discipline (mirrors the rest of the harness): silence or content with no clear fundamental is REFUSED (voiced = false), never handed a confident wrong number. Confidence is the fraction of segment energy the fundamental and its harmonics explain — high for a tone or a saw, low for noise, and gated below PitchOptions::min_confidence. The estimator also recovers the true fundamental when a HARMONIC is the loudest partial (a rolled-off pulse whose 2nd, 3rd, … or higher partial dominates, or a faint fundamental beneath a loud harmonic): it ranks the coarse peak against its subharmonics (down to coarse/8) and adopts the lowest that both explains the segment and carries real energy at its own fundamental — the tooth measured against a leakage-aware floor, so a short analysis window's spectral leakage is not mistaken for a subharmonic partial.
Two honest limitations: • A genuine MISSING-FUNDAMENTAL signal — energy only at 2·f0 and 3·f0 with nothing at f0 — has no tone at f0 to lock onto, so the estimate stays on the loudest present partial (2·f0), an octave above the perceived pitch. This is reported as the frequency actually present, not a fabricated f0. • The subharmonic descent stops at coarse/8, so a signal whose loudest partial is the 9th-or-higher harmonic while the fundamental is faint (rare — most oscillators roll off monotonically) can report a lower harmonic rather than the true f0. Both cases return a frequency genuinely present in the signal, never a fabricated one.
Determinism: pure arithmetic over the supplied buffer (the FFT peak plus a deterministic golden-section search). Identical input yields identical output on a given platform. Test/tool tier only — never linked into a runtime build.