Classes | |
| struct | CheckResult |
| Outcome of one signal assertion. More... | |
| struct | AnalyzeOptions |
| Options for analyze(). More... | |
| struct | ChannelMetrics |
| Per-channel facts about a rendered buffer. More... | |
| struct | BufferMetrics |
| Whole-buffer facts. channels.size() == channel count. More... | |
| struct | FrequencyEstimate |
| Result of estimate_frequency(). More... | |
| struct | ResponsePoint |
| One point on a response curve: frequency and magnitude in dB. More... | |
| struct | ResponseCurve |
| Magnitude/frequency-response curve. More... | |
| struct | ResponseOptions |
| Options for the response analyzer. Lengths must be powers of two. More... | |
| struct | PhasePoint |
| One point on a phase/group-delay curve. More... | |
| struct | PhaseCurve |
| Phase / group-delay curve of a transfer function. More... | |
| struct | GroupDelayOptions |
| Options for the phase/group-delay analyzer. Lengths must be powers of two. More... | |
| struct | Harmonic |
| One harmonic's contribution to a distortion measurement. More... | |
| struct | ThdResult |
| Total harmonic distortion result. More... | |
| struct | ThdOptions |
| Options for the THD analyzer. More... | |
| struct | ToneFit |
| Least-squares fit of a single sinusoid at a known frequency. More... | |
| struct | AliasComponent |
| One expected component of the ideal series, and what was measured where it landed. More... | |
| struct | AliasOptions |
| Options for measure_aliasing. More... | |
| struct | AliasReport |
| Three-way classification of an oscillator's output into harmonic / alias / noise, with the worst in-band alias called out. More... | |
| struct | ModulationSidebandOptions |
| Options for measure_modulation_sidebands. More... | |
| struct | ModulationSidebandReport |
| Where a steady tone's energy went after a periodic modulation was applied to it. More... | |
| struct | LatencyEvidence |
| One processor's latency evidence: the facts, the measurement, the verdict. More... | |
| struct | DelayedNullOptions |
| Knobs for measure_delayed_passthrough. More... | |
| struct | MarkerOffsetOptions |
| Knobs for measure_marker_offset. More... | |
| struct | MeasuredMode |
| One resonant mode recovered from a rendered impulse response. More... | |
| struct | ModeAnalysisOptions |
| Options for analyze_modes. More... | |
| struct | ModeAnalysis |
| Modes recovered from an impulse response, strongest first. More... | |
| struct | MeasuredPartial |
| One tracked partial of a (quasi-)harmonic sound. More... | |
| struct | InharmonicityOptions |
| Options for measure_inharmonicity. More... | |
| struct | InharmonicityResult |
| Inharmonicity of a (quasi-)harmonic tone. More... | |
| struct | CycleObservation |
| One cycle of a decaying quasi-sinusoid. More... | |
| struct | CycleTrackOptions |
| Options for track_cycles. More... | |
| struct | CycleTrack |
| Time-resolved track of one note. More... | |
| struct | Ar2Observation |
| One short-window AR(2) pole fit. More... | |
| struct | Ar2TrackOptions |
| Options for track_ar2. More... | |
| struct | Ar2Track |
| AR(2) pole track — an independent cross-check on track_cycles. More... | |
| struct | ModalExtractionOptions |
| struct | ModalExtractionCandidate |
| struct | ModalExtractionResult |
| struct | PitchEstimate |
| A single fundamental-frequency estimate. More... | |
| struct | PitchOptions |
| Options for estimate_pitch. More... | |
| struct | PitchTrackPoint |
| One point on an f0(t) trajectory: the frame's center time and its estimate. More... | |
| struct | PitchTrackOptions |
| Options for track_pitch. More... | |
| struct | PitchTrack |
| An f0(t) trajectory extracted from a render — the demodulator output the drift/jitter statistics consume. More... | |
Enumerations | |
| enum class | Window { rectangular , hann , hamming , blackman , flat_top , kaiser } |
| Analysis window. More... | |
| enum class | ComponentClass { harmonic , alias } |
| How measure_aliasing classified one expected component of the ideal harmonic series h · f0. More... | |
| enum class | LatencyReportStatus { available , unsupported , query_failed , invalid } |
| Whether a latency report could be read at all, and whether it was sane. More... | |
| enum class | LatencyReportObservation { stable , changed , unobservable } |
| Whether the report held still for the duration of the render. More... | |
| enum class | LatencyObservationMode { processor_flag_and_poll , per_block_poll , none } |
| How the report was watched while the render ran. More... | |
| enum class | LatencyPolicy { none , delayed_passthrough_null , marker_offset } |
| The probe used to turn audio into a delay measurement. More... | |
| enum class | LatencyMeasurementStatus { match , mismatch , not_measurable , not_requested } |
| What the probe concluded. More... | |
| enum class | LatencyContractOutcome { satisfied , violated , inconclusive , not_requested } |
| The gating verdict. More... | |
| enum class | ModalExtractionError { none , empty_input , input_too_short , silent_input , non_finite_input , invalid_sample_rate , invalid_options , no_modes } |
Functions | |
| std::string | metrics_to_json (const BufferMetrics &metrics, std::string_view scenario) |
| Serialize metrics to a JSON string. | |
| std::filesystem::path | write_metrics_artifact (const BufferMetrics &metrics, std::string_view scenario) |
| Write metrics_to_json() to <temp>/pulp-audio-metrics/<sanitized-scenario>.json (overwriting any previous run's artifact for the same scenario) and return the path. | |
| CheckResult | assert_no_nan_inf (const BufferMetrics &metrics) |
| Fails if any channel contains NaN or ±Inf samples. | |
| CheckResult | assert_not_clipped (const BufferMetrics &metrics, double ceiling_dbfs=-0.1) |
| Fails if any channel's peak reaches ceiling_dbfs or any sample counted as clipped during analysis. | |
| CheckResult | assert_silent (const BufferMetrics &metrics, double threshold_dbfs=-90.0) |
| Fails if any channel's RMS is at or above threshold_dbfs. | |
| CheckResult | assert_not_silent (const BufferMetrics &metrics, double min_rms_dbfs=-60.0) |
| Fails if every channel's RMS is below min_rms_dbfs. | |
| CheckResult | assert_peak_between (const BufferMetrics &metrics, double min_dbfs, double max_dbfs) |
| Fails unless every channel's peak lies within [min_dbfs, max_dbfs]. | |
| CheckResult | assert_rms_between (const BufferMetrics &metrics, double min_dbfs, double max_dbfs) |
| Fails unless every channel's RMS lies within [min_dbfs, max_dbfs]. | |
| CheckResult | assert_frequency_near (std::span< const float > samples, double sample_rate, double expected_hz, double tolerance_cents=5.0) |
| Fails unless the zero-crossing frequency estimate is within tolerance_cents of expected_hz. | |
| CheckResult | assert_null_near (const pulp::audio::BufferView< const float > &a, const pulp::audio::BufferView< const float > &b, double tolerance_dbfs=-120.0) |
| Null test: fails if the per-sample residual |a−b| peak is at or above tolerance_dbfs. | |
| CheckResult | assert_null_near (const pulp::audio::Buffer< float > &a, const pulp::audio::Buffer< float > &b, double tolerance_dbfs=-120.0) |
| Null test: fails if the per-sample residual |a−b| peak is at or above tolerance_dbfs. | |
| CheckResult | assert_channels_independent (const pulp::audio::BufferView< const float > &buffer) |
| Fails if any two channels are sample-identical (within 1e-9), which usually means a channel-routing bug duplicated one channel. | |
| CheckResult | assert_channels_independent (const pulp::audio::Buffer< float > &buffer) |
| Fails if any two channels are sample-identical (within 1e-9), which usually means a channel-routing bug duplicated one channel. | |
| std::string | response_curve_to_json (const ResponseCurve &curve, std::string_view scenario) |
| Serialize a magnitude/response curve to a JSON string. | |
| std::string | thd_to_json (const ThdResult &thd, std::string_view scenario) |
| Serialize a THD result to a JSON string (including the harmonic breakdown). | |
| std::string | phase_curve_to_json (const PhaseCurve &curve, std::string_view scenario) |
| Serialize a phase/group-delay curve to a JSON string. | |
| std::filesystem::path | write_response_artifact (const ResponseCurve &curve, std::string_view scenario) |
| Write response_curve_to_json to <temp>/pulp-audio-doctor/<sanitized-scenario>.response.json and return the path (overwriting any previous run's artifact for the same scenario). | |
| std::filesystem::path | write_thd_artifact (const ThdResult &thd, std::string_view scenario) |
| Write thd_to_json to <temp>/pulp-audio-doctor/<sanitized-scenario>.thd.json and return path. | |
| std::filesystem::path | write_phase_artifact (const PhaseCurve &curve, std::string_view scenario) |
| Write phase_curve_to_json to <temp>/pulp-audio-doctor/<sanitized-scenario>.groupdelay.json and return the path. | |
| double | to_dbfs (double linear) |
| Linear amplitude (1.0 == full scale) to dBFS, clamped to kSilenceFloorDb. | |
| double | from_dbfs (double dbfs) |
| dBFS to linear amplitude. | |
| BufferMetrics | analyze (const pulp::audio::BufferView< const float > &buffer, double sample_rate, const AnalyzeOptions &options={}) |
| Analyze a const view. Pure arithmetic; safe for any buffer size. | |
| BufferMetrics | analyze (const pulp::audio::Buffer< float > &buffer, double sample_rate, const AnalyzeOptions &options={}) |
| Convenience overload for an owning Buffer. | |
| FrequencyEstimate | estimate_frequency (std::span< const float > samples, double sample_rate) |
| Estimate the dominant frequency of one channel. | |
| std::string | summarize (const BufferMetrics &metrics, const FrequencyEstimate &frequency={}) |
| Compact, agent-readable signal summary. | |
| std::string | window_name (Window w) |
| Human-readable window name for artifacts/messages ("rectangular", "hann", "hamming", "blackman", "flat_top", "kaiser"). | |
| double | window_coherent_gain (Window w, int n, double kaiser_beta=kDefaultKaiserBeta) |
| Coherent gain (a.k.a. | |
| std::vector< double > | window_coefficients (Window w, int n, double kaiser_beta=kDefaultKaiserBeta) |
| Coefficients of w for an n-sample analysis segment. | |
| ResponseCurve | response_relative_to_input (const pulp::audio::BufferView< const float > &input, const pulp::audio::BufferView< const float > &output, double sample_rate, std::span< const double > checkpoints_hz, const ResponseOptions &options={}) |
| Compute the magnitude response of output relative to input, one channel. | |
| ResponseCurve | magnitude_spectrum_curve (const pulp::audio::BufferView< const float > &signal, double sample_rate, std::span< const double > checkpoints_hz, const ResponseOptions &options={}) |
| The signal's OWN magnitude spectrum, one channel, in dB relative to its own peak bin (loudest frequency = 0 dB, everything else negative). | |
| PhaseCurve | measure_group_delay (const pulp::audio::BufferView< const float > &input, const pulp::audio::BufferView< const float > &output, double sample_rate, std::span< const double > checkpoints_hz, const GroupDelayOptions &options={}) |
| Phase and group delay of output relative to input, one channel. | |
| ThdResult | measure_thd (const pulp::audio::BufferView< const float > &signal, double fundamental_hz, double sample_rate, const ThdOptions &options={}) |
| Measure THD / THD+N of signal (one channel of an already-rendered buffer) at fundamental_hz. | |
| ToneFit | fit_tone (std::span< const double > samples, double cycles_per_sample) |
| Fit one sinusoid at cycles_per_sample (frequency ÷ sample rate; 0.5 is Nyquist) to samples by least squares. | |
| double | tone_residual_db (std::span< const double > samples, double cycles_per_sample) |
| Energy of samples NOT explained by a tone at cycles_per_sample, in dB relative to the fitted tone's own energy: 10·log10(residual / fitted). | |
| double | tone_gain_db (std::span< const double > samples, double cycles_per_sample, double input_amplitude) |
| Gain of a tone at cycles_per_sample relative to input_amplitude, in dB: 20·log10(fitted_amplitude / input_amplitude). | |
| double | fold_frequency (double frequency_hz, double sample_rate) |
| Where a component at frequency_hz LANDS once sampled at sample_rate. | |
| AliasReport | measure_aliasing (const pulp::audio::BufferView< const float > &signal, double fundamental_hz, double sample_rate, const AliasOptions &options={}) |
| Classify signal (one channel of an already-rendered buffer) against the ideal harmonic series of fundamental_hz, and report the worst in-band alias. | |
| ModulationSidebandReport | measure_modulation_sidebands (const pulp::audio::BufferView< const float > &signal, double carrier_hz, double modulation_hz, double sample_rate, const ModulationSidebandOptions &options={}) |
| Classify signal (one channel of an already-rendered buffer) as a tone at carrier_hz modulated at modulation_hz, and report the energy at the modulation's own sidebands against the energy anywhere else in the band. | |
| LatencyEvidence | measure_delayed_passthrough (const pulp::audio::Buffer< float > &input, const pulp::audio::Buffer< float > &output, std::optional< int > reported_samples, const DelayedNullOptions &options={}) |
| Sweep the delay D that best explains output as a copy of input, and compare it against reported_samples. | |
| LatencyEvidence | measure_marker_offset (const pulp::audio::Buffer< float > &input, const pulp::audio::Buffer< float > &output, std::optional< int > reported_samples, const MarkerOffsetOptions &options={}) |
| Measure delay as output_onset - input_marker - intrinsic_response_offset and compare it against reported_samples. | |
| LatencyEvidence & | apply_report_observation (LatencyEvidence &evidence) |
| Fold a report-observation fact into already-measured evidence. | |
| LatencyEvidence & | apply_expected_samples (LatencyEvidence &evidence, int expected, std::int64_t tolerance_samples=0) |
| Additionally require the latency to equal expected, pinning the value rather than only its self-consistency. | |
| const char * | to_string (LatencyReportStatus status) |
| const char * | to_string (LatencyReportObservation observation) |
| const char * | to_string (LatencyObservationMode mode) |
| const char * | to_string (LatencyPolicy policy) |
| const char * | to_string (LatencyMeasurementStatus status) |
| const char * | to_string (LatencyContractOutcome outcome) |
| std::string | latency_evidence_to_json (const LatencyEvidence &evidence) |
| Serialize evidence to a JSON object string. | |
| std::string | latency_evidence_summary (const LatencyEvidence &evidence) |
| One-line human summary, suitable for a test failure message. | |
| ModeAnalysis | analyze_modes (std::span< const float > ir, double sample_rate, const ModeAnalysisOptions &options={}) |
| Recover the modes present in ir, a rendered impulse response (mono). | |
| MeasuredMode | measure_mode (std::span< const float > ir, double sample_rate, double guess_hz, const ModeAnalysisOptions &options={}) |
| Measure one mode whose frequency is already known approximately. | |
| std::string | summarize (const ModeAnalysis &analysis) |
| One-line human-readable summary of a mode analysis, for INFO() in a test. | |
| std::string | summarize (const MeasuredMode &mode) |
| InharmonicityResult | measure_inharmonicity (std::span< const float > signal, double sample_rate, double f0_guess, const InharmonicityOptions &options={}) |
| Track the partials of signal starting from f0_guess and measure the inharmonicity. | |
| std::string | summarize (const InharmonicityResult &result) |
| CycleTrack | track_cycles (std::span< const float > signal, double sample_rate, const CycleTrackOptions &options={}) |
| Track f0(t), sigma(t) and Q(t) of a decaying quasi-sinusoidal note, cycle by cycle. | |
| Ar2Track | track_ar2 (std::span< const float > signal, double sample_rate, const Ar2TrackOptions &options={}) |
| std::string | summarize (const CycleTrack &track) |
| std::string | summarize (const Ar2Track &track) |
| double | windowed_magnitude (std::span< const float > x, std::size_t start, std::size_t length, double freq_hz, double sample_rate) |
| Hann-windowed DFT magnitude of x[start .
| |
| ModalExtractionResult | compile_modal_spec (std::span< const float > impulse, double sample_rate, const ModalExtractionOptions &options={}) |
| Compile a bounded, mono impulse response into the existing ModalSpec data model. | |
| double | cents_between (double hz, double reference_hz) |
| Musical interval between two frequencies in cents: 1200·log2(hz / reference). | |
| PitchEstimate | 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 | estimate_pitch (std::span< const float > samples, double sample_rate, const PitchOptions &options={}) |
| Span overload — analyzes a single already-extracted channel. | |
| PitchTrack | track_pitch (std::span< const float > samples, double sample_rate, const PitchTrackOptions &options={}) |
| Slide a window across samples, estimating the fundamental at each hop. | |
Variables | |
| constexpr std::int64_t | kMetricsArtifactSchemaVersion = 1 |
| Current artifact schema version. | |
| constexpr std::int64_t | kDoctorCurveSchemaVersion = 1 |
| Current Doctor-curve schema version. | |
| constexpr double | kSilenceFloorDb = -200.0 |
| Floor used when converting a zero/negative linear level to dBFS, so summaries and messages stay finite. | |
| constexpr double | kDefaultKaiserBeta = 14.0 |
| Default Kaiser β. | |
| constexpr int | kLatencyEvidenceSchemaVersion = 1 |
| Schema version of the emitted object. Bump on any breaking field change. | |
|
strong |
How measure_aliasing classified one expected component of the ideal harmonic series h · f0.
|
strong |
|
strong |
|
strong |
|
strong |
The probe used to turn audio into a delay measurement.
|
strong |
|
strong |
Whether a latency report could be read at all, and whether it was sane.
|
strong |
|
strong |
Analysis window.
The leakage policy each implies is part of the Analyzer Determinism Contract and is recorded in the curve artifact.
Choosing one is a dynamic-range decision, not a style preference. A window's first side lobe is the floor below which a small component sitting near a loud one cannot be seen: the analyzer reports its OWN leakage skirt instead of the signal. Measuring a −100 dB component next to a 0 dB fundamental through Hann (−31 dB side lobes) measures Hann.
First-side-lobe figures below are MEASURED by test_audio_doctor.cpp's side-lobe test (48 kHz, 16384-point FFT, half-bin-offset tone), not quoted from a table:
| Window | 1st side lobe | Falloff | Usable floor near a loud tone |
|---|---|---|---|
| rectangular | −14 dB | −6 dB/oct | bin-coherent tones only |
| hann | −31 dB | −18 dB/oct | ~−60 dBc |
| hamming | −41 dB | −6 dB/oct | ~−45 dBc |
| blackman | −57 dB | −18 dB/oct | ~−90 dBc |
| flat_top | −93 dB | ~flat | ~−93 dBc (does NOT improve) |
| kaiser | tunable by β | steep | −124 dBc at β = 14 |
Only kaiser reaches a −100 dBc gate. That is a measured result, not a preference: at 16 bins from a 0 dB fundamental a −100 dBc tone reads −82 dB through hann, −91 dB through blackman, and −96 dB through flat_top — every one of those is the window's own leakage, not the signal (the test proves it by re-measuring with the small tone REMOVED and getting the same number).
Note the falloff column outranks the first-side-lobe column with distance: hann's first side lobe is 62 dB worse than flat_top's, but hann falls at −18 dB/octave while flat_top is flat, so 64 bins out hann's floor (−117 dB) is already better than flat_top's (−101 dB). flat_top is for amplitude accuracy on a tone it CAN resolve, never for floor depth.
Coefficient math is NOT duplicated here: every window delegates to pulp::signal::WindowFunction (core/signal/windowing.hpp), the source of truth. See window_coefficients for the periodic/normalization contract.
| Enumerator | |
|---|---|
| rectangular | Rectangular (no taper). Zero scalloping/leakage ONLY when every tone of interest is bin-coherent (an integer number of cycles in the FFT length). Use for THD on a coherent tone; the harmonics then land on exact bins with no spectral spreading. |
| hann | Periodic Hann (0.5 - 0.5*cos(2π n / N)). −31 dB side lobes, wide main lobe; tolerant of non-coherent tones at the cost of bin resolution. Use for broadband response curves where the stimulus is not guaranteed bin-coherent. NOT usable below roughly −60 dBc near a loud tone — reach for blackman/flat_top/kaiser there. |
| hamming | Periodic Hamming. −41 dB first side lobe but a slow −6 dB/octave falloff, so its far skirt stays HIGHER than Hann's despite the better first lobe. Rarely the right pick here; prefer blackman or kaiser when the floor is what matters. |
| blackman | Periodic Blackman. −57 dB first side lobe with an −18 dB/octave falloff, so the skirt collapses quickly with distance from the tone. Good to roughly −90 dBc. NOT sufficient for a −100 dBc gate: at 16 bins from a loud tone its leakage is still ≈ −89 dB and a −100 dB component reads ≈ −91 dB. Reach for kaiser there. |
| flat_top | Periodic flat-top (5-term). A flat-topped main lobe makes a tone's measured amplitude accurate to ~0.01 dB regardless of where it falls between bins — that is what this window is for. Its side lobes are ≈ −93 dB with an essentially FLAT falloff, so unlike hann/blackman its floor barely improves with distance (still ≈ −101 dB 64 bins out, by which point hann is at −117 dB). Its ~10-bin main lobe also swallows anything closer than ~6 bins. Use for amplitude accuracy, never for floor depth — it is not adequate for a −100 dBc gate. |
| kaiser | Periodic Kaiser, side-lobe level dialed by β (kaiser_beta on the options structs, default kDefaultKaiserBeta). The only window here that reaches a −100 dBc gate — see kDefaultKaiserBeta. Trades main-lobe width for floor depth as β rises. |
| BufferMetrics pulp::test::audio::analyze | ( | const pulp::audio::Buffer< float > & | buffer, |
| double | sample_rate, | ||
| const AnalyzeOptions & | options = {} ) |
Convenience overload for an owning Buffer.
| BufferMetrics pulp::test::audio::analyze | ( | const pulp::audio::BufferView< const float > & | buffer, |
| double | sample_rate, | ||
| const AnalyzeOptions & | options = {} ) |
Analyze a const view. Pure arithmetic; safe for any buffer size.
| ModeAnalysis pulp::test::audio::analyze_modes | ( | std::span< const float > | ir, |
| double | sample_rate, | ||
| const ModeAnalysisOptions & | options = {} ) |
Recover the modes present in ir, a rendered impulse response (mono).
Discovery nominates spectral peaks; each is then refined on a cents grid and given a T60 from a log-envelope slope fit over the stated dB window. Returns modes sorted by descending amplitude. A mode that could not be fit still appears — with confidence == 0 and a note — because "there is a peak at 830 Hz that does not decay exponentially" is a finding, not a failure.
| LatencyEvidence & pulp::test::audio::apply_expected_samples | ( | LatencyEvidence & | evidence, |
| int | expected, | ||
| std::int64_t | tolerance_samples = 0 ) |
Additionally require the latency to equal expected, pinning the value rather than only its self-consistency.
Violates a passing result whose reported latency is not the intended one. Call after a measurement.
Use it to regression-pin a known-good latency: it is the only thing that catches a processor whose true delay AND report both moved together.
| LatencyEvidence & pulp::test::audio::apply_report_observation | ( | LatencyEvidence & | evidence | ) |
Fold a report-observation fact into already-measured evidence.
A processor whose latency moved mid-render has no single "the" latency, so a fixed-latency proof cannot stand even if the measured and reported values happen to agree at the end — including when the value moves away and comes back. Call this after a measurement to demote such a result to inconclusive.
| CheckResult pulp::test::audio::assert_channels_independent | ( | const pulp::audio::Buffer< float > & | buffer | ) |
Fails if any two channels are sample-identical (within 1e-9), which usually means a channel-routing bug duplicated one channel.
| CheckResult pulp::test::audio::assert_channels_independent | ( | const pulp::audio::BufferView< const float > & | buffer | ) |
Fails if any two channels are sample-identical (within 1e-9), which usually means a channel-routing bug duplicated one channel.
| CheckResult pulp::test::audio::assert_frequency_near | ( | std::span< const float > | samples, |
| double | sample_rate, | ||
| double | expected_hz, | ||
| double | tolerance_cents = 5.0 ) |
Fails unless the zero-crossing frequency estimate is within tolerance_cents of expected_hz.
Estimator limits are documented on estimate_frequency; this also fails when no estimate is possible.
| CheckResult pulp::test::audio::assert_no_nan_inf | ( | const BufferMetrics & | metrics | ) |
Fails if any channel contains NaN or ±Inf samples.
| CheckResult pulp::test::audio::assert_not_clipped | ( | const BufferMetrics & | metrics, |
| double | ceiling_dbfs = -0.1 ) |
Fails if any channel's peak reaches ceiling_dbfs or any sample counted as clipped during analysis.
| CheckResult pulp::test::audio::assert_not_silent | ( | const BufferMetrics & | metrics, |
| double | min_rms_dbfs = -60.0 ) |
Fails if every channel's RMS is below min_rms_dbfs.
| CheckResult pulp::test::audio::assert_null_near | ( | const pulp::audio::Buffer< float > & | a, |
| const pulp::audio::Buffer< float > & | b, | ||
| double | tolerance_dbfs = -120.0 ) |
Null test: fails if the per-sample residual |a−b| peak is at or above tolerance_dbfs.
Buffers must have identical shape. Convenience overload over owning buffers.
| CheckResult pulp::test::audio::assert_null_near | ( | const pulp::audio::BufferView< const float > & | a, |
| const pulp::audio::BufferView< const float > & | b, | ||
| double | tolerance_dbfs = -120.0 ) |
Null test: fails if the per-sample residual |a−b| peak is at or above tolerance_dbfs.
Buffers must have identical shape.
| CheckResult pulp::test::audio::assert_peak_between | ( | const BufferMetrics & | metrics, |
| double | min_dbfs, | ||
| double | max_dbfs ) |
Fails unless every channel's peak lies within [min_dbfs, max_dbfs].
| CheckResult pulp::test::audio::assert_rms_between | ( | const BufferMetrics & | metrics, |
| double | min_dbfs, | ||
| double | max_dbfs ) |
Fails unless every channel's RMS lies within [min_dbfs, max_dbfs].
| CheckResult pulp::test::audio::assert_silent | ( | const BufferMetrics & | metrics, |
| double | threshold_dbfs = -90.0 ) |
Fails if any channel's RMS is at or above threshold_dbfs.
| double pulp::test::audio::cents_between | ( | double | hz, |
| double | reference_hz ) |
Musical interval between two frequencies in cents: 1200·log2(hz / reference).
Positive when hz is above reference. Returns kSilenceFloorDb-style sentinel behavior is NOT used here — both arguments must be > 0 (throws otherwise), because a cents value is only meaningful between two real pitches.
| ModalExtractionResult pulp::test::audio::compile_modal_spec | ( | std::span< const float > | impulse, |
| double | sample_rate, | ||
| const ModalExtractionOptions & | options = {} ) |
Compile a bounded, mono impulse response into the existing ModalSpec data model.
All storage and FFT work are offline/control-thread operations. Candidates are ranked by amplitude * confidence, ties by frequency, while the emitted spec is always frequency sorted. The candidate ledger retains accepted and rejected evidence for an auditable result.
| FrequencyEstimate pulp::test::audio::estimate_frequency | ( | std::span< const float > | samples, |
| double | sample_rate ) |
Estimate the dominant frequency of one channel.
Estimator (declared per the Analyzer Determinism Contract): counts positive-going zero crossings with linear interpolation between the bracketing samples, then averages the crossing-to-crossing periods. Confidence is 1 − (period standard deviation / mean period), clamped to [0, 1]. Suited to near-periodic, single-pitch signals (test tones, oscillator output). Not a pitch detector for harmonically dense or noisy material — use the offline FFT analyzers for that. Needs at least three crossings; returns {0.0, 0.0} otherwise.
| 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.
Accurate for harmonically-dense signals (saw, pulse, additive stacks) where estimate_frequency's zero-crossing method fails. Fails closed: throws std::invalid_argument for a non-positive sample rate or an analysis window that does not lie within the buffer; returns voiced = false (never a fabricated frequency) for silence or non-tonal content.
| 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.
| ToneFit pulp::test::audio::fit_tone | ( | std::span< const double > | samples, |
| double | cycles_per_sample ) |
Fit one sinusoid at cycles_per_sample (frequency ÷ sample rate; 0.5 is Nyquist) to samples by least squares.
Limits, all load-bearing:
| double pulp::test::audio::fold_frequency | ( | double | frequency_hz, |
| double | sample_rate ) |
Where a component at frequency_hz LANDS once sampled at sample_rate.
Sampling maps the whole frequency axis onto [0, sample_rate/2] by wrapping at sample_rate and mirroring about Nyquist. This folds repeatedly, not once: at 48 kHz, 80 kHz (1.67·fs) wraps to 32 kHz and then mirrors to 16 kHz. Sign is ignored (a real signal's spectrum is symmetric).
Exact-boundary landings (0 and Nyquist) are returned as-is; they are degenerate to MEASURE (see fit_tone), not to compute.
| double pulp::test::audio::from_dbfs | ( | double | dbfs | ) |
dBFS to linear amplitude.
| std::string pulp::test::audio::latency_evidence_summary | ( | const LatencyEvidence & | evidence | ) |
One-line human summary, suitable for a test failure message.
e.g. "reported 512, measured 448 (delta -64) — VIOLATED".
| std::string pulp::test::audio::latency_evidence_to_json | ( | const LatencyEvidence & | evidence | ) |
Serialize evidence to a JSON object string.
| ResponseCurve pulp::test::audio::magnitude_spectrum_curve | ( | const pulp::audio::BufferView< const float > & | signal, |
| double | sample_rate, | ||
| std::span< const double > | checkpoints_hz, | ||
| const ResponseOptions & | options = {} ) |
The signal's OWN magnitude spectrum, one channel, in dB relative to its own peak bin (loudest frequency = 0 dB, everything else negative).
Unlike response_relative_to_input this is NOT a transfer response — there is no reference stimulus — so it answers "what frequencies are present and how loud relative to the strongest" for an arbitrary captured file. DC (bin 0) is removed before analysis and excluded from the peak so a DC offset can't dominate the normalization. Use a leakage-tolerant window (Window::hann) for arbitrary, non-bin-coherent file content. Peak-normalization makes the result FFT-backend-stable (a ratio). analyzer is set to "magnitude_spectrum".
| AliasReport pulp::test::audio::measure_aliasing | ( | const pulp::audio::BufferView< const float > & | signal, |
| double | fundamental_hz, | ||
| double | sample_rate, | ||
| const AliasOptions & | options = {} ) |
Classify signal (one channel of an already-rendered buffer) against the ideal harmonic series of fundamental_hz, and report the worst in-band alias.
The caller is responsible for the signal being a steady tone at fundamental_hz over the analysis segment; use analysis_offset to skip settling. Throws std::invalid_argument on a non-positive rate/frequency, a fundamental at or above Nyquist, a band qualifier outside (0, Nyquist], a series whose harmonics all sit below Nyquist (no alias site to measure — see AliasOptions::num_harmonics), a segment too short to fit the requested series, or a signal with no energy at the fundamental.
| LatencyEvidence pulp::test::audio::measure_delayed_passthrough | ( | const pulp::audio::Buffer< float > & | input, |
| const pulp::audio::Buffer< float > & | output, | ||
| std::optional< int > | reported_samples, | ||
| const DelayedNullOptions & | options = {} ) |
Sweep the delay D that best explains output as a copy of input, and compare it against reported_samples.
The processor must be in a declared identity / bypass / fully-dry mode, so that its output genuinely is its input, delayed. Every sample is checked, so a one-sample misreport fails.
Returns evidence with the policy, measurement, and verdict filled in. report_status / reported_samples / observation fields are the caller's to supply — this function does not touch a processor. When reported_samples is null the delay is still measured and reported, but the outcome is inconclusive (there is nothing to check it against).
| PhaseCurve pulp::test::audio::measure_group_delay | ( | const pulp::audio::BufferView< const float > & | input, |
| const pulp::audio::BufferView< const float > & | output, | ||
| double | sample_rate, | ||
| std::span< const double > | checkpoints_hz, | ||
| const GroupDelayOptions & | options = {} ) |
Phase and group delay of output relative to input, one channel.
── Estimator ────────────────────────────────────────────────────────────── Group delay is the derivative τ(ω) = -dφ/dω, and differentiating a noisy, wrapped phase curve by finite differences is both fragile (it aliases whenever the true phase advances more than π between bins) and noisy (a difference amplifies the bin-to-bin error). This analyzer instead uses the Fourier/ramped-signal identity, which computes the derivative analytically rather than numerically:
τ(ω) = Re{ X_r(ω) · conj(X(ω)) } / |X(ω)|² where X_r = FFT(n·x[n])
(from dX/dω = -j·FFT(n·x[n])). It is evaluated per bin on the reference and the output separately and subtracted — τ_H = τ_output − τ_input — because phase subtracts in a ratio and the derivative is linear. Three properties follow, and they are why this estimator was chosen:
── Phase unwrapping ─────────────────────────────────────────────────────── phase_rad is reported separately and IS unwrapped, by the standard cumulative-offset method: walk the bins low to high and add the multiple of 2π that keeps each successive raw atan2 difference inside (−π, π]. This is exact only while the true phase advances less than π per bin, i.e. while group_delay < fft_length / 2 samples (the phase slope is −τ·2π/N per bin). Beyond that the phase aliases and unwrapping silently tracks the wrong branch — budget fft_length accordingly.
Unwrapping also cannot recover the branch across a magnitude null or a gap in the reference spectrum: there is no phase to read in between, so the accumulated offset carries an unresolved 2πk ambiguity past it. That is a precondition on the WALK, not on the destination bin, so defined — which asks only about a bin's own energy — cannot express it. phase_defined is the gate that does: it is true only for a bin the unwrap reached over an unbroken run of defined bins from DC, and phase_rad is NaN everywhere else, including at bins that are themselves perfectly well defined. So a band-limited or gapped reference yields real group delay at every bin it excites and phase only up to the first gap — which is the honest split. group_delay_samples does not inherit the ambiguity at all; it never consults the unwrapped phase.
The aliasing bound above (group_delay < fft_length / 2) is NOT gated: it is a budgeting duty on the caller, and per bin the analyzer cannot tell an aliased phase from a true one. It is stated, not enforced. The gap ambiguity is gated because it IS exactly testable.
── Stopband contract ────────────────────────────────────────────────────── Where the transfer magnitude is at the noise floor there is no signal whose phase could be measured, and atan2 of numerical noise yields a plausible- looking number that means nothing. Every bin whose magnitude is more than options.magnitude_floor_db below the curve's peak is therefore reported defined = false with NaN group delay, and NaN phase both there and at every bin above it that the unwrap could only reach through it. A bin whose reference (input) spectrum is itself negligible is undefined for the same reason.
That relative gate is backstopped by an absolute one, because the two fail in different places: the relative gate is measured against the curve's own peak, so a processor whose output is entirely silent has a peak that IS the numerical floor, putting every bin at 0 dB relative to it. Such a curve would report a flat, fully-defined, zero-delay response for a processor that emitted nothing. Bins whose absolute transfer magnitude is at the numerical floor are therefore undefined regardless of the relative reading, and a silent output yields a curve with no defined bins at all — which is the honest answer. The analyzer reports no group delay it did not measure.
Computed in double precision (Fft64) throughout: the estimator's numerator is a difference of products that can cancel heavily near a null, and the double path is also the backend-portable one.
| InharmonicityResult pulp::test::audio::measure_inharmonicity | ( | std::span< const float > | signal, |
| double | sample_rate, | ||
| double | f0_guess, | ||
| const InharmonicityOptions & | options = {} ) |
Track the partials of signal starting from f0_guess and measure the inharmonicity.
The fundamental is refined first, then each partial is searched around the stiff-string prediction using the B estimated from the partials found so far, so a stiff string's high partials are still found after they have drifted well sharp of n * f0.
| LatencyEvidence pulp::test::audio::measure_marker_offset | ( | const pulp::audio::Buffer< float > & | input, |
| const pulp::audio::Buffer< float > & | output, | ||
| std::optional< int > | reported_samples, | ||
| const MarkerOffsetOptions & | options = {} ) |
Measure delay as output_onset - input_marker - intrinsic_response_offset and compare it against reported_samples.
Weaker than the delayed-null policy — it checks one onset rather than every sample — but it works for processors that reshape the signal (a convolver, a filter) where a null comparison is meaningless. It refuses to measure when the input marker is not unique or the output has no detectable onset.
| MeasuredMode pulp::test::audio::measure_mode | ( | std::span< const float > | ir, |
| double | sample_rate, | ||
| double | guess_hz, | ||
| const ModeAnalysisOptions & | options = {} ) |
Measure one mode whose frequency is already known approximately.
Skips discovery entirely — use this when a test prescribed the mode and wants to read it back (the calibration path), or when a peak is known to be buried under a louder neighbour that discovery would nominate instead.
| ModulationSidebandReport pulp::test::audio::measure_modulation_sidebands | ( | const pulp::audio::BufferView< const float > & | signal, |
| double | carrier_hz, | ||
| double | modulation_hz, | ||
| double | sample_rate, | ||
| const ModulationSidebandOptions & | options = {} ) |
Classify signal (one channel of an already-rendered buffer) as a tone at carrier_hz modulated at modulation_hz, and report the energy at the modulation's own sidebands against the energy anywhere else in the band.
A modulation_hz of zero claims no sidebands, so every bin but the carrier's is residual: the control a modulated run is compared to.
The caller is responsible for the signal being steady over the analysis segment. Throws std::invalid_argument on a non-positive rate or carrier, a carrier at or above Nyquist, a negative modulation frequency, a band outside (0, Nyquist], or a segment that does not fit the signal.
| ThdResult pulp::test::audio::measure_thd | ( | const pulp::audio::BufferView< const float > & | signal, |
| double | fundamental_hz, | ||
| double | sample_rate, | ||
| const ThdOptions & | options = {} ) |
Measure THD / THD+N of signal (one channel of an already-rendered buffer) at fundamental_hz.
The caller is responsible for ensuring the signal is a steady tone at that frequency over the analysis segment; coherent is computed from fundamental_hz, fft_length, and sample_rate and recorded for the reader to judge the tolerance.
Fails closed rather than fabricating a pass: throws std::invalid_argument for a fundamental at or above Nyquist (it would clamp to the Nyquist bin and read thd = 0), for a buffer that does not cover the analysis segment (see the determinism contract above), and for a signal with no energy at the fundamental bin — silence would otherwise measure thd = 0, letting a dead processor pass a distortion gate.
| std::string pulp::test::audio::metrics_to_json | ( | const BufferMetrics & | metrics, |
| std::string_view | scenario ) |
Serialize metrics to a JSON string.
scenario is recorded verbatim as provenance (which test scenario produced this buffer).
| std::string pulp::test::audio::phase_curve_to_json | ( | const PhaseCurve & | curve, |
| std::string_view | scenario ) |
Serialize a phase/group-delay curve to a JSON string.
A point carries the two gates PhaseCurve defines — "defined" and the stricter "phase_defined" — and omits the field each one gates when it is false: group_delay_samples / group_delay_seconds under defined, phase_rad under phase_defined. Their in-memory value is NaN, which JSON cannot represent, and writing a placeholder would hand a reader a number the analyzer never measured. A stopband point omits all three; a defined point above a gap in the reference spectrum carries the delay pair and omits phase_rad. Read the matching gate before reaching for a field — or just test presence, which says the same thing.
The magnitude key is magnitude_db_rel_peak, not the .response.json artifact's magnitude_db, because the two are different quantities: this one is normalized to the phase curve's own peak transfer magnitude, while .response.json's is the absolute out/in transfer ratio. A processor with 12 dB of passband gain reports +12 in the sibling artifact and 0 here.
| std::string pulp::test::audio::response_curve_to_json | ( | const ResponseCurve & | curve, |
| std::string_view | scenario ) |
Serialize a magnitude/response curve to a JSON string.
scenario is recorded verbatim as provenance.
| ResponseCurve pulp::test::audio::response_relative_to_input | ( | const pulp::audio::BufferView< const float > & | input, |
| const pulp::audio::BufferView< const float > & | output, | ||
| double | sample_rate, | ||
| std::span< const double > | checkpoints_hz, | ||
| const ResponseOptions & | options = {} ) |
Compute the magnitude response of output relative to input, one channel.
input and output are already-rendered buffers (e.g. an impulse and the processor's impulse response, or a decoded reference and processed WAV). The output spectrum is divided by the input spectrum bin-by-bin, giving a true transfer magnitude in dB. Both segments use the same window so its shaping cancels in the ratio. No DC removal (an impulse's flat spectrum is the reference). checkpoints_hz are read from the nearest full-resolution bin.
| std::string pulp::test::audio::summarize | ( | const Ar2Track & | track | ) |
| std::string pulp::test::audio::summarize | ( | const BufferMetrics & | metrics, |
| const FrequencyEstimate & | frequency = {} ) |
Compact, agent-readable signal summary.
Multi-line text; stable field ordering for diffing.
| std::string pulp::test::audio::summarize | ( | const CycleTrack & | track | ) |
| std::string pulp::test::audio::summarize | ( | const InharmonicityResult & | result | ) |
| std::string pulp::test::audio::summarize | ( | const MeasuredMode & | mode | ) |
| std::string pulp::test::audio::summarize | ( | const ModeAnalysis & | analysis | ) |
One-line human-readable summary of a mode analysis, for INFO() in a test.
| std::string pulp::test::audio::thd_to_json | ( | const ThdResult & | thd, |
| std::string_view | scenario ) |
Serialize a THD result to a JSON string (including the harmonic breakdown).
| double pulp::test::audio::to_dbfs | ( | double | linear | ) |
Linear amplitude (1.0 == full scale) to dBFS, clamped to kSilenceFloorDb.
| const char * pulp::test::audio::to_string | ( | LatencyContractOutcome | outcome | ) |
| const char * pulp::test::audio::to_string | ( | LatencyMeasurementStatus | status | ) |
| const char * pulp::test::audio::to_string | ( | LatencyObservationMode | mode | ) |
| const char * pulp::test::audio::to_string | ( | LatencyPolicy | policy | ) |
| const char * pulp::test::audio::to_string | ( | LatencyReportObservation | observation | ) |
| const char * pulp::test::audio::to_string | ( | LatencyReportStatus | status | ) |
| double pulp::test::audio::tone_gain_db | ( | std::span< const double > | samples, |
| double | cycles_per_sample, | ||
| double | input_amplitude ) |
Gain of a tone at cycles_per_sample relative to input_amplitude, in dB: 20·log10(fitted_amplitude / input_amplitude).
The passband-flatness counterpart to tone_residual_db — same fit, different readout. Throws if input_amplitude is not > 0.
| double pulp::test::audio::tone_residual_db | ( | std::span< const double > | samples, |
| double | cycles_per_sample ) |
Energy of samples NOT explained by a tone at cycles_per_sample, in dB relative to the fitted tone's own energy: 10·log10(residual / fitted).
This is the single-tone purity primitive. Because it subtracts the tone rather than windowing around it, it has no leakage skirt to fight — it resolves residuals below −100 dB on a signal whose tone is NOT bin-coherent and whose length is not a power of two, neither of which any FFT analyzer here can offer.
What it measures: EVERYTHING that is not the tone, lumped together — harmonics, aliases, noise, and hum are one number. It answers "how pure is this?", never "what is the impurity?". For that, measure_aliasing fits the whole expected grid and reports the offenders separately.
| Ar2Track pulp::test::audio::track_ar2 | ( | std::span< const float > | signal, |
| double | sample_rate, | ||
| const Ar2TrackOptions & | options = {} ) |
| CycleTrack pulp::test::audio::track_cycles | ( | std::span< const float > | signal, |
| double | sample_rate, | ||
| const CycleTrackOptions & | options = {} ) |
Track f0(t), sigma(t) and Q(t) of a decaying quasi-sinusoidal note, cycle by cycle.
Method: positive-going zero crossings with sub-sample linear interpolation give per-cycle periods (f0) and per-cycle peak envelopes; sigma is the negative slope of a sliding least-squares fit of the log envelope over 2 * sigma_half_window + 1 cycles; Q = pi * f0 / sigma.
This resolves the question a single T60 cannot: whether a resonator's decay rate co-varies with its own instantaneous state within one note. The calibration test proves it does not invent that co-variation out of a frequency glide.
| 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.
Throws std::invalid_argument for a non-positive rate, window, or hop, or a window longer than the signal. Unvoiced frames are kept in points (with voiced = false) so the caller sees where the estimator refused; the voiced_* accessors filter them out for the statistics stage.
| std::vector< double > pulp::test::audio::window_coefficients | ( | Window | w, |
| int | n, | ||
| double | kaiser_beta = kDefaultKaiserBeta ) |
Coefficients of w for an n-sample analysis segment.
Two properties define this function, and both are load-bearing:
Periodic (DFT-even), not symmetric. Coefficient i uses denominator n, not n - 1, so the window wraps exactly the way the FFT assumes the segment does. pulp::signal::WindowFunction::generate is symmetric, so this generates n + 1 points and drops the last — that yields the periodic form exactly, with no coefficient math duplicated here.
Coherent-gain normalized. Coefficients are divided by window_coherent_gain(w), so every window has a DC gain of exactly 1 and a tone's measured magnitude is the same through any of them. Without this a window swap would silently move the 0 dB reference (flat-top attenuates by ~13 dB relative to rectangular), which would make a "−100 dB" claim meaningless. For rectangular the divisor is 1.0 and for hann it is 0.5 — both exact powers of two — so those two paths are bit-for-bit identical to the un-normalized coefficients they replaced, and every dB/ratio the analyzers report is unchanged.
| double pulp::test::audio::window_coherent_gain | ( | Window | w, |
| int | n, | ||
| double | kaiser_beta = kDefaultKaiserBeta ) |
Coherent gain (a.k.a.
DC gain) of w over an n-sample segment: the mean of its coefficients, i.e. the factor by which the window scales a tone's peak-bin magnitude. This is the number window_coefficients divides out — see its normalization contract.
For rectangular (1.0) and hann (0.5) this is the exact analytic constant — an exact power of two, which is what preserves their bit-exact results. Every other window measures the true mean of its length-n coefficients (which is why this takes n), so the DC gain lands on exactly 1 regardless of how core/signal rounds its coefficient literals.
| std::string pulp::test::audio::window_name | ( | Window | w | ) |
Human-readable window name for artifacts/messages ("rectangular", "hann", "hamming", "blackman", "flat_top", "kaiser").
| double pulp::test::audio::windowed_magnitude | ( | std::span< const float > | x, |
| std::size_t | start, | ||
| std::size_t | length, | ||
| double | freq_hz, | ||
| double | sample_rate ) |
Hann-windowed DFT magnitude of x[start .
. start+length) evaluated at exactly freq_hz — the measurement primitive the refinement and envelope stages are built on. Evaluating at an exact frequency rather than a bin centre is what removes the FFT's scalloping error from every number this header reports. length is clamped to the available samples.
| std::filesystem::path pulp::test::audio::write_metrics_artifact | ( | const BufferMetrics & | metrics, |
| std::string_view | scenario ) |
Write metrics_to_json() to <temp>/pulp-audio-metrics/<sanitized-scenario>.json (overwriting any previous run's artifact for the same scenario) and return the path.
Scenario names are sanitized to [A-Za-z0-9._-] for the filename only. Returns an empty path if the temp dir is unavailable or the open/write fails — callers should treat an empty path as "no artifact written" rather than reporting a phantom successful write.
| std::filesystem::path pulp::test::audio::write_phase_artifact | ( | const PhaseCurve & | curve, |
| std::string_view | scenario ) |
Write phase_curve_to_json to <temp>/pulp-audio-doctor/<sanitized-scenario>.groupdelay.json and return the path.
| std::filesystem::path pulp::test::audio::write_response_artifact | ( | const ResponseCurve & | curve, |
| std::string_view | scenario ) |
Write response_curve_to_json to <temp>/pulp-audio-doctor/<sanitized-scenario>.response.json and return the path (overwriting any previous run's artifact for the same scenario).
| std::filesystem::path pulp::test::audio::write_thd_artifact | ( | const ThdResult & | thd, |
| std::string_view | scenario ) |
Write thd_to_json to <temp>/pulp-audio-doctor/<sanitized-scenario>.thd.json and return path.
|
inlineconstexpr |
Default Kaiser β.
Chosen from measurement, not a formula. At 48 kHz / 16384 points against a 0 dB half-bin-offset fundamental (test_audio_doctor.cpp), β = 14 measures a leakage floor of −124 dB or lower at ≥ 12 bins from the tone and reads a −100 dBc component to within 0.4 dB. That clears the harness's "detection floor ≤ −110 dB" acceptance bar with ~14 dB of margin.
Why not lower: β = 12 measures only ≈ −102 dB immediately outside the main lobe — right at a −100 dBc gate, with no margin. Why not core/signal's generic β = 3 fallback: ≈ −36 dB, useless here. Raise β for a deeper floor and a wider main lobe; lower it for sharper resolution of close-in components you do not need −100 dB of range on.
That floor holds in the tone's NEIGHBORHOOD, not at the bottom of the spectrum: removing the mean of a non-integer-cycle tone leaves a DC-removal pedestal that no window touches — measured on the same fixture, bins 1–3 read ≈ −66 / −75 / −92 dB through kaiser β = 14. A component landing within a few bins of DC is pedestal-limited through ANY window; measure it with the projection path instead (measure_aliasing fits the constant jointly and reads a −100 dBc component exactly even under a 0.5 DC offset).
|
inlineconstexpr |
Current Doctor-curve schema version.
Bump when a field changes meaning or is removed; purely additive fields do not require a bump.
|
inlineconstexpr |
Schema version of the emitted object. Bump on any breaking field change.
|
inlineconstexpr |
Current artifact schema version.
Bump when fields change meaning or are removed; purely additive fields do not require a bump.
|
inlineconstexpr |
Floor used when converting a zero/negative linear level to dBFS, so summaries and messages stay finite.
-200 dB is far below any audible or asserted threshold.