From c4cbc16eca062d6064e01c9f68d927414946a835 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 23:49:45 +0000 Subject: [PATCH 1/3] Adopt TapHouse v4 (normalize style pin, distribute tidy.sh) Bump the drift-check ref v3 -> v4 and add scripts/tidy.sh (the local clang-tidy gate mirror, now distributed by TapHouse). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MRTDgYKY38WH8Bqz17W9k1 --- .github/workflows/style.yml | 4 +- scripts/tidy.sh | 77 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100755 scripts/tidy.sh diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index fe121cf..a18d4ef 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -7,9 +7,9 @@ on: [push, pull_request] jobs: drift: - uses: tap/taphouse/.github/workflows/drift-check.yml@v3 + uses: tap/taphouse/.github/workflows/drift-check.yml@v4 with: - ref: v3 + ref: v4 clang-tidy: runs-on: ubuntu-latest diff --git a/scripts/tidy.sh b/scripts/tidy.sh new file mode 100755 index 0000000..45fc009 --- /dev/null +++ b/scripts/tidy.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Local mirror of the CI clang-tidy gate (.github/workflows/style.yml): the +# TapHouse .clang-tidy naming + mandatory-braces checks over this project's +# own translation units. Run it before pushing. +# +# WHY THIS EXISTS (and is not a pre-commit hook): the pre-commit hook only +# runs clang-FORMAT, which is cheap and context-free. clang-TIDY needs a +# compile database (a configured CMake build) and compiles every TU to reason +# about types — too slow and stateful for a per-commit hook, so CI keeps it as +# its own gate. This script is the fast local equivalent of that gate. +# +# Usage: +# scripts/tidy.sh # sweep every project TU (full CI mirror) +# scripts/tidy.sh tests/test_foo.cpp … # only the given TU(s) — fast, for a change +# +# Env: +# CLANG_TIDY=clang-tidy-18 # binary to use (default: clang-tidy-18, then clang-tidy) +# TIDY_BUILD=build-tidy # compile-database build dir (kept separate from ./build) +# TIDY_RECONFIGURE=1 # force a cmake re-configure of the compile database +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +# Prefer clang-tidy-18 — the version the CI gate installs and the .clang-tidy +# checks are validated against; a different major version may disagree. +tidy="${CLANG_TIDY:-}" +if [ -z "$tidy" ]; then + if command -v clang-tidy-18 >/dev/null 2>&1; then + tidy=clang-tidy-18 + elif command -v clang-tidy >/dev/null 2>&1; then + tidy=clang-tidy + echo "warning: clang-tidy-18 not found; using '$tidy'. CI pins v18 — results may differ." >&2 + else + echo "error: no clang-tidy found. Install clang-tidy-18 (apt) or set CLANG_TIDY=..." >&2 + exit 127 + fi +fi + +build="${TIDY_BUILD:-build-tidy}" + +# A compile database is what clang-tidy needs; reuse it across runs unless it is +# missing or a reconfigure is forced. This does not touch your ./build dir. +if [ "${TIDY_RECONFIGURE:-0}" = "1" ] || [ ! -f "$build/compile_commands.json" ]; then + echo "== configuring compile database in $build/ (one-time; reuses cached deps) ==" + cmake -B "$build" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON >/dev/null +fi + +# File list: the given TUs, or — matching CI exactly — every project TU in the +# database with third_party/ and fetched deps (_deps) excluded. +if [ "$#" -gt 0 ]; then + files=("$@") +else + mapfile -t files < <(python3 -c " +import json +for e in json.load(open('$build/compile_commands.json')): + f = e['file'] + if 'third_party' not in f and '_deps' not in f: + print(f)") +fi + +echo "== clang-tidy ($tidy) over ${#files[@]} file(s) ==" +fail=0 +for f in "${files[@]}"; do + out="$("$tidy" -p "$build" "$f" 2>/dev/null || true)" + if printf '%s\n' "$out" | grep -qE "warning:|error:"; then + printf '%s\n' "$out" + fail=1 + fi +done + +if [ "$fail" -eq 0 ]; then + echo "clang-tidy clean." +else + echo "clang-tidy found violations (above); fix before pushing." >&2 + exit 1 +fi From a368aa5999782280e394e6f6227f8a1390fbceb9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 14:25:49 +0000 Subject: [PATCH 2/3] Rename the C++ namespace srt -> tap::samplerate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns SampleRateTap with the tap:: family convention (tap::dsp, tap::ambi, tap::mu, ...). This changes only the C++ namespace: the srt/ include directory, the SampleRateTap CMake target, the SRT_* build options, the srt_* C ABI, and the srt_test test-support namespace are all external contracts and stay as-is (mirroring AmbiTap, which kept ambitap/ + AMBITAP_ + AmbiTap::ambitap under tap::ambi). No back-compat alias — hard rename, no external consumer of the C++ namespace. All 73 tests pass; the C ABI shared library still builds. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MRTDgYKY38WH8Bqz17W9k1 --- README.md | 10 ++--- bench/bench_asrc.cpp | 46 ++++++++++---------- bench/compare/bench_compare.cpp | 18 ++++---- bench/icount/cmp_main.cpp | 4 +- bench/icount/icount_main.cpp | 10 ++--- book/src/appendix/cpp-decisions.md | 4 +- book/src/part1/sample-traits.md | 2 +- book/src/part2/tests.md | 2 +- book/src/part5/scaling.md | 4 +- docs/PERFORMANCE.md | 2 +- examples/alsa_bridge.cpp | 12 +++--- examples/drifting_clocks.cpp | 14 +++--- examples/pico2_cyccnt/main.cpp | 18 ++++---- examples/pico2_dualcore/main.cpp | 22 +++++----- include/srt/asrc.h | 6 +-- include/srt/detail/kaiser.h | 4 +- include/srt/pi_servo.h | 4 +- include/srt/polyphase_filter.h | 4 +- include/srt/sample_traits.h | 4 +- include/srt/spsc_ring.h | 4 +- scripts/book_figures_trace.cpp | 6 +-- tests/support/multitone_analysis.h | 2 +- tests/support/two_clock_sim.h | 4 +- tests/test_asrc_lock.cpp | 22 +++++----- tests/test_asrc_program.cpp | 20 ++++----- tests/test_asrc_quality.cpp | 20 ++++----- tests/test_asrc_quality_16k.cpp | 10 ++--- tests/test_fade.cpp | 4 +- tests/test_fixed_point.cpp | 32 +++++++------- tests/test_hardening.cpp | 68 +++++++++++++++--------------- tests/test_kaiser.cpp | 18 ++++---- tests/test_latency.cpp | 8 ++-- tests/test_multichannel.cpp | 8 ++-- tests/test_polyphase.cpp | 20 ++++----- tests/test_servo.cpp | 14 +++--- tests/test_spsc_ring.cpp | 10 ++--- tests/test_spsc_ring_threads.cpp | 2 +- tools/capi/srt_capi.cpp | 20 ++++----- 38 files changed, 241 insertions(+), 241 deletions(-) diff --git a/README.md b/README.md index c06f658..1b63831 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ target_link_libraries(app PRIVATE SampleRateTap::SampleRateTap) ```cpp #include -srt::config cfg; +tap::samplerate::config cfg; cfg.sample_rate_hz = 48000.0; cfg.channels = 2; -srt::async_sample_rate_converter asrc(cfg); // allocates + designs filter; may throw +tap::samplerate::async_sample_rate_converter asrc(cfg); // allocates + designs filter; may throw // Input-device thread (input clock): asrc.push(input_interleaved, frames); // noexcept, lock-free @@ -46,7 +46,7 @@ asrc.push(input_interleaved, frames); // noexcept, lock-free asrc.pull(output_interleaved, frames); // noexcept, lock-free; silence // until filled/locked -srt::converter_status st = asrc.status(); // any thread: state, ppm, fill, +tap::samplerate::converter_status st = asrc.status(); // any thread: state, ppm, fill, // underruns/overruns/resyncs ``` @@ -233,7 +233,7 @@ reference-microphone processing — but `filter_spec` band edges and `servo_config` bandwidths are absolute Hz designed for ~48 kHz, and running another rate with unscaled defaults silently costs quality (measured: ~32 dB at 16 kHz). Start any non-48 kHz deployment from -`srt::config::for_sample_rate(rate_hz)`, which rescales both (plus the servo +`tap::samplerate::config::for_sample_rate(rate_hz)`, which rescales both (plus the servo hold times); `filter_spec::scaled_to` / `servo_config::scaled_to` exist for custom presets. Measured through that factory (`tests/test_asrc_quality_16k.cpp`), 16 kHz matches the 48 kHz @@ -353,7 +353,7 @@ Indicative numbers from a shared machine (Intel(R) Xeon(R) Processor @ 2.80GHz, ## Sample types -The datapath is templated on the sample type via `srt::sample_traits` +The datapath is templated on the sample type via `tap::samplerate::sample_traits` (`include/srt/sample_traits.h`). Three formats are provided: | Type | Alias | Format | Measured SNR (997 Hz / 19.5 kHz, half scale, +200 ppm) | diff --git a/bench/bench_asrc.cpp b/bench/bench_asrc.cpp index 9f41d5e..4a20f6f 100644 --- a/bench/bench_asrc.cpp +++ b/bench/bench_asrc.cpp @@ -1,5 +1,5 @@ // Host benchmarks for the SampleRateTap hot path. Two layers: -// - Kernel: srt::interpolate() in isolation (one output sample, one +// - Kernel: tap::samplerate::interpolate() in isolation (one output sample, one // channel) — the datapath's arithmetic floor. // - Pipeline: steady-state push()+pull() through the full converter in // 128-frame blocks, the realistic duplex cost per frame. @@ -22,7 +22,7 @@ namespace { if constexpr (std::is_floating_point_v) return static_cast(v); else - return srt::detail::round_sat(v * static_cast(std::numeric_limits::max())); + return tap::samplerate::detail::round_sat(v * static_cast(std::numeric_limits::max())); } template @@ -35,29 +35,29 @@ namespace { } template - void kernelBench(benchmark::State& state, const srt::filter_spec& spec) { - const srt::polyphase_filter_bank bank(spec, 48000.0); + void kernelBench(benchmark::State& state, const tap::samplerate::filter_spec& spec) { + const tap::samplerate::polyphase_filter_bank bank(spec, 48000.0); const auto hist = sineBlock(bank.taps(), 997.0, 0.5); double mu = 0.0; for (auto _ : state) { mu += 0.6180339887498949; // golden-ratio stride visits phases evenly if (mu >= 1.0) mu -= 1.0; - benchmark::DoNotOptimize(srt::interpolate(bank, hist.data(), mu)); + benchmark::DoNotOptimize(tap::samplerate::interpolate(bank, hist.data(), mu)); } state.SetItemsProcessed(static_cast(state.iterations())); } template - void pipelineBench(benchmark::State& state, const srt::filter_spec& spec, std::size_t channels) { + void pipelineBench(benchmark::State& state, const tap::samplerate::filter_spec& spec, std::size_t channels) { constexpr std::size_t kBlock = 128; - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = channels; cfg.filter = spec; // The FIFO setpoint must exceed the pull block size (see README latency // notes); 2 blocks gives headroom without distorting per-frame cost. cfg.target_latency_frames = 2 * kBlock; - srt::basic_async_sample_rate_converter asrc(cfg); + tap::samplerate::basic_async_sample_rate_converter asrc(cfg); // One second of pregenerated input so signal synthesis stays out of the // measured region; consumed cyclically. @@ -87,19 +87,19 @@ namespace { // --- Kernel: type x preset ------------------------------------------------ void BM_Kernel_Float_Fast(benchmark::State& s) { - kernelBench(s, srt::filter_spec::fast()); + kernelBench(s, tap::samplerate::filter_spec::fast()); } void BM_Kernel_Float_Balanced(benchmark::State& s) { - kernelBench(s, srt::filter_spec::balanced()); + kernelBench(s, tap::samplerate::filter_spec::balanced()); } void BM_Kernel_Float_Transparent(benchmark::State& s) { - kernelBench(s, srt::filter_spec::transparent()); + kernelBench(s, tap::samplerate::filter_spec::transparent()); } void BM_Kernel_Q15_Balanced(benchmark::State& s) { - kernelBench(s, srt::filter_spec::balanced()); + kernelBench(s, tap::samplerate::filter_spec::balanced()); } void BM_Kernel_Q31_Balanced(benchmark::State& s) { - kernelBench(s, srt::filter_spec::balanced()); + kernelBench(s, tap::samplerate::filter_spec::balanced()); } BENCHMARK(BM_Kernel_Float_Fast); BENCHMARK(BM_Kernel_Float_Balanced); @@ -109,36 +109,36 @@ namespace { // --- Pipeline: type x channels (balanced), plus the transparent ceiling --- void BM_Pipeline_Float_Balanced_1ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 1); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 1); } void BM_Pipeline_Float_Balanced_2ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 2); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 2); } void BM_Pipeline_Float_Balanced_8ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 8); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 8); } void BM_Pipeline_Q15_Balanced_2ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 2); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 2); } void BM_Pipeline_Q31_Balanced_2ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 2); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 2); } void BM_Pipeline_Float_Transparent_2ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::transparent(), 2); + pipelineBench(s, tap::samplerate::filter_spec::transparent(), 2); } // Deployment shapes: 12 channels (7.1.4 surround), 16 (AVB stream bundling // reference microphones with the program feed). void BM_Pipeline_Float_Balanced_12ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 12); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 12); } void BM_Pipeline_Q15_Balanced_12ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 12); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 12); } void BM_Pipeline_Float_Balanced_16ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 16); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 16); } void BM_Pipeline_Q15_Balanced_16ch(benchmark::State& s) { - pipelineBench(s, srt::filter_spec::balanced(), 16); + pipelineBench(s, tap::samplerate::filter_spec::balanced(), 16); } BENCHMARK(BM_Pipeline_Float_Balanced_1ch); BENCHMARK(BM_Pipeline_Float_Balanced_2ch); diff --git a/bench/compare/bench_compare.cpp b/bench/compare/bench_compare.cpp index a588ad4..7dc9403 100644 --- a/bench/compare/bench_compare.cpp +++ b/bench/compare/bench_compare.cpp @@ -73,9 +73,9 @@ namespace { }; template - void srtBench(benchmark::State& state, const srt::filter_spec& spec, std::size_t channels) { - const srt::polyphase_filter_bank bank(spec, 48000.0); - srt::fractional_resampler rs(bank, channels); + void srtBench(benchmark::State& state, const tap::samplerate::filter_spec& spec, std::size_t channels) { + const tap::samplerate::polyphase_filter_bank bank(spec, 48000.0); + tap::samplerate::fractional_resampler rs(bank, channels); InputTap inFloat(48000, channels); // Requantize the shared float source once at setup for fixed-point runs. std::vector buf(48000 * channels); @@ -86,7 +86,7 @@ namespace { if constexpr (std::is_floating_point_v) buf[i] = tmp[i]; else - buf[i] = srt::detail::round_sat(static_cast(tmp[i]) + buf[i] = tap::samplerate::detail::round_sat(static_cast(tmp[i]) * static_cast(std::numeric_limits::max())); } } @@ -176,13 +176,13 @@ namespace { // --- ~120 dB tier: mono / stereo / 8ch ------------------------------------- void BM_SRT_Balanced_1ch(benchmark::State& s) { - srtBench(s, srt::filter_spec::balanced(), 1); + srtBench(s, tap::samplerate::filter_spec::balanced(), 1); } void BM_SRT_Balanced_2ch(benchmark::State& s) { - srtBench(s, srt::filter_spec::balanced(), 2); + srtBench(s, tap::samplerate::filter_spec::balanced(), 2); } void BM_SRT_Balanced_8ch(benchmark::State& s) { - srtBench(s, srt::filter_spec::balanced(), 8); + srtBench(s, tap::samplerate::filter_spec::balanced(), 8); } void BM_LSR_Medium_1ch(benchmark::State& s) { lsrBench(s, SRC_SINC_MEDIUM_QUALITY, 1); @@ -214,7 +214,7 @@ namespace { // --- ~140 dB tier, stereo --------------------------------------------------- void BM_SRT_Transparent_2ch(benchmark::State& s) { - srtBench(s, srt::filter_spec::transparent(), 2); + srtBench(s, tap::samplerate::filter_spec::transparent(), 2); } void BM_LSR_Best_2ch(benchmark::State& s) { lsrBench(s, SRC_SINC_BEST_QUALITY, 2); @@ -229,7 +229,7 @@ namespace { // --- Fixed-point (no competitor analog; libsamplerate and soxr are // float-only engines — this is the row embedded targets actually run) ------ void BM_SRT_Q15_Balanced_2ch(benchmark::State& s) { - srtBench(s, srt::filter_spec::balanced(), 2); + srtBench(s, tap::samplerate::filter_spec::balanced(), 2); } BENCHMARK(BM_SRT_Q15_Balanced_2ch); diff --git a/bench/icount/cmp_main.cpp b/bench/icount/cmp_main.cpp index 08a3a02..e85eae5 100644 --- a/bench/icount/cmp_main.cpp +++ b/bench/icount/cmp_main.cpp @@ -45,8 +45,8 @@ namespace { #if SRT_CMP_ENGINE == 0 double run() { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), 48000.0); - srt::fractional_resampler rs(bank, kCh); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), 48000.0); + tap::samplerate::fractional_resampler rs(bank, kCh); const auto input = sineInput(12000); // 0.25 s, cycled std::size_t pos = 0; const auto pop = [&](float* dst, std::size_t n) { diff --git a/bench/icount/icount_main.cpp b/bench/icount/icount_main.cpp index cded0f3..f40863a 100644 --- a/bench/icount/icount_main.cpp +++ b/bench/icount/icount_main.cpp @@ -26,7 +26,7 @@ namespace { if constexpr (std::is_floating_point_v) return static_cast(v); else - return srt::detail::round_sat(v * static_cast(std::numeric_limits::max())); + return tap::samplerate::detail::round_sat(v * static_cast(std::numeric_limits::max())); } template @@ -40,7 +40,7 @@ namespace { template double runKernel() { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), 48000.0); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), 48000.0); const auto hist = sineBlock(bank.taps(), 997.0, 0.5); double sink = 0.0; double mu = 0.0; @@ -48,7 +48,7 @@ namespace { mu += 0.6180339887498949; if (mu >= 1.0) mu -= 1.0; - sink += static_cast(srt::interpolate(bank, hist.data(), mu)); + sink += static_cast(tap::samplerate::interpolate(bank, hist.data(), mu)); } return sink; } @@ -61,9 +61,9 @@ namespace { double runPipeline() { constexpr std::size_t kCh = SRT_SC_CH; constexpr std::size_t kBlock = 32; - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = kCh; - srt::basic_async_sample_rate_converter asrc(cfg); + tap::samplerate::basic_async_sample_rate_converter asrc(cfg); const auto input = sineBlock(12000 * kCh, 997.0, 0.5); // 0.25 s, cycled std::vector out(kBlock * kCh); diff --git a/book/src/appendix/cpp-decisions.md b/book/src/appendix/cpp-decisions.md index 5b9be81..44558d6 100644 --- a/book/src/appendix/cpp-decisions.md +++ b/book/src/appendix/cpp-decisions.md @@ -600,8 +600,8 @@ the conversion is a `reinterpret_cast` in a pair of helpers: extern "C" { struct SrtHandle; } // opaque namespace { -srt::async_sample_rate_converter* impl(SrtHandle* h) noexcept { ... } -const srt::async_sample_rate_converter* impl(const SrtHandle* h) noexcept { ... } +tap::samplerate::async_sample_rate_converter* impl(SrtHandle* h) noexcept { ... } +const tap::samplerate::async_sample_rate_converter* impl(const SrtHandle* h) noexcept { ... } } ``` diff --git a/book/src/part1/sample-traits.md b/book/src/part1/sample-traits.md index 48e9d83..074b512 100644 --- a/book/src/part1/sample-traits.md +++ b/book/src/part1/sample-traits.md @@ -422,7 +422,7 @@ python3 -c "print(32767*65535, 2**31-1, 1 - 32767*65535/(2**31-1))" # and DcGainIsUnityQ15 fails its ±4 tolerance. # 2. In finalize (Q15), delete clamp_sat and cast directly — the full-scale # sine test detects wraparound as a blown second difference. -# 3. Instantiate srt::basic_async_sample_rate_converter anywhere and +# 3. Instantiate tap::samplerate::basic_async_sample_rate_converter anywhere and # read the concept diagnostic: every missing operation, by name, at the # line you wrote. ``` diff --git a/book/src/part2/tests.md b/book/src/part2/tests.md index 28d3c57..a9ce45e 100644 --- a/book/src/part2/tests.md +++ b/book/src/part2/tests.md @@ -37,7 +37,7 @@ And a representative enforcement: ```cpp TEST(AsrcQuality, Balanced997Hz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::balanced(), 997.0), 128.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::balanced(), 997.0), 128.0); } ``` diff --git a/book/src/part5/scaling.md b/book/src/part5/scaling.md index 2e8ae97..8a5703a 100644 --- a/book/src/part5/scaling.md +++ b/book/src/part5/scaling.md @@ -153,7 +153,7 @@ by a factor of three because its tuning was written in hertz. The remedy is the `scaled_to` trio, and the factory that applies it: ```cpp -srt::config cfg = srt::config::for_sample_rate(16000.0); +tap::samplerate::config cfg = tap::samplerate::config::for_sample_rate(16000.0); cfg.channels = ...; // then adjust as usual ``` @@ -293,7 +293,7 @@ ctest --test-dir build -R AsrcQuality16k --output-on-failure # The -32 dB failure itself, reproduced: in test_asrc_quality_16k.cpp, # keep config::for_sample_rate(k_fs) but overwrite the servo with unscaled -# defaults (cfg.servo = srt::servo_config{};) — the converter still builds +# defaults (cfg.servo = tap::samplerate::servo_config{};) — the converter still builds # and locks, and every threshold fails by ~30 dB, falling 6 dB per octave # of tone frequency: the FM signature. (Restoring the unscaled *filter* # instead fails fast: the constructor rejects band edges above the input diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index c9fcce0..6348602 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -10,7 +10,7 @@ the hot path follow it. |---|---|---| | Throughput | ns per output frame, steady-state `pull()`+`push()`, reported as ×realtime at 48 kHz | host (Google Benchmark) | | Tail latency | p99/max per-call time for `pull(128)` over long runs — the RT budget lives in the tail, not the mean | host | -| Kernel cost | `srt::interpolate()` in isolation (≈ all datapath cycles: taps × channels MACs) | host | +| Kernel cost | `tap::samplerate::interpolate()` in isolation (≈ all datapath cycles: taps × channels MACs) | host | | Embedded cost | **executed instructions** per output frame via QEMU TCG plugins — deterministic to the instruction, noise-free, well-correlated with real cost for scalar code | Hexagon (qemu-user), Cortex-M55 and Cortex-M33 (qemu-system) | Cycle-accurate embedded numbers require vendor simulators (Hexagon SDK diff --git a/examples/alsa_bridge.cpp b/examples/alsa_bridge.cpp index ff3b242..f94c05b 100644 --- a/examples/alsa_bridge.cpp +++ b/examples/alsa_bridge.cpp @@ -40,13 +40,13 @@ namespace { g_stop.store(true, std::memory_order_relaxed); } - const char* state_name(srt::converter_state s) { + const char* state_name(tap::samplerate::converter_state s) { switch (s) { - case srt::converter_state::filling: + case tap::samplerate::converter_state::filling: return "Filling"; - case srt::converter_state::acquiring: + case tap::samplerate::converter_state::acquiring: return "Acquiring"; - case srt::converter_state::locked: + case tap::samplerate::converter_state::locked: return "Locked"; } return "?"; @@ -223,7 +223,7 @@ int main(int argc, char** argv) { || !open_device(out, args.out_dev, SND_PCM_STREAM_PLAYBACK, args)) return 1; - srt::config cfg; + tap::samplerate::config cfg; cfg.sample_rate_hz = static_cast(args.rate); cfg.channels = args.channels; cfg.target_latency_frames = args.latency; @@ -232,7 +232,7 @@ int main(int argc, char** argv) { // occupancy excursions can demote the servo stage spuriously. cfg.servo.unlock_threshold_frames = std::max(cfg.servo.unlock_threshold_frames, 1.5 * static_cast(args.period)); - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); std::printf("designed latency: %.2f ms%s\n", asrc.designed_latency_seconds() * 1e3, args.tone_hz > 0.0 ? " (tone mode: captured samples discarded)" : ""); diff --git a/examples/drifting_clocks.cpp b/examples/drifting_clocks.cpp index da640ab..0fba174 100644 --- a/examples/drifting_clocks.cpp +++ b/examples/drifting_clocks.cpp @@ -34,13 +34,13 @@ namespace { constexpr std::size_t k_chunk = 96; constexpr double k_run_seconds = 20.0; - const char* state_name(srt::converter_state s) { + const char* state_name(tap::samplerate::converter_state s) { switch (s) { - case srt::converter_state::filling: + case tap::samplerate::converter_state::filling: return "Filling"; - case srt::converter_state::acquiring: + case tap::samplerate::converter_state::acquiring: return "Acquiring"; - case srt::converter_state::locked: + case tap::samplerate::converter_state::locked: return "Locked"; } return "?"; @@ -49,12 +49,12 @@ namespace { } // namespace int main() { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; cfg.target_latency_frames = 960; // 20 ms: room for OS scheduling jitter cfg.servo.lock_threshold_frames = 4.0; cfg.servo.unlock_threshold_frames = 96.0; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); std::printf("drifting_clocks: producer 48000.0 Hz, consumer %+.0f ppm, %g s\n", k_consumer_ppm, k_run_seconds); std::printf("designed latency: %.2f ms\n", asrc.designed_latency_seconds() * 1e3); @@ -114,7 +114,7 @@ int main() { // The producer pushes at exactly 48000 Hz and the consumer pulls 500 ppm // fast, so the converter must consume input slower than unity: -500 ppm. std::printf("\nfinal: state=%s ppm(avg)=%+.2f (expected ~%+.0f)\n", state_name(st.state), ppm_avg, -k_consumer_ppm); - const bool ok = st.state == srt::converter_state::locked && std::abs(ppm_avg + k_consumer_ppm) < 150.0; + const bool ok = st.state == tap::samplerate::converter_state::locked && std::abs(ppm_avg + k_consumer_ppm) < 150.0; std::printf("%s\n", ok ? "OK" : "NOT CONVERGED (heavily loaded machine?)"); return ok ? 0 : 1; } diff --git a/examples/pico2_cyccnt/main.cpp b/examples/pico2_cyccnt/main.cpp index fbb36e5..b8b2c5f 100644 --- a/examples/pico2_cyccnt/main.cpp +++ b/examples/pico2_cyccnt/main.cpp @@ -64,7 +64,7 @@ namespace { if constexpr (std::is_floating_point_v) return static_cast(v); else - return srt::detail::roundSat(v * static_cast(std::numeric_limits::max())); + return tap::samplerate::detail::roundSat(v * static_cast(std::numeric_limits::max())); } template @@ -77,18 +77,18 @@ namespace { } template - void runCase(const char* typeName, const char* presetName, const srt::filter_spec& spec, std::size_t channels) { - srt::Config cfg; + void runCase(const char* typeName, const char* presetName, const tap::samplerate::filter_spec& spec, std::size_t channels) { + tap::samplerate::Config cfg; cfg.channels = channels; cfg.filter = spec; // Heap-constructed so allocation failure (e.g. 12ch + float on a tighter // build) degrades to a printed SKIP row instead of a hard fault. - std::unique_ptr> asrc; + std::unique_ptr> asrc; std::vector input; std::vector out; try { - asrc = std::make_unique>(cfg); + asrc = std::make_unique>(cfg); input = sineBlock(kInputFrames * channels, 997.0, 0.5); out.resize(kBlockFrames * channels); } @@ -160,14 +160,14 @@ int main() { "cyc/frame", "%core@48k"); for (const std::size_t ch : {std::size_t{1}, std::size_t{2}, std::size_t{12}}) { - runCase("q15", "fast", srt::filter_spec::fast(), ch); - runCase("q15", "balanced", srt::filter_spec::balanced(), ch); + runCase("q15", "fast", tap::samplerate::filter_spec::fast(), ch); + runCase("q15", "balanced", tap::samplerate::filter_spec::balanced(), ch); } #if PICO2_MEASURE_FLOAT // Soft FP64 accumulation: expected brutally slow on the M33 (the QEMU // baselines put pipeline_float at ~3.8x pipeline_q15 instructions). - runCase("float", "fast", srt::filter_spec::fast(), 1); - runCase("float", "balanced", srt::filter_spec::balanced(), 1); + runCase("float", "fast", tap::samplerate::filter_spec::fast(), 1); + runCase("float", "balanced", tap::samplerate::filter_spec::balanced(), 1); #endif std::printf("SRT_PICO2_DONE\n"); diff --git a/examples/pico2_dualcore/main.cpp b/examples/pico2_dualcore/main.cpp index edfabc5..e617472 100644 --- a/examples/pico2_dualcore/main.cpp +++ b/examples/pico2_dualcore/main.cpp @@ -47,7 +47,7 @@ namespace { - using Asrc = srt::AsyncSampleRateConverterQ15; + using Asrc = tap::samplerate::AsyncSampleRateConverterQ15; constexpr std::size_t kBlockFrames = 32; constexpr std::size_t kMaxChannels = 12; @@ -301,18 +301,18 @@ namespace { // balanced() with band edges scaled to 16 kHz: identical L/T — same table // size and same per-frame cycle cost — with pass/stop at the same normalized // frequencies (README "Measured performance"; tests/test_asrc_quality_16k.cpp). - srt::filter_spec balanced16k() { - srt::filter_spec f = srt::filter_spec::balanced(); + tap::samplerate::filter_spec balanced16k() { + tap::samplerate::filter_spec f = tap::samplerate::filter_spec::balanced(); f.passband_hz = 20000.0 * 16.0 / 48.0; f.stopband_hz = 28000.0 * 16.0 / 48.0; return f; } - const char* stateName(srt::converter_state s) { + const char* stateName(tap::samplerate::converter_state s) { switch (s) { - case srt::converter_state::Filling: + case tap::samplerate::converter_state::Filling: return "Filling"; - case srt::converter_state::Acquiring: + case tap::samplerate::converter_state::Acquiring: return "Acquiring"; default: return "Locked"; @@ -328,7 +328,7 @@ namespace { std::vector out(kInputFrames * channels); const double w = 2.0 * std::numbers::pi * 997.0 / rateHz; for (std::size_t f = 0; f < kInputFrames; ++f) { - const auto v = srt::detail::roundSat(0.5 * std::sin(w * static_cast(f)) * 32767.0); + const auto v = tap::samplerate::detail::roundSat(0.5 * std::sin(w * static_cast(f)) * 32767.0); for (std::size_t c = 0; c < channels; ++c) out[f * channels + c] = v; } @@ -338,7 +338,7 @@ namespace { PhaseResult runPhase(const PhaseSpec& ph) { PhaseResult r; - srt::Config cfg; + tap::samplerate::Config cfg; cfg.sample_rate_hz = ph.rateHz; cfg.channels = ph.channels; cfg.target_latency_frames = kTargetLatencyFrames; @@ -410,8 +410,8 @@ namespace { if (off + kBlockFrames * ph.channels > input.size()) off = 0; - const srt::converter_status st = asrc->status(); - if (!locked && st.state == srt::converter_state::Locked) { + const tap::samplerate::converter_status st = asrc->status(); + if (!locked && st.state == tap::samplerate::converter_state::Locked) { locked = true; lockUs = time_us_64() - tStart; undAtLock = st.underruns; @@ -450,7 +450,7 @@ namespace { while (!g.consumerDone.load(std::memory_order_acquire)) tight_loop_contents(); const Snapshot fin = readSnapshot(); - const srt::converter_status st = asrc->status(); + const tap::samplerate::converter_status st = asrc->status(); ppmFinal = st.ppm; g.asrc.store(nullptr, std::memory_order_release); diff --git a/include/srt/asrc.h b/include/srt/asrc.h index ab24885..99d961e 100644 --- a/include/srt/asrc.h +++ b/include/srt/asrc.h @@ -18,7 +18,7 @@ #include "srt/sample_traits.h" #include "srt/spsc_ring.h" -namespace srt { +namespace tap::samplerate { // ANCHOR: p0_config /// Converter configuration. The defaults give ~1.5 ms designed latency at @@ -41,7 +41,7 @@ namespace srt { /// see filter_spec::scaledTo and servo_config::scaledTo — and is the /// recommended starting point for any non-48 kHz deployment: /// - /// srt::Config cfg = srt::Config::forSampleRate(16000.0); + /// tap::samplerate::Config cfg = tap::samplerate::Config::forSampleRate(16000.0); /// cfg.channels = ...; // then adjust as usual /// /// Frame-denominated fields (targetLatencyFrames, fifoFrames) are @@ -412,4 +412,4 @@ namespace srt { /// Q31 fixed-point converter (int32_t samples; see sample_traits). using async_sample_rate_converter_q31 = basic_async_sample_rate_converter; -} // namespace srt +} // namespace tap::samplerate diff --git a/include/srt/detail/kaiser.h b/include/srt/detail/kaiser.h index e2c751d..2ad5891 100644 --- a/include/srt/detail/kaiser.h +++ b/include/srt/detail/kaiser.h @@ -20,7 +20,7 @@ #include #include -namespace srt::detail { +namespace tap::samplerate::detail { // ANCHOR: kai_besseli0 /// Modified Bessel function of the first kind, order zero, by power series. @@ -360,4 +360,4 @@ namespace srt::detail { build(); } -} // namespace srt::detail +} // namespace tap::samplerate::detail diff --git a/include/srt/pi_servo.h b/include/srt/pi_servo.h index e974f15..6ba56fb 100644 --- a/include/srt/pi_servo.h +++ b/include/srt/pi_servo.h @@ -46,7 +46,7 @@ #include #include -namespace srt { +namespace tap::samplerate { // ANCHOR: sv_config /// Servo tuning. Defaults suit a 48 kHz near-unity converter. @@ -263,4 +263,4 @@ namespace srt { lock_stage m_stage = lock_stage::acquire; }; -} // namespace srt +} // namespace tap::samplerate diff --git a/include/srt/polyphase_filter.h b/include/srt/polyphase_filter.h index fe4f3c9..bc11b00 100644 --- a/include/srt/polyphase_filter.h +++ b/include/srt/polyphase_filter.h @@ -64,7 +64,7 @@ #define SRT_CP_MIN_CHANNELS 4 #endif -namespace srt { +namespace tap::samplerate { // ANCHOR: bank_spec /// Specification of the interpolation prototype filter. @@ -658,4 +658,4 @@ namespace srt { bool m_primed = false; }; -} // namespace srt +} // namespace tap::samplerate diff --git a/include/srt/sample_traits.h b/include/srt/sample_traits.h index 0347d0e..fd14b4d 100644 --- a/include/srt/sample_traits.h +++ b/include/srt/sample_traits.h @@ -25,7 +25,7 @@ #include #include -namespace srt { +namespace tap::samplerate { namespace detail { @@ -245,4 +245,4 @@ namespace srt { static_assert(sample_type); // ANCHOR_END: st_concept -} // namespace srt +} // namespace tap::samplerate diff --git a/include/srt/spsc_ring.h b/include/srt/spsc_ring.h index d29b423..2160a6a 100644 --- a/include/srt/spsc_ring.h +++ b/include/srt/spsc_ring.h @@ -19,7 +19,7 @@ #include #include -namespace srt { +namespace tap::samplerate { // ANCHOR: contract /// Lock-free SPSC ring buffer of trivially copyable elements. @@ -137,4 +137,4 @@ namespace srt { // ANCHOR_END: layout }; -} // namespace srt +} // namespace tap::samplerate diff --git a/scripts/book_figures_trace.cpp b/scripts/book_figures_trace.cpp index d4bc3d7..7acea2f 100644 --- a/scripts/book_figures_trace.cpp +++ b/scripts/book_figures_trace.cpp @@ -29,9 +29,9 @@ int main(int argc, char** argv) { const double dropStart = argc > 5 ? std::atof(argv[5]) : -1.0; const double dropDur = argc > 6 ? std::atof(argv[6]) : 0.0; - srt::Config cfg; + tap::samplerate::Config cfg; cfg.channels = 1; - srt::AsyncSampleRateConverter conv(cfg); + tap::samplerate::AsyncSampleRateConverter conv(cfg); const double fsOut = cfg.sampleRateHz; const double fsIn = fsOut * (1.0 + ppm * 1e-6); // producer's crystal @@ -54,7 +54,7 @@ int main(int argc, char** argv) { } conv.pull(out.data(), pullBlock); tPull += static_cast(pullBlock) / fsOut; - const srt::Status s = conv.status(); + const tap::samplerate::Status s = conv.status(); std::printf("%.6f,%.2f,%d,%.2f,%llu\n", tPull, s.fifoFillFrames, static_cast(s.state), s.ppm, static_cast(s.underruns)); } diff --git a/tests/support/multitone_analysis.h b/tests/support/multitone_analysis.h index 1b5e2f1..bd31ca9 100644 --- a/tests/support/multitone_analysis.h +++ b/tests/support/multitone_analysis.h @@ -140,7 +140,7 @@ namespace srt_test { ata[r * n2 + q] = ata[q * n2 + r]; } } - srt::detail::solve_dense(ata, aty, sol, n2); + tap::samplerate::detail::solve_dense(ata, aty, sol, n2); for (std::size_t t = 0; t < k; ++t) { fits[t] = tone_fit{sol[2 * t], sol[2 * t + 1]}; } diff --git a/tests/support/two_clock_sim.h b/tests/support/two_clock_sim.h index 3f0b738..aad23b7 100644 --- a/tests/support/two_clock_sim.h +++ b/tests/support/two_clock_sim.h @@ -14,9 +14,9 @@ namespace srt_test { // ANCHOR: pf_knobs - template + template struct two_clock_sim_t { - srt::basic_async_sample_rate_converter& asrc; + tap::samplerate::basic_async_sample_rate_converter& asrc; double fs_in; ///< input-domain event rate (true input sample rate) double fs_out; ///< output-domain event rate (true output sample rate) std::size_t channels = 1; diff --git a/tests/test_asrc_lock.cpp b/tests/test_asrc_lock.cpp index ccfbca5..5b5da22 100644 --- a/tests/test_asrc_lock.cpp +++ b/tests/test_asrc_lock.cpp @@ -11,14 +11,14 @@ namespace { constexpr double k_fs = 48000.0; - srt::config mono_config() { - srt::config cfg; + tap::samplerate::config mono_config() { + tap::samplerate::config cfg; cfg.channels = 1; return cfg; } TEST(AsrcLock, LocksAndHoldsAtConstantOffset) { - srt::async_sample_rate_converter asrc(mono_config()); + tap::samplerate::async_sample_rate_converter asrc(mono_config()); srt_test::two_clock_sim sim{.asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1}; bool locked_by2s = false; double ppm_sum = 0.0; @@ -26,7 +26,7 @@ namespace { std::size_t tail_blocks = 0; sim.run(60.0, [&](const float*, std::size_t, double t) { const auto st = asrc.status(); - if (t < 2.0 && st.state == srt::converter_state::locked) { + if (t < 2.0 && st.state == tap::samplerate::converter_state::locked) { locked_by2s = true; } if (t > 30.0) { // average over many block-beat cycles @@ -37,7 +37,7 @@ namespace { }); const auto st = asrc.status(); EXPECT_TRUE(locked_by2s); - EXPECT_EQ(st.state, srt::converter_state::locked); + EXPECT_EQ(st.state, tap::samplerate::converter_state::locked); EXPECT_EQ(st.underruns, 0u); EXPECT_EQ(st.overruns, 0u); EXPECT_EQ(st.resyncs, 0u); @@ -51,7 +51,7 @@ namespace { } TEST(AsrcLock, TracksDriftRampWithoutUnlocking) { - srt::async_sample_rate_converter asrc(mono_config()); + tap::samplerate::async_sample_rate_converter asrc(mono_config()); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = k_fs, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; // Input clock drifts 0 -> +300 ppm over 30 s (10 ppm/s, far faster than @@ -61,7 +61,7 @@ namespace { bool ever_locked = false; sim.run(45.0, [&](const float*, std::size_t, double) { const auto st = asrc.status(); - if (st.state == srt::converter_state::locked) { + if (st.state == tap::samplerate::converter_state::locked) { ever_locked = true; } else if (ever_locked) { @@ -80,7 +80,7 @@ namespace { // At +500 ppm a forward slip happens every 2000 output samples. A clean // sine's second difference is bounded by A*omega^2; any window-shift // discontinuity would blow far past that bound. - srt::async_sample_rate_converter asrc(mono_config()); + tap::samplerate::async_sample_rate_converter asrc(mono_config()); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double amp = 0.5; @@ -110,10 +110,10 @@ namespace { TEST(AsrcLock, RecoversFromConsumerStall) { // Producer keeps pushing while the consumer stops pulling: occupancy blows // through the high watermark, the converter hard-resyncs, then relocks. - srt::async_sample_rate_converter asrc(mono_config()); + tap::samplerate::async_sample_rate_converter asrc(mono_config()); srt_test::two_clock_sim sim{.asrc = asrc, .fs_in = k_fs * (1.0 + 100e-6), .fs_out = k_fs, .channels = 1}; sim.run(10.0, [&](const float*, std::size_t, double) {}); - ASSERT_EQ(asrc.status().state, srt::converter_state::locked); + ASSERT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); // Stall: push 3000 frames with no pulls (FIFO capacity is 1024 mono). std::vector burst(3000, 0.0f); @@ -127,7 +127,7 @@ namespace { srt_test::two_clock_sim resume{.asrc = asrc, .fs_in = k_fs * (1.0 + 100e-6), .fs_out = k_fs, .channels = 1}; resume.run(10.0, [&](const float*, std::size_t, double) {}); const auto st = asrc.status(); - EXPECT_EQ(st.state, srt::converter_state::locked); + EXPECT_EQ(st.state, tap::samplerate::converter_state::locked); EXPECT_GE(st.resyncs, 1u); } diff --git a/tests/test_asrc_program.cpp b/tests/test_asrc_program.cpp index 1de38ea..0724685 100644 --- a/tests/test_asrc_program.cpp +++ b/tests/test_asrc_program.cpp @@ -23,11 +23,11 @@ namespace { // 24 pink-weighted tones, 60 Hz - 16 kHz, through a +200 ppm offset; the // residual after removing every tone is everything the converter got wrong, // weighted the way real program material weights it. - double measure_program_snr_db(const srt::filter_spec& spec) { - srt::config cfg; + double measure_program_snr_db(const tap::samplerate::filter_spec& spec) { + tap::samplerate::config cfg; cfg.channels = 1; cfg.filter = spec; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); const double fs_in = k_fs * (1.0 + k_eps); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = fs_in, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; @@ -42,7 +42,7 @@ namespace { } }); EXPECT_EQ(asrc.status().underruns, 0u); - EXPECT_EQ(asrc.status().state, srt::converter_state::locked); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); const double snr = srt_test::program_weighted_snr_db(tail, comb, fs_in, k_fs); std::printf("[ measured ] program-weighted (24 pink tones), %zu phases x %zu taps: %.1f dB\n", spec.num_phases, spec.taps_per_phase, snr); @@ -52,11 +52,11 @@ namespace { // Worst-case single sine near Nyquist, for the honesty line in economy()'s // documentation: this preset trades exactly this number. - double measure_sine_snr_db(const srt::filter_spec& spec, double freq_hz) { - srt::config cfg; + double measure_sine_snr_db(const tap::samplerate::filter_spec& spec, double freq_hz) { + tap::samplerate::config cfg; cfg.channels = 1; cfg.filter = spec; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; @@ -106,12 +106,12 @@ namespace { // while its worst-case sine near Nyquist honestly reads ~96 dB-class. TEST(ProgramWeighted, BalancedBaseline) { // Measured 134.5 dB. - EXPECT_GT(measure_program_snr_db(srt::filter_spec::balanced()), 128.0); + EXPECT_GT(measure_program_snr_db(tap::samplerate::filter_spec::balanced()), 128.0); } TEST(ProgramWeighted, EconomyNearBalanced) { // Measured 131.6 dB — 2.9 dB under balanced at 2/3 the per-sample // compute. This single number is the preset's reason to exist. - const double eco = measure_program_snr_db(srt::filter_spec::economy()); + const double eco = measure_program_snr_db(tap::samplerate::filter_spec::economy()); EXPECT_GT(eco, 125.0); } TEST(ProgramWeighted, EconomyWorstCaseSineIsDocumented) { @@ -119,7 +119,7 @@ namespace { // "96 dB-class"; the extra gap to 77 dB at 19.5 kHz is the L=512 // interpolation floor at 0.40625 of the sample rate plus the design's // transition starting at 18 kHz.) - EXPECT_GT(measure_sine_snr_db(srt::filter_spec::economy(), 19500.0), 70.0); + EXPECT_GT(measure_sine_snr_db(tap::samplerate::filter_spec::economy(), 19500.0), 70.0); } } // namespace diff --git a/tests/test_asrc_quality.cpp b/tests/test_asrc_quality.cpp index 3adba2d..fab4c86 100644 --- a/tests/test_asrc_quality.cpp +++ b/tests/test_asrc_quality.cpp @@ -19,11 +19,11 @@ namespace { // transfer) and measures the residual after removing the fitted fundamental // from the last second of output. The output normalized frequency is the // input normalized frequency scaled by fsIn/fsOut. - double measure_snr_db(const srt::filter_spec& spec, double freq_hz) { - srt::config cfg; + double measure_snr_db(const tap::samplerate::filter_spec& spec, double freq_hz) { + tap::samplerate::config cfg; cfg.channels = 1; cfg.filter = spec; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; @@ -41,7 +41,7 @@ namespace { } }); EXPECT_EQ(asrc.status().underruns, 0u); - EXPECT_EQ(asrc.status().state, srt::converter_state::locked); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); const double nu_out_expected = nu_in * (1.0 + k_eps); const auto fit = srt_test::fit_sine_tracked(tail, nu_out_expected); EXPECT_NEAR(fit.amplitude, k_amp, 0.01); @@ -58,22 +58,22 @@ namespace { // phase-table rows, which falls ~12 dB per doubling of numPhases and rises // ~12 dB per octave of signal frequency. TEST(AsrcQuality, Balanced997Hz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::balanced(), 997.0), 128.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::balanced(), 997.0), 128.0); } TEST(AsrcQuality, Balanced6kHz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::balanced(), 6000.0), 114.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::balanced(), 6000.0), 114.0); } TEST(AsrcQuality, Balanced12kHz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::balanced(), 12000.0), 106.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::balanced(), 12000.0), 106.0); } TEST(AsrcQuality, Balanced19_5kHz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::balanced(), 19500.0), 100.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::balanced(), 19500.0), 100.0); } TEST(AsrcQuality, Transparent997Hz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::transparent(), 997.0), 128.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::transparent(), 997.0), 128.0); } TEST(AsrcQuality, Transparent19_5kHz) { - EXPECT_GT(measure_snr_db(srt::filter_spec::transparent(), 19500.0), 103.0); + EXPECT_GT(measure_snr_db(tap::samplerate::filter_spec::transparent(), 19500.0), 103.0); } } // namespace diff --git a/tests/test_asrc_quality_16k.cpp b/tests/test_asrc_quality_16k.cpp index 8e49430..74bd83c 100644 --- a/tests/test_asrc_quality_16k.cpp +++ b/tests/test_asrc_quality_16k.cpp @@ -41,9 +41,9 @@ namespace { // from Config::forSampleRate (filter band edges, servo bandwidths and // hold times). double measure_snr_db16k(double freq_hz) { - srt::config cfg = srt::config::for_sample_rate(k_fs); + tap::samplerate::config cfg = tap::samplerate::config::for_sample_rate(k_fs); cfg.channels = 1; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; @@ -64,7 +64,7 @@ namespace { } }); EXPECT_EQ(asrc.status().underruns, 0u); - EXPECT_EQ(asrc.status().state, srt::converter_state::locked); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); const double nu_out_expected = nu_in * (1.0 + k_eps); const auto fit = srt_test::fit_sine_tracked(tail, nu_out_expected); EXPECT_NEAR(fit.amplitude, k_amp, 0.01); @@ -85,8 +85,8 @@ namespace { // Fast deterministic check of the scaling rule itself (the sims below are // the behavioral validation). TEST(AsrcQuality16k, ForSampleRateScalesHzFieldsOnly) { - const srt::config c = srt::config::for_sample_rate(16000.0); - const srt::config d; // 48 kHz defaults + const tap::samplerate::config c = tap::samplerate::config::for_sample_rate(16000.0); + const tap::samplerate::config d; // 48 kHz defaults const double r = 16000.0 / 48000.0; EXPECT_DOUBLE_EQ(c.sample_rate_hz, 16000.0); EXPECT_DOUBLE_EQ(c.filter.passband_hz, d.filter.passband_hz * r); diff --git a/tests/test_fade.cpp b/tests/test_fade.cpp index d454efe..cc6d8a9 100644 --- a/tests/test_fade.cpp +++ b/tests/test_fade.cpp @@ -12,9 +12,9 @@ namespace { // observable: the first produced frame is strongly attenuated and the // output reaches the full DC value once the ramp has passed. TEST(Fade, OutputRampsAfterFill) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); std::vector in(32, 0.5f); std::vector out(32); diff --git a/tests/test_fixed_point.cpp b/tests/test_fixed_point.cpp index dc54971..7d03af9 100644 --- a/tests/test_fixed_point.cpp +++ b/tests/test_fixed_point.cpp @@ -15,8 +15,8 @@ namespace { constexpr double k_fs = 48000.0; constexpr double k_eps = 200e-6; - using q15 = srt::sample_traits; - using q31 = srt::sample_traits; + using q15 = tap::samplerate::sample_traits; + using q31 = tap::samplerate::sample_traits; TEST(FixedPoint, CoefficientConversionRoundsAndSaturates) { EXPECT_EQ(q15::make_coeff(0.0), 0); @@ -42,20 +42,20 @@ namespace { // the property survives fixed point exactly: measured 0 LSB deviation over // a 256-point mu sweep for both formats (tolerance 1 for safety only). TEST(FixedPoint, DcGainIsUnityQ15) { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); std::vector dc(bank.taps(), 32767); for (int i = 0; i < 16; ++i) { const double mu = static_cast(i) / 16.0; - EXPECT_NEAR(srt::interpolate(bank, dc.data(), mu), 32767, 1) << "mu=" << mu; + EXPECT_NEAR(tap::samplerate::interpolate(bank, dc.data(), mu), 32767, 1) << "mu=" << mu; } } TEST(FixedPoint, DcGainIsUnityQ31) { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); std::vector dc(bank.taps(), 2147483647); for (int i = 0; i < 16; ++i) { const double mu = static_cast(i) / 16.0; - EXPECT_NEAR(srt::interpolate(bank, dc.data(), mu), 2147483647.0, 1.0) << "mu=" << mu; + EXPECT_NEAR(tap::samplerate::interpolate(bank, dc.data(), mu), 2147483647.0, 1.0) << "mu=" << mu; } } @@ -63,8 +63,8 @@ namespace { // exactly k_coeff_scale (the largest-remainder correction in the bank ctor). template void check_row_sums_exact() { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); - const auto scale = static_cast(srt::sample_traits::k_coeff_scale); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); + const auto scale = static_cast(tap::samplerate::sample_traits::k_coeff_scale); for (std::size_t p = 0; p < bank.num_phases(); ++p) { std::int64_t sum = 0; for (std::size_t t = 0; t < bank.taps(); ++t) { @@ -82,18 +82,18 @@ namespace { // End-to-end quality across a +200 ppm clock crossing, like the float suite: // resample a sine, fit and remove the fundamental, measure the residual. - template + template double measure_snr_db(double freq_hz, double amp) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::basic_async_sample_rate_converter asrc(cfg); + tap::samplerate::basic_async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim_t sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; const double full_scale = static_cast(std::numeric_limits::max()); sim.gen = [&](std::uint64_t i) { const double v = amp * std::sin(2.0 * std::numbers::pi * nu_in * static_cast(i)); - return srt::detail::round_sat(v * full_scale); + return tap::samplerate::detail::round_sat(v * full_scale); }; std::vector tail; // normalized to [-1, 1] for the analysis helpers tail.reserve(48000); @@ -106,7 +106,7 @@ namespace { } }); EXPECT_EQ(asrc.status().underruns, 0u); - EXPECT_EQ(asrc.status().state, srt::converter_state::locked); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); const auto fit = srt_test::fit_sine_tracked(tail, nu_in * (1.0 + k_eps)); EXPECT_NEAR(fit.amplitude, amp, 0.01); const double snr = srt_test::snr_db(fit); @@ -133,14 +133,14 @@ namespace { // Drive at 99% of full scale: any internal overflow/wraparound would // produce gross discontinuities; saturating finalize must keep the // second difference at the analytic bound for a clean sine. - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter_q15 asrc(cfg); + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); srt_test::two_clock_sim_t sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu = 1000.0 / k_fs; sim.gen = [&](std::uint64_t i) { - return srt::detail::round_sat( + return tap::samplerate::detail::round_sat( 0.99 * 32767.0 * std::sin(2.0 * std::numbers::pi * nu * static_cast(i))); }; std::vector tail; diff --git a/tests/test_hardening.cpp b/tests/test_hardening.cpp index 9ffc581..e84c6f5 100644 --- a/tests/test_hardening.cpp +++ b/tests/test_hardening.cpp @@ -25,7 +25,7 @@ namespace { // its effective setpoint to the observed block; these runs must lock with // zero underruns and report the raise. void run_feasibility(std::size_t pull_block) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; // Lock-stage promotion gates compare smoothed occupancy error against // frame thresholds; with very coarse blocks the block-quantization @@ -37,7 +37,7 @@ namespace { cfg.servo.lock_threshold_frames = static_cast(pull_block) / 8.0; cfg.servo.unlock_threshold_frames = static_cast(pull_block) * 1.5; } - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{.asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, @@ -56,7 +56,7 @@ namespace { } }); const auto st = asrc.status(); - EXPECT_EQ(st.state, srt::converter_state::locked) << "pull=" << pull_block; + EXPECT_EQ(st.state, tap::samplerate::converter_state::locked) << "pull=" << pull_block; EXPECT_EQ(st.underruns, 0u) << "pull=" << pull_block; EXPECT_GT(st.effective_target_latency_frames, 48u) << "pull=" << pull_block; EXPECT_NEAR(ppm_sum / static_cast(blocks), 200.0, 25.0) << "pull=" << pull_block; @@ -73,9 +73,9 @@ namespace { } TEST(Feasibility, SmallPullsKeepConfiguredSetpoint) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{.asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1}; sim.run(5.0, [](const float*, std::size_t, double) {}); // 32-frame pulls against the 48-frame default were always feasible; @@ -87,45 +87,45 @@ namespace { // silently (NaN coefficient tables, image-passing filters, UB-range eps). TEST(ConfigValidation, RejectsSilentMisbehavior) { { - srt::config c; + tap::samplerate::config c; c.sample_rate_hz = std::numeric_limits::quiet_NaN(); - EXPECT_THROW(srt::async_sample_rate_converter{c}, std::invalid_argument); + EXPECT_THROW(tap::samplerate::async_sample_rate_converter{c}, std::invalid_argument); } { - srt::config c; // anti-image cutoff above input Nyquist + tap::samplerate::config c; // anti-image cutoff above input Nyquist c.filter.passband_hz = 23000.0; c.filter.stopband_hz = 47000.0; - EXPECT_THROW(srt::async_sample_rate_converter{c}, std::invalid_argument); + EXPECT_THROW(tap::samplerate::async_sample_rate_converter{c}, std::invalid_argument); } { - srt::config c; // eps * 2^64 would overflow int64 in the phase path + tap::samplerate::config c; // eps * 2^64 would overflow int64 in the phase path c.servo.max_deviation_ppm = 400000.0; - EXPECT_THROW(srt::async_sample_rate_converter{c}, std::invalid_argument); + EXPECT_THROW(tap::samplerate::async_sample_rate_converter{c}, std::invalid_argument); } { - srt::config c; + tap::samplerate::config c; c.servo.quiet_bandwidth_hz = std::numeric_limits::infinity(); - EXPECT_THROW(srt::async_sample_rate_converter{c}, std::invalid_argument); + EXPECT_THROW(tap::samplerate::async_sample_rate_converter{c}, std::invalid_argument); } { - srt::config c; + tap::samplerate::config c; c.fifo_frames = 64; // below the high-watermark capacity requirement - EXPECT_THROW(srt::async_sample_rate_converter{c}, std::invalid_argument); + EXPECT_THROW(tap::samplerate::async_sample_rate_converter{c}, std::invalid_argument); } // The rate-scaling factory sits exactly on the band-edge sum boundary // (passband + stopband == fs up to rounding); it must keep constructing. - EXPECT_NO_THROW(srt::async_sample_rate_converter{srt::config::for_sample_rate(16000.0)}); - EXPECT_NO_THROW(srt::async_sample_rate_converter{srt::config::for_sample_rate(44100.0)}); + EXPECT_NO_THROW(tap::samplerate::async_sample_rate_converter{tap::samplerate::config::for_sample_rate(16000.0)}); + EXPECT_NO_THROW(tap::samplerate::async_sample_rate_converter{tap::samplerate::config::for_sample_rate(44100.0)}); } // Audit finding F3: with a setpoint below the resampler's staged-scratch // size (16 frames), a hard resync used to drain the ring entirely and // cascade straight back into Filling. TEST(Resync, SmallSetpointRecovers) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; cfg.target_latency_frames = 4; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); std::vector in(32, 0.25f); std::vector out(64); for (int i = 0; i < 8; ++i) { // reach steady operation @@ -145,23 +145,23 @@ namespace { } TEST(Reset, ConsumerResetRelocks) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{.asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1}; sim.run(5.0, [](const float*, std::size_t, double) {}); - ASSERT_EQ(asrc.status().state, srt::converter_state::locked); + ASSERT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); asrc.reset_from_consumer(); - EXPECT_EQ(asrc.status().state, srt::converter_state::filling); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::filling); srt_test::two_clock_sim sim2{.asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1}; sim2.run(5.0, [](const float*, std::size_t, double) {}); - EXPECT_EQ(asrc.status().state, srt::converter_state::locked); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); } TEST(EdgeCalls, ZeroLengthAndOversized) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 2; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); std::vector in(2 * 4096, 0.1f); std::vector out(2 * 8192); EXPECT_EQ(asrc.push(in.data(), 0), 0u); @@ -181,9 +181,9 @@ namespace { // Fixed-point fade-in: test_fade.cpp covers float only; the Q15 scaleSample // branch (round-and-saturate) was untested. TEST(FadeQ15, OutputRampsAfterFill) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter_q15 asrc(cfg); + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); std::vector in(32, 16384); std::vector out(32); std::vector made; @@ -206,14 +206,14 @@ namespace { // suites and the Hexagon leg, whose exclusion filters keep out every long // quality suite — leaving those targets without any on-target SNR check). TEST(QuickQuality, Q15Tone997) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter_q15 asrc(cfg); + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); srt_test::two_clock_sim_t sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 8, .chunk_out = 8}; const double nu = 997.0 / k_fs; sim.gen = [&](std::uint64_t i) { - return srt::detail::round_sat( + return tap::samplerate::detail::round_sat( 0.5 * 32767.0 * std::sin(2.0 * std::numbers::pi * nu * static_cast(i))); }; std::vector tail; @@ -237,14 +237,14 @@ namespace { // 1 s near-full-scale variant of FixedPoint.FullScaleSineDoesNotWrapQ15, // sized for emulation and named so the bare-metal filter keeps it: the // wide-MAC (SMLALD) target previously never saw near-full-scale input. - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter_q15 asrc(cfg); + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); srt_test::two_clock_sim_t sim{ .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 8, .chunk_out = 8}; const double nu = 1000.0 / k_fs; sim.gen = [&](std::uint64_t i) { - return srt::detail::round_sat( + return tap::samplerate::detail::round_sat( 0.99 * 32767.0 * std::sin(2.0 * std::numbers::pi * nu * static_cast(i))); }; std::vector tail; diff --git a/tests/test_kaiser.cpp b/tests/test_kaiser.cpp index ceb9d25..082cb9a 100644 --- a/tests/test_kaiser.cpp +++ b/tests/test_kaiser.cpp @@ -10,7 +10,7 @@ namespace { - using namespace srt::detail; + using namespace tap::samplerate::detail; TEST(Kaiser, BesselI0ReferenceValues) { EXPECT_DOUBLE_EQ(bessel_i0(0.0), 1.0); @@ -44,7 +44,7 @@ namespace { return 20.0 * std::log10(std::abs(acc) / static_cast(num_phases)); } - void check_prototype_meets_spec(const srt::filter_spec& spec, double fs) { + void check_prototype_meets_spec(const tap::samplerate::filter_spec& spec, double fs) { const std::size_t phases = std::bit_ceil(spec.num_phases); const std::size_t n = phases * spec.taps_per_phase; std::vector h(n); @@ -82,19 +82,19 @@ namespace { } TEST(Kaiser, FastPrototypeMeetsSpec) { - check_prototype_meets_spec(srt::filter_spec::fast(), 48000.0); + check_prototype_meets_spec(tap::samplerate::filter_spec::fast(), 48000.0); } TEST(Kaiser, BalancedPrototypeMeetsSpec) { - check_prototype_meets_spec(srt::filter_spec::balanced(), 48000.0); + check_prototype_meets_spec(tap::samplerate::filter_spec::balanced(), 48000.0); } TEST(Kaiser, TransparentPrototypeMeetsSpec) { - check_prototype_meets_spec(srt::filter_spec::transparent(), 48000.0); + check_prototype_meets_spec(tap::samplerate::filter_spec::transparent(), 48000.0); } TEST(Kaiser, EconomyPrototypeMeetsSpec) { - check_prototype_meets_spec(srt::filter_spec::economy(), 48000.0); + check_prototype_meets_spec(tap::samplerate::filter_spec::economy(), 48000.0); } // The compensated presets must also hold their specs at scaled rates (the @@ -104,7 +104,7 @@ namespace { // sum is identical (measured spread 1.8e-15 -- machine epsilon -- vs 4.7e-6 // for the plain fast() design, whose spread is its stopband leakage at fs). TEST(Kaiser, CompensatedBranchSumsAreUniform) { - const auto spec = srt::filter_spec::balanced(); + const auto spec = tap::samplerate::filter_spec::balanced(); const std::size_t phases = std::bit_ceil(spec.num_phases); std::vector h(phases * spec.taps_per_phase); design_prototype_compensated(h, phases, (spec.passband_hz + spec.stopband_hz) / 48000.0, @@ -124,8 +124,8 @@ namespace { } TEST(Kaiser, CompensatedSpecsHoldAt16k) { - check_prototype_meets_spec(srt::filter_spec::balanced().scaled_to(16000.0), 16000.0); - check_prototype_meets_spec(srt::filter_spec::economy().scaled_to(16000.0), 16000.0); + check_prototype_meets_spec(tap::samplerate::filter_spec::balanced().scaled_to(16000.0), 16000.0); + check_prototype_meets_spec(tap::samplerate::filter_spec::economy().scaled_to(16000.0), 16000.0); } } // namespace diff --git a/tests/test_latency.cpp b/tests/test_latency.cpp index d5a5460..3bc050f 100644 --- a/tests/test_latency.cpp +++ b/tests/test_latency.cpp @@ -11,9 +11,9 @@ namespace { constexpr double k_fs = 48000.0; TEST(Latency, ImpulseDelayMatchesDesignedLatency) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim sim{ .asrc = asrc, .fs_in = k_fs, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const std::uint64_t impulse_index = 24000; // 0.5 s in, well past acquisition @@ -43,9 +43,9 @@ namespace { } TEST(Latency, DesignedLatencyConsistency) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = 1; - srt::async_sample_rate_converter asrc(cfg); + tap::samplerate::async_sample_rate_converter asrc(cfg); const double group_delay = asrc.filter_bank().group_delay_samples(); EXPECT_NEAR(group_delay, 24.0, 0.1); // ~T/2 for balanced (T = 48) EXPECT_NEAR(asrc.designed_latency_seconds(), diff --git a/tests/test_multichannel.cpp b/tests/test_multichannel.cpp index 5ef5fae..faac741 100644 --- a/tests/test_multichannel.cpp +++ b/tests/test_multichannel.cpp @@ -41,7 +41,7 @@ namespace { return static_cast(v); } else { - return srt::detail::round_sat(v * static_cast(std::numeric_limits::max())); + return tap::samplerate::detail::round_sat(v * static_cast(std::numeric_limits::max())); } } @@ -69,9 +69,9 @@ namespace { template std::vector measure_independence(std::size_t channels, double total_seconds, double window_seconds, std::size_t chunk) { - srt::config cfg; + tap::samplerate::config cfg; cfg.channels = channels; - srt::basic_async_sample_rate_converter asrc(cfg); + tap::samplerate::basic_async_sample_rate_converter asrc(cfg); srt_test::two_clock_sim_t sim{.asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, @@ -92,7 +92,7 @@ namespace { } }); EXPECT_EQ(asrc.status().underruns, 0u); - EXPECT_EQ(asrc.status().state, srt::converter_state::locked); + EXPECT_EQ(asrc.status().state, tap::samplerate::converter_state::locked); const std::size_t frames = tail.size() / channels; std::vector x(frames); diff --git a/tests/test_polyphase.cpp b/tests/test_polyphase.cpp index a864230..9c30b92 100644 --- a/tests/test_polyphase.cpp +++ b/tests/test_polyphase.cpp @@ -12,18 +12,18 @@ namespace { constexpr double k_fs = 48000.0; TEST(Polyphase, DcGainIsUnityAcrossMu) { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); std::vector ones(bank.taps(), 1.0f); std::mt19937 rng(7); std::uniform_real_distribution uni(0.0, 1.0); for (int i = 0; i < 64; ++i) { const double mu = uni(rng); - EXPECT_NEAR(srt::interpolate(bank, ones.data(), mu), 1.0, 1e-4) << "mu=" << mu; + EXPECT_NEAR(tap::samplerate::interpolate(bank, ones.data(), mu), 1.0, 1e-4) << "mu=" << mu; } } TEST(Polyphase, ExtraRowEqualsPhaseZeroAdvancedOneTap) { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); const std::size_t L = bank.num_phases(); const std::size_t T = bank.taps(); // Rows are stored tap-reversed over an oldest-first window, so "advanced @@ -38,7 +38,7 @@ namespace { // Worst-case fractional-delay error against the analytic sine, swept over mu. // The interpolated output at fractional position mu corresponds to input time // tau = J - T/2 + mu + 1/(2L) where J is the newest sample index in the window. - double max_error_db(const srt::polyphase_filter_bank& bank, double freq_hz) { + double max_error_db(const tap::samplerate::polyphase_filter_bank& bank, double freq_hz) { const double nu = freq_hz / k_fs; const std::size_t T = bank.taps(); const double L = static_cast(bank.num_phases()); @@ -53,7 +53,7 @@ namespace { const double mu = static_cast(i) / 257.0; const double tau = static_cast(J) - static_cast(T) / 2.0 + mu + 1.0 / (2.0 * L); const double expected = std::sin(2.0 * std::numbers::pi * nu * tau); - const double err = std::abs(static_cast(srt::interpolate(bank, hist, mu)) - expected); + const double err = std::abs(static_cast(tap::samplerate::interpolate(bank, hist, mu)) - expected); max_err = std::max(max_err, err); } } @@ -61,7 +61,7 @@ namespace { } TEST(Polyphase, FractionalDelayAccuracyBalanced) { - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); // Error budget: this absolute-error sweep sees BOTH the inter-phase // interpolation floor and the prototype's in-spec passband ripple (a // gain deviation of r dB reads here as 20*log10(10^(r/20)-1): the @@ -80,7 +80,7 @@ namespace { } TEST(Polyphase, FractionalDelayAccuracyTransparent) { - const srt::polyphase_filter_bank bank(srt::filter_spec::transparent(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::transparent(), k_fs); EXPECT_LT(max_error_db(bank, 997.0), -104.0); EXPECT_LT(max_error_db(bank, 19000.0), -80.0); } @@ -88,7 +88,7 @@ namespace { TEST(Polyphase, MuWrapIsContinuousWithWindowShift) { // interpolate(hist, mu -> 1) must equal interpolate(hist shifted by one // newer sample, mu = 0): the whole-sample slip invariant. - const srt::polyphase_filter_bank bank(srt::filter_spec::balanced(), k_fs); + const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); const std::size_t T = bank.taps(); std::vector x(2 * T); std::mt19937 rng(99); @@ -98,8 +98,8 @@ namespace { } const float* hist_old = x.data(); // window ending at x[T-1] const float* hist_new = x.data() + 1; // window ending at x[T] - const float at_wrap = srt::interpolate(bank, hist_old, 1.0 - 1e-9); - const float at_zero = srt::interpolate(bank, hist_new, 0.0); + const float at_wrap = tap::samplerate::interpolate(bank, hist_old, 1.0 - 1e-9); + const float at_zero = tap::samplerate::interpolate(bank, hist_new, 0.0); EXPECT_NEAR(at_wrap, at_zero, 1e-4); } diff --git a/tests/test_servo.cpp b/tests/test_servo.cpp index 8834495..ac2af41 100644 --- a/tests/test_servo.cpp +++ b/tests/test_servo.cpp @@ -18,7 +18,7 @@ namespace { }; TEST(Servo, LocksFromConstantOffsetAndNullsError) { - srt::pi_servo servo(srt::servo_config{}, k_fs, k_target); + tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); plant plant; const double eps_true = 300e-6; bool locked_within1_5s = false; @@ -38,7 +38,7 @@ namespace { } TEST(Servo, TracksSlowDriftRampWithBoundedLag) { - srt::pi_servo servo(srt::servo_config{}, k_fs, k_target); + tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); plant plant; // Settle at 0 ppm first. for (double t = 0.0; t < 5.0; t += k_dt) { @@ -63,7 +63,7 @@ namespace { } TEST(Servo, BandwidthSwitchIsTransientFree) { - srt::pi_servo servo(srt::servo_config{}, k_fs, k_target); + tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); plant plant; const double eps_true = 200e-6; // Run until just locked. @@ -80,21 +80,21 @@ namespace { plant.step(eps_true, servo.update(plant.occ, 0.0, k_dt)); max_err = std::max(max_err, std::abs(plant.occ - k_target)); } - EXPECT_LT(max_err, srt::servo_config{}.lock_threshold_frames); + EXPECT_LT(max_err, tap::samplerate::servo_config{}.lock_threshold_frames); EXPECT_TRUE(servo.locked()); } TEST(Servo, ClampsToMaxDeviation) { - srt::servo_config cfg; + tap::samplerate::servo_config cfg; cfg.max_deviation_ppm = 100.0; - srt::pi_servo servo(cfg, k_fs, k_target); + tap::samplerate::pi_servo servo(cfg, k_fs, k_target); // Huge occupancy error must saturate at 1.5x the configured range. const double eps = servo.update(k_target + 10000.0, 0.0, k_dt); EXPECT_LE(eps, 1.5 * 100e-6 + 1e-12); } TEST(Servo, DropoutResetKeepsPpmEstimate) { - srt::pi_servo servo(srt::servo_config{}, k_fs, k_target); + tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); plant plant; const double eps_true = 250e-6; for (double t = 0.0; t < 6.0; t += k_dt) { diff --git a/tests/test_spsc_ring.cpp b/tests/test_spsc_ring.cpp index 5abdcbe..b7424ee 100644 --- a/tests/test_spsc_ring.cpp +++ b/tests/test_spsc_ring.cpp @@ -12,12 +12,12 @@ namespace { TEST(spsc_ring, CapacityRoundsUpToPowerOfTwo) { - srt::spsc_ring r(100); + tap::samplerate::spsc_ring r(100); EXPECT_EQ(r.capacity(), 128u); } TEST(spsc_ring, FillDrainExactness) { - srt::spsc_ring r(8); + tap::samplerate::spsc_ring r(8); std::vector src(8); std::iota(src.begin(), src.end(), 0); EXPECT_EQ(r.write(src.data(), 8), 8u); @@ -30,7 +30,7 @@ namespace { } TEST(spsc_ring, WrapAroundPreservesData) { - srt::spsc_ring r(16); + tap::samplerate::spsc_ring r(16); std::uint32_t seq = 0; std::uint32_t expect = 0; // Repeatedly write 5, read 5 so the indices wrap many times. @@ -49,7 +49,7 @@ namespace { } TEST(spsc_ring, DiscardAdvancesConsumer) { - srt::spsc_ring r(16); + tap::samplerate::spsc_ring r(16); int buf[10]; std::iota(buf, buf + 10, 0); ASSERT_EQ(r.write(buf, 10), 10u); @@ -62,7 +62,7 @@ namespace { } TEST(spsc_ring, PartialWriteWhenNearlyFull) { - srt::spsc_ring r(8); + tap::samplerate::spsc_ring r(8); int buf[6] = {0, 1, 2, 3, 4, 5}; ASSERT_EQ(r.write(buf, 6), 6u); EXPECT_EQ(r.write(buf, 6), 2u); // only 2 slots free diff --git a/tests/test_spsc_ring_threads.cpp b/tests/test_spsc_ring_threads.cpp index cfa1670..f968340 100644 --- a/tests/test_spsc_ring_threads.cpp +++ b/tests/test_spsc_ring_threads.cpp @@ -13,7 +13,7 @@ namespace { TEST(spsc_ring, TwoThreadStressPreservesSequence) { constexpr std::uint64_t k_total = 10'000'000; - srt::spsc_ring ring(1024); + tap::samplerate::spsc_ring ring(1024); std::thread producer([&] { std::mt19937 rng(12345); diff --git a/tools/capi/srt_capi.cpp b/tools/capi/srt_capi.cpp index 1e6d2b4..f76b00c 100644 --- a/tools/capi/srt_capi.cpp +++ b/tools/capi/srt_capi.cpp @@ -25,11 +25,11 @@ struct SrtHandle; // opaque } namespace { - srt::async_sample_rate_converter* impl(SrtHandle* h) noexcept { - return reinterpret_cast(h); + tap::samplerate::async_sample_rate_converter* impl(SrtHandle* h) noexcept { + return reinterpret_cast(h); } - const srt::async_sample_rate_converter* impl(const SrtHandle* h) noexcept { - return reinterpret_cast(h); + const tap::samplerate::async_sample_rate_converter* impl(const SrtHandle* h) noexcept { + return reinterpret_cast(h); } } // namespace // ANCHOR_END: abi_impl @@ -44,16 +44,16 @@ unsigned srt_version(void) noexcept { /// preset: 0 = fast, 1 = balanced, 2 = transparent. SrtHandle* srt_create(double sample_rate_hz, std::size_t channels, std::size_t target_latency_frames, int preset) noexcept { - srt::config cfg; + tap::samplerate::config cfg; cfg.sample_rate_hz = sample_rate_hz; cfg.channels = channels; if (target_latency_frames != 0) cfg.target_latency_frames = target_latency_frames; - cfg.filter = preset == 0 ? srt::filter_spec::fast() - : preset == 2 ? srt::filter_spec::transparent() - : srt::filter_spec::balanced(); + cfg.filter = preset == 0 ? tap::samplerate::filter_spec::fast() + : preset == 2 ? tap::samplerate::filter_spec::transparent() + : tap::samplerate::filter_spec::balanced(); try { - return reinterpret_cast(new srt::async_sample_rate_converter(cfg)); + return reinterpret_cast(new tap::samplerate::async_sample_rate_converter(cfg)); } catch (...) { return nullptr; @@ -83,7 +83,7 @@ void srt_status(const SrtHandle* h, double out[6]) noexcept { out[i] = 0.0; return; } - const srt::converter_status s = impl(h)->status(); + const tap::samplerate::converter_status s = impl(h)->status(); out[0] = static_cast(static_cast(s.state)); out[1] = s.ppm; out[2] = s.fifo_fill_frames; From b6995fa169f4b8d67783710ec29983e9a6793b8e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 15:47:18 +0000 Subject: [PATCH 3/3] Reflow clang-format alignment after the namespace rename tap::samplerate is wider than srt, which shifted clang-format's consecutive-declaration / assignment alignment columns. Reformat with the Tap-wide pinned clang-format (v18.1.3) so the pre-commit gate passes. No functional change. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MRTDgYKY38WH8Bqz17W9k1 --- bench/bench_asrc.cpp | 8 +++--- bench/compare/bench_compare.cpp | 6 ++--- bench/icount/cmp_main.cpp | 6 ++--- bench/icount/icount_main.cpp | 12 ++++----- examples/pico2_cyccnt/main.cpp | 7 ++--- examples/pico2_dualcore/main.cpp | 11 ++++---- tests/support/two_clock_sim.h | 10 ++++---- tests/test_asrc_lock.cpp | 8 +++--- tests/test_asrc_program.cpp | 10 ++++---- tests/test_asrc_quality.cpp | 4 +-- tests/test_asrc_quality_16k.cpp | 8 +++--- tests/test_fixed_point.cpp | 16 ++++++------ tests/test_hardening.cpp | 44 +++++++++++++++++--------------- tests/test_latency.cpp | 6 ++--- tests/test_multichannel.cpp | 12 ++++----- tests/test_polyphase.cpp | 23 +++++++++-------- tests/test_servo.cpp | 18 ++++++------- tests/test_spsc_ring.cpp | 10 ++++---- tests/test_spsc_ring_threads.cpp | 2 +- tools/capi/srt_capi.cpp | 12 ++++----- 20 files changed, 119 insertions(+), 114 deletions(-) diff --git a/bench/bench_asrc.cpp b/bench/bench_asrc.cpp index 4a20f6f..6c7d13e 100644 --- a/bench/bench_asrc.cpp +++ b/bench/bench_asrc.cpp @@ -37,8 +37,8 @@ namespace { template void kernelBench(benchmark::State& state, const tap::samplerate::filter_spec& spec) { const tap::samplerate::polyphase_filter_bank bank(spec, 48000.0); - const auto hist = sineBlock(bank.taps(), 997.0, 0.5); - double mu = 0.0; + const auto hist = sineBlock(bank.taps(), 997.0, 0.5); + double mu = 0.0; for (auto _ : state) { mu += 0.6180339887498949; // golden-ratio stride visits phases evenly if (mu >= 1.0) @@ -50,8 +50,8 @@ namespace { template void pipelineBench(benchmark::State& state, const tap::samplerate::filter_spec& spec, std::size_t channels) { - constexpr std::size_t kBlock = 128; - tap::samplerate::config cfg; + constexpr std::size_t kBlock = 128; + tap::samplerate::config cfg; cfg.channels = channels; cfg.filter = spec; // The FIFO setpoint must exceed the pull block size (see README latency diff --git a/bench/compare/bench_compare.cpp b/bench/compare/bench_compare.cpp index 7dc9403..bdeedd4 100644 --- a/bench/compare/bench_compare.cpp +++ b/bench/compare/bench_compare.cpp @@ -76,7 +76,7 @@ namespace { void srtBench(benchmark::State& state, const tap::samplerate::filter_spec& spec, std::size_t channels) { const tap::samplerate::polyphase_filter_bank bank(spec, 48000.0); tap::samplerate::fractional_resampler rs(bank, channels); - InputTap inFloat(48000, channels); + InputTap inFloat(48000, channels); // Requantize the shared float source once at setup for fixed-point runs. std::vector buf(48000 * channels); { @@ -86,8 +86,8 @@ namespace { if constexpr (std::is_floating_point_v) buf[i] = tmp[i]; else - buf[i] = tap::samplerate::detail::round_sat(static_cast(tmp[i]) - * static_cast(std::numeric_limits::max())); + buf[i] = tap::samplerate::detail::round_sat( + static_cast(tmp[i]) * static_cast(std::numeric_limits::max())); } } std::size_t pos = 0; diff --git a/bench/icount/cmp_main.cpp b/bench/icount/cmp_main.cpp index e85eae5..ba5c164 100644 --- a/bench/icount/cmp_main.cpp +++ b/bench/icount/cmp_main.cpp @@ -47,9 +47,9 @@ namespace { double run() { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), 48000.0); tap::samplerate::fractional_resampler rs(bank, kCh); - const auto input = sineInput(12000); // 0.25 s, cycled - std::size_t pos = 0; - const auto pop = [&](float* dst, std::size_t n) { + const auto input = sineInput(12000); // 0.25 s, cycled + std::size_t pos = 0; + const auto pop = [&](float* dst, std::size_t n) { const std::size_t avail = 12000 - pos; const std::size_t take = n < avail ? n : avail; for (std::size_t i = 0; i < take * kCh; ++i) diff --git a/bench/icount/icount_main.cpp b/bench/icount/icount_main.cpp index f40863a..b92c810 100644 --- a/bench/icount/icount_main.cpp +++ b/bench/icount/icount_main.cpp @@ -41,9 +41,9 @@ namespace { template double runKernel() { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), 48000.0); - const auto hist = sineBlock(bank.taps(), 997.0, 0.5); - double sink = 0.0; - double mu = 0.0; + const auto hist = sineBlock(bank.taps(), 997.0, 0.5); + double sink = 0.0; + double mu = 0.0; for (int i = 0; i < 200000; ++i) { mu += 0.6180339887498949; if (mu >= 1.0) @@ -59,9 +59,9 @@ namespace { template double runPipeline() { - constexpr std::size_t kCh = SRT_SC_CH; - constexpr std::size_t kBlock = 32; - tap::samplerate::config cfg; + constexpr std::size_t kCh = SRT_SC_CH; + constexpr std::size_t kBlock = 32; + tap::samplerate::config cfg; cfg.channels = kCh; tap::samplerate::basic_async_sample_rate_converter asrc(cfg); diff --git a/examples/pico2_cyccnt/main.cpp b/examples/pico2_cyccnt/main.cpp index b8b2c5f..843df66 100644 --- a/examples/pico2_cyccnt/main.cpp +++ b/examples/pico2_cyccnt/main.cpp @@ -77,7 +77,8 @@ namespace { } template - void runCase(const char* typeName, const char* presetName, const tap::samplerate::filter_spec& spec, std::size_t channels) { + void runCase(const char* typeName, const char* presetName, const tap::samplerate::filter_spec& spec, + std::size_t channels) { tap::samplerate::Config cfg; cfg.channels = channels; cfg.filter = spec; @@ -85,8 +86,8 @@ namespace { // Heap-constructed so allocation failure (e.g. 12ch + float on a tighter // build) degrades to a printed SKIP row instead of a hard fault. std::unique_ptr> asrc; - std::vector input; - std::vector out; + std::vector input; + std::vector out; try { asrc = std::make_unique>(cfg); input = sineBlock(kInputFrames * channels, 997.0, 0.5); diff --git a/examples/pico2_dualcore/main.cpp b/examples/pico2_dualcore/main.cpp index e617472..f3c4dd3 100644 --- a/examples/pico2_dualcore/main.cpp +++ b/examples/pico2_dualcore/main.cpp @@ -303,8 +303,8 @@ namespace { // frequencies (README "Measured performance"; tests/test_asrc_quality_16k.cpp). tap::samplerate::filter_spec balanced16k() { tap::samplerate::filter_spec f = tap::samplerate::filter_spec::balanced(); - f.passband_hz = 20000.0 * 16.0 / 48.0; - f.stopband_hz = 28000.0 * 16.0 / 48.0; + f.passband_hz = 20000.0 * 16.0 / 48.0; + f.stopband_hz = 28000.0 * 16.0 / 48.0; return f; } @@ -328,7 +328,8 @@ namespace { std::vector out(kInputFrames * channels); const double w = 2.0 * std::numbers::pi * 997.0 / rateHz; for (std::size_t f = 0; f < kInputFrames; ++f) { - const auto v = tap::samplerate::detail::roundSat(0.5 * std::sin(w * static_cast(f)) * 32767.0); + const auto v = + tap::samplerate::detail::roundSat(0.5 * std::sin(w * static_cast(f)) * 32767.0); for (std::size_t c = 0; c < channels; ++c) out[f * channels + c] = v; } @@ -449,9 +450,9 @@ namespace { g.stop.store(true, std::memory_order_release); while (!g.consumerDone.load(std::memory_order_acquire)) tight_loop_contents(); - const Snapshot fin = readSnapshot(); + const Snapshot fin = readSnapshot(); const tap::samplerate::converter_status st = asrc->status(); - ppmFinal = st.ppm; + ppmFinal = st.ppm; g.asrc.store(nullptr, std::memory_order_release); // PASS = the deployment-shape claims, made falsifiable: diff --git a/tests/support/two_clock_sim.h b/tests/support/two_clock_sim.h index aad23b7..489ea6a 100644 --- a/tests/support/two_clock_sim.h +++ b/tests/support/two_clock_sim.h @@ -17,11 +17,11 @@ namespace srt_test { template struct two_clock_sim_t { tap::samplerate::basic_async_sample_rate_converter& asrc; - double fs_in; ///< input-domain event rate (true input sample rate) - double fs_out; ///< output-domain event rate (true output sample rate) - std::size_t channels = 1; - std::size_t chunk_in = 32; ///< frames pushed per producer event - std::size_t chunk_out = 32; ///< frames pulled per consumer event + double fs_in; ///< input-domain event rate (true input sample rate) + double fs_out; ///< output-domain event rate (true output sample rate) + std::size_t channels = 1; + std::size_t chunk_in = 32; ///< frames pushed per producer event + std::size_t chunk_out = 32; ///< frames pulled per consumer event /// Input signal generator: value at input sample index i (all channels). std::function gen = [](std::uint64_t) { return S{}; }; /// Per-channel generator (sample index, channel); overrides gen when set. diff --git a/tests/test_asrc_lock.cpp b/tests/test_asrc_lock.cpp index 5b5da22..eab1bc9 100644 --- a/tests/test_asrc_lock.cpp +++ b/tests/test_asrc_lock.cpp @@ -52,8 +52,8 @@ namespace { TEST(AsrcLock, TracksDriftRampWithoutUnlocking) { tap::samplerate::async_sample_rate_converter asrc(mono_config()); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = k_fs, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = k_fs, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; // Input clock drifts 0 -> +300 ppm over 30 s (10 ppm/s, far faster than // real oscillator wander), then holds for the loop to reconverge. sim.fs_in_scale = [](double t) { return 1.0 + 300e-6 * std::min(t, 30.0) / 30.0; }; @@ -81,8 +81,8 @@ namespace { // sine's second difference is bounded by A*omega^2; any window-shift // discontinuity would blow far past that bound. tap::samplerate::async_sample_rate_converter asrc(mono_config()); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double amp = 0.5; const double nu = 1000.0 / k_fs; sim.gen = [&](std::uint64_t i) { diff --git a/tests/test_asrc_program.cpp b/tests/test_asrc_program.cpp index 0724685..f7d4b3e 100644 --- a/tests/test_asrc_program.cpp +++ b/tests/test_asrc_program.cpp @@ -28,9 +28,9 @@ namespace { cfg.channels = 1; cfg.filter = spec; tap::samplerate::async_sample_rate_converter asrc(cfg); - const double fs_in = k_fs * (1.0 + k_eps); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = fs_in, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + const double fs_in = k_fs * (1.0 + k_eps); + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = fs_in, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const auto comb = srt_test::tone_comb::pink(24, 60.0, 16000.0, 0.9); sim.gen = [&](std::uint64_t i) { return static_cast(comb.sample_at(i, fs_in)); }; std::vector tail; @@ -57,8 +57,8 @@ namespace { cfg.channels = 1; cfg.filter = spec; tap::samplerate::async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; sim.gen = [&](std::uint64_t i) { return static_cast(0.5 * std::sin(2.0 * std::numbers::pi * nu_in * static_cast(i))); diff --git a/tests/test_asrc_quality.cpp b/tests/test_asrc_quality.cpp index fab4c86..6964ead 100644 --- a/tests/test_asrc_quality.cpp +++ b/tests/test_asrc_quality.cpp @@ -24,8 +24,8 @@ namespace { cfg.channels = 1; cfg.filter = spec; tap::samplerate::async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; sim.gen = [&](std::uint64_t i) { return static_cast(k_amp * std::sin(2.0 * std::numbers::pi * nu_in * static_cast(i))); diff --git a/tests/test_asrc_quality_16k.cpp b/tests/test_asrc_quality_16k.cpp index 74bd83c..e3759c3 100644 --- a/tests/test_asrc_quality_16k.cpp +++ b/tests/test_asrc_quality_16k.cpp @@ -42,10 +42,10 @@ namespace { // hold times). double measure_snr_db16k(double freq_hz) { tap::samplerate::config cfg = tap::samplerate::config::for_sample_rate(k_fs); - cfg.channels = 1; + cfg.channels = 1; tap::samplerate::async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; sim.gen = [&](std::uint64_t i) { return static_cast(k_amp * std::sin(2.0 * std::numbers::pi * nu_in * static_cast(i))); @@ -87,7 +87,7 @@ namespace { TEST(AsrcQuality16k, ForSampleRateScalesHzFieldsOnly) { const tap::samplerate::config c = tap::samplerate::config::for_sample_rate(16000.0); const tap::samplerate::config d; // 48 kHz defaults - const double r = 16000.0 / 48000.0; + const double r = 16000.0 / 48000.0; EXPECT_DOUBLE_EQ(c.sample_rate_hz, 16000.0); EXPECT_DOUBLE_EQ(c.filter.passband_hz, d.filter.passband_hz * r); EXPECT_DOUBLE_EQ(c.filter.stopband_hz, d.filter.stopband_hz * r); diff --git a/tests/test_fixed_point.cpp b/tests/test_fixed_point.cpp index 7d03af9..d91a080 100644 --- a/tests/test_fixed_point.cpp +++ b/tests/test_fixed_point.cpp @@ -43,7 +43,7 @@ namespace { // a 256-point mu sweep for both formats (tolerance 1 for safety only). TEST(FixedPoint, DcGainIsUnityQ15) { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); - std::vector dc(bank.taps(), 32767); + std::vector dc(bank.taps(), 32767); for (int i = 0; i < 16; ++i) { const double mu = static_cast(i) / 16.0; EXPECT_NEAR(tap::samplerate::interpolate(bank, dc.data(), mu), 32767, 1) << "mu=" << mu; @@ -52,7 +52,7 @@ namespace { TEST(FixedPoint, DcGainIsUnityQ31) { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); - std::vector dc(bank.taps(), 2147483647); + std::vector dc(bank.taps(), 2147483647); for (int i = 0; i < 16; ++i) { const double mu = static_cast(i) / 16.0; EXPECT_NEAR(tap::samplerate::interpolate(bank, dc.data(), mu), 2147483647.0, 1.0) << "mu=" << mu; @@ -64,7 +64,7 @@ namespace { template void check_row_sums_exact() { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); - const auto scale = static_cast(tap::samplerate::sample_traits::k_coeff_scale); + const auto scale = static_cast(tap::samplerate::sample_traits::k_coeff_scale); for (std::size_t p = 0; p < bank.num_phases(); ++p) { std::int64_t sum = 0; for (std::size_t t = 0; t < bank.taps(); ++t) { @@ -87,8 +87,8 @@ namespace { tap::samplerate::config cfg; cfg.channels = 1; tap::samplerate::basic_async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim_t sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim_t sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + k_eps), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu_in = freq_hz / k_fs; const double full_scale = static_cast(std::numeric_limits::max()); sim.gen = [&](std::uint64_t i) { @@ -135,9 +135,9 @@ namespace { // second difference at the analytic bound for a clean sine. tap::samplerate::config cfg; cfg.channels = 1; - tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); - srt_test::two_clock_sim_t sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); + srt_test::two_clock_sim_t sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const double nu = 1000.0 / k_fs; sim.gen = [&](std::uint64_t i) { return tap::samplerate::detail::round_sat( diff --git a/tests/test_hardening.cpp b/tests/test_hardening.cpp index e84c6f5..3b88618 100644 --- a/tests/test_hardening.cpp +++ b/tests/test_hardening.cpp @@ -38,12 +38,12 @@ namespace { cfg.servo.unlock_threshold_frames = static_cast(pull_block) * 1.5; } tap::samplerate::async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim sim{.asrc = asrc, - .fs_in = k_fs * (1.0 + 200e-6), - .fs_out = k_fs, - .channels = 1, - .chunk_in = 32, - .chunk_out = pull_block}; + srt_test::two_clock_sim sim{.asrc = asrc, + .fs_in = k_fs * (1.0 + 200e-6), + .fs_out = k_fs, + .channels = 1, + .chunk_in = 32, + .chunk_out = pull_block}; sim.gen = [](std::uint64_t i) { return static_cast(0.5 * std::sin(0.13 * static_cast(i))); }; // Coarse blocks keep the servo in Track, where instantaneous ppm swings // with the block-beat FM — average it, as the 48 kHz lock test does. @@ -114,8 +114,10 @@ namespace { } // The rate-scaling factory sits exactly on the band-edge sum boundary // (passband + stopband == fs up to rounding); it must keep constructing. - EXPECT_NO_THROW(tap::samplerate::async_sample_rate_converter{tap::samplerate::config::for_sample_rate(16000.0)}); - EXPECT_NO_THROW(tap::samplerate::async_sample_rate_converter{tap::samplerate::config::for_sample_rate(44100.0)}); + EXPECT_NO_THROW( + tap::samplerate::async_sample_rate_converter{tap::samplerate::config::for_sample_rate(16000.0)}); + EXPECT_NO_THROW( + tap::samplerate::async_sample_rate_converter{tap::samplerate::config::for_sample_rate(44100.0)}); } // Audit finding F3: with a setpoint below the resampler's staged-scratch @@ -126,8 +128,8 @@ namespace { cfg.channels = 1; cfg.target_latency_frames = 4; tap::samplerate::async_sample_rate_converter asrc(cfg); - std::vector in(32, 0.25f); - std::vector out(64); + std::vector in(32, 0.25f); + std::vector out(64); for (int i = 0; i < 8; ++i) { // reach steady operation asrc.push(in.data(), 32), asrc.pull(out.data(), 32); } @@ -162,8 +164,8 @@ namespace { tap::samplerate::config cfg; cfg.channels = 2; tap::samplerate::async_sample_rate_converter asrc(cfg); - std::vector in(2 * 4096, 0.1f); - std::vector out(2 * 8192); + std::vector in(2 * 4096, 0.1f); + std::vector out(2 * 8192); EXPECT_EQ(asrc.push(in.data(), 0), 0u); EXPECT_EQ(asrc.pull(out.data(), 0), 0u); for (int i = 0; i < 64; ++i) { @@ -184,9 +186,9 @@ namespace { tap::samplerate::config cfg; cfg.channels = 1; tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); - std::vector in(32, 16384); - std::vector out(32); - std::vector made; + std::vector in(32, 16384); + std::vector out(32); + std::vector made; for (int it = 0; it < 400 && made.size() < 200; ++it) { asrc.push(in.data(), in.size()); const std::size_t n = asrc.pull(out.data(), out.size()); @@ -208,9 +210,9 @@ namespace { TEST(QuickQuality, Q15Tone997) { tap::samplerate::config cfg; cfg.channels = 1; - tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); - srt_test::two_clock_sim_t sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 8, .chunk_out = 8}; + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); + srt_test::two_clock_sim_t sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + 200e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 8, .chunk_out = 8}; const double nu = 997.0 / k_fs; sim.gen = [&](std::uint64_t i) { return tap::samplerate::detail::round_sat( @@ -239,9 +241,9 @@ namespace { // wide-MAC (SMLALD) target previously never saw near-full-scale input. tap::samplerate::config cfg; cfg.channels = 1; - tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); - srt_test::two_clock_sim_t sim{ - .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 8, .chunk_out = 8}; + tap::samplerate::async_sample_rate_converter_q15 asrc(cfg); + srt_test::two_clock_sim_t sim{ + .asrc = asrc, .fs_in = k_fs * (1.0 + 500e-6), .fs_out = k_fs, .channels = 1, .chunk_in = 8, .chunk_out = 8}; const double nu = 1000.0 / k_fs; sim.gen = [&](std::uint64_t i) { return tap::samplerate::detail::round_sat( diff --git a/tests/test_latency.cpp b/tests/test_latency.cpp index 3bc050f..7f9f2fc 100644 --- a/tests/test_latency.cpp +++ b/tests/test_latency.cpp @@ -14,8 +14,8 @@ namespace { tap::samplerate::config cfg; cfg.channels = 1; tap::samplerate::async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim sim{ - .asrc = asrc, .fs_in = k_fs, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; + srt_test::two_clock_sim sim{ + .asrc = asrc, .fs_in = k_fs, .fs_out = k_fs, .channels = 1, .chunk_in = 1, .chunk_out = 1}; const std::uint64_t impulse_index = 24000; // 0.5 s in, well past acquisition sim.gen = [&](std::uint64_t i) { return i == impulse_index ? 1.0f : 0.0f; }; std::vector out; @@ -46,7 +46,7 @@ namespace { tap::samplerate::config cfg; cfg.channels = 1; tap::samplerate::async_sample_rate_converter asrc(cfg); - const double group_delay = asrc.filter_bank().group_delay_samples(); + const double group_delay = asrc.filter_bank().group_delay_samples(); EXPECT_NEAR(group_delay, 24.0, 0.1); // ~T/2 for balanced (T = 48) EXPECT_NEAR(asrc.designed_latency_seconds(), (static_cast(cfg.target_latency_frames) + group_delay) / k_fs, 1e-12); diff --git a/tests/test_multichannel.cpp b/tests/test_multichannel.cpp index faac741..bcd6edb 100644 --- a/tests/test_multichannel.cpp +++ b/tests/test_multichannel.cpp @@ -72,12 +72,12 @@ namespace { tap::samplerate::config cfg; cfg.channels = channels; tap::samplerate::basic_async_sample_rate_converter asrc(cfg); - srt_test::two_clock_sim_t sim{.asrc = asrc, - .fs_in = k_fs * (1.0 + k_eps), - .fs_out = k_fs, - .channels = channels, - .chunk_in = chunk, - .chunk_out = chunk}; + srt_test::two_clock_sim_t sim{.asrc = asrc, + .fs_in = k_fs * (1.0 + k_eps), + .fs_out = k_fs, + .channels = channels, + .chunk_in = chunk, + .chunk_out = chunk}; sim.gen_ch = [&](std::uint64_t i, std::size_t c) { const double w = 2.0 * std::numbers::pi * channel_freq_hz(c) / k_fs; // Per-channel phase offsets decorrelate the channel waveforms. diff --git a/tests/test_polyphase.cpp b/tests/test_polyphase.cpp index 9c30b92..fe415e7 100644 --- a/tests/test_polyphase.cpp +++ b/tests/test_polyphase.cpp @@ -13,9 +13,9 @@ namespace { TEST(Polyphase, DcGainIsUnityAcrossMu) { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); - std::vector ones(bank.taps(), 1.0f); - std::mt19937 rng(7); - std::uniform_real_distribution uni(0.0, 1.0); + std::vector ones(bank.taps(), 1.0f); + std::mt19937 rng(7); + std::uniform_real_distribution uni(0.0, 1.0); for (int i = 0; i < 64; ++i) { const double mu = uni(rng); EXPECT_NEAR(tap::samplerate::interpolate(bank, ones.data(), mu), 1.0, 1e-4) << "mu=" << mu; @@ -24,8 +24,8 @@ namespace { TEST(Polyphase, ExtraRowEqualsPhaseZeroAdvancedOneTap) { const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); - const std::size_t L = bank.num_phases(); - const std::size_t T = bank.taps(); + const std::size_t L = bank.num_phases(); + const std::size_t T = bank.taps(); // Rows are stored tap-reversed over an oldest-first window, so "advanced // by one input sample" means row L shifted one slot toward newer samples: // phase(L)[u] == phase(0)[u-1], with the oldest slot of row L zero. @@ -53,8 +53,9 @@ namespace { const double mu = static_cast(i) / 257.0; const double tau = static_cast(J) - static_cast(T) / 2.0 + mu + 1.0 / (2.0 * L); const double expected = std::sin(2.0 * std::numbers::pi * nu * tau); - const double err = std::abs(static_cast(tap::samplerate::interpolate(bank, hist, mu)) - expected); - max_err = std::max(max_err, err); + const double err = + std::abs(static_cast(tap::samplerate::interpolate(bank, hist, mu)) - expected); + max_err = std::max(max_err, err); } } return 20.0 * std::log10(max_err); @@ -89,10 +90,10 @@ namespace { // interpolate(hist, mu -> 1) must equal interpolate(hist shifted by one // newer sample, mu = 0): the whole-sample slip invariant. const tap::samplerate::polyphase_filter_bank bank(tap::samplerate::filter_spec::balanced(), k_fs); - const std::size_t T = bank.taps(); - std::vector x(2 * T); - std::mt19937 rng(99); - std::uniform_real_distribution uni(-1.0f, 1.0f); + const std::size_t T = bank.taps(); + std::vector x(2 * T); + std::mt19937 rng(99); + std::uniform_real_distribution uni(-1.0f, 1.0f); for (auto& v : x) { v = uni(rng); } diff --git a/tests/test_servo.cpp b/tests/test_servo.cpp index ac2af41..686e394 100644 --- a/tests/test_servo.cpp +++ b/tests/test_servo.cpp @@ -19,10 +19,10 @@ namespace { TEST(Servo, LocksFromConstantOffsetAndNullsError) { tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); - plant plant; - const double eps_true = 300e-6; - bool locked_within1_5s = false; - double t = 0.0; + plant plant; + const double eps_true = 300e-6; + bool locked_within1_5s = false; + double t = 0.0; for (; t < 30.0; t += k_dt) { // locked loop is 0.05 Hz: allow it to settle const double eps = servo.update(plant.occ, 0.0, k_dt); plant.step(eps_true, eps); @@ -39,7 +39,7 @@ namespace { TEST(Servo, TracksSlowDriftRampWithBoundedLag) { tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); - plant plant; + plant plant; // Settle at 0 ppm first. for (double t = 0.0; t < 5.0; t += k_dt) { plant.step(0.0, servo.update(plant.occ, 0.0, k_dt)); @@ -64,8 +64,8 @@ namespace { TEST(Servo, BandwidthSwitchIsTransientFree) { tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); - plant plant; - const double eps_true = 200e-6; + plant plant; + const double eps_true = 200e-6; // Run until just locked. double t = 0.0; while (!servo.locked() && t < 5.0) { @@ -95,8 +95,8 @@ namespace { TEST(Servo, DropoutResetKeepsPpmEstimate) { tap::samplerate::pi_servo servo(tap::samplerate::servo_config{}, k_fs, k_target); - plant plant; - const double eps_true = 250e-6; + plant plant; + const double eps_true = 250e-6; for (double t = 0.0; t < 6.0; t += k_dt) { plant.step(eps_true, servo.update(plant.occ, 0.0, k_dt)); } diff --git a/tests/test_spsc_ring.cpp b/tests/test_spsc_ring.cpp index b7424ee..39e0bad 100644 --- a/tests/test_spsc_ring.cpp +++ b/tests/test_spsc_ring.cpp @@ -18,7 +18,7 @@ namespace { TEST(spsc_ring, FillDrainExactness) { tap::samplerate::spsc_ring r(8); - std::vector src(8); + std::vector src(8); std::iota(src.begin(), src.end(), 0); EXPECT_EQ(r.write(src.data(), 8), 8u); EXPECT_EQ(r.write(src.data(), 1), 0u); // full @@ -31,8 +31,8 @@ namespace { TEST(spsc_ring, WrapAroundPreservesData) { tap::samplerate::spsc_ring r(16); - std::uint32_t seq = 0; - std::uint32_t expect = 0; + std::uint32_t seq = 0; + std::uint32_t expect = 0; // Repeatedly write 5, read 5 so the indices wrap many times. for (int round = 0; round < 100; ++round) { std::uint32_t buf[5]; @@ -50,7 +50,7 @@ namespace { TEST(spsc_ring, DiscardAdvancesConsumer) { tap::samplerate::spsc_ring r(16); - int buf[10]; + int buf[10]; std::iota(buf, buf + 10, 0); ASSERT_EQ(r.write(buf, 10), 10u); EXPECT_EQ(r.discard(4), 4u); @@ -63,7 +63,7 @@ namespace { TEST(spsc_ring, PartialWriteWhenNearlyFull) { tap::samplerate::spsc_ring r(8); - int buf[6] = {0, 1, 2, 3, 4, 5}; + int buf[6] = {0, 1, 2, 3, 4, 5}; ASSERT_EQ(r.write(buf, 6), 6u); EXPECT_EQ(r.write(buf, 6), 2u); // only 2 slots free EXPECT_EQ(r.read_available(), 8u); diff --git a/tests/test_spsc_ring_threads.cpp b/tests/test_spsc_ring_threads.cpp index f968340..9d8ac56 100644 --- a/tests/test_spsc_ring_threads.cpp +++ b/tests/test_spsc_ring_threads.cpp @@ -12,7 +12,7 @@ namespace { TEST(spsc_ring, TwoThreadStressPreservesSequence) { - constexpr std::uint64_t k_total = 10'000'000; + constexpr std::uint64_t k_total = 10'000'000; tap::samplerate::spsc_ring ring(1024); std::thread producer([&] { diff --git a/tools/capi/srt_capi.cpp b/tools/capi/srt_capi.cpp index f76b00c..012d61e 100644 --- a/tools/capi/srt_capi.cpp +++ b/tools/capi/srt_capi.cpp @@ -84,12 +84,12 @@ void srt_status(const SrtHandle* h, double out[6]) noexcept { return; } const tap::samplerate::converter_status s = impl(h)->status(); - out[0] = static_cast(static_cast(s.state)); - out[1] = s.ppm; - out[2] = s.fifo_fill_frames; - out[3] = static_cast(s.underruns); - out[4] = static_cast(s.overruns); - out[5] = static_cast(s.resyncs); + out[0] = static_cast(static_cast(s.state)); + out[1] = s.ppm; + out[2] = s.fifo_fill_frames; + out[3] = static_cast(s.underruns); + out[4] = static_cast(s.overruns); + out[5] = static_cast(s.resyncs); } double srt_designed_latency_seconds(const SrtHandle* h) noexcept {