Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,62 @@ the float path; Q15's floor is the 16-bit format itself. The servo and the
filter design always run in double (control path / one-time init, a handful
of operations per block).

## Position in the Tap family

SampleRateTap is one of two rate converters in the **Tap** family, both
built on the same shared substrate:

```
┌────────────────────────────┐
│ DspTap │ shared substrate (submodule)
│ kaiser design · sample │
│ traits (float/Q15/Q31) · │
│ FIR dot kernels · row-sum │
│ quantization · analysis │
└──────┬──────────────┬──────┘
│ │
┌────────────┴───┐ ┌──────┴─────────┐
│ SampleRateTap │ │ RatioTap │
│ async, near- │ │ sync, 44.1↔48, │
│ unity, servo │ │ speed-first │
└────────────┬───┘ └──────┬─────────┘
│ │
└──── test-only│dependency:
golden cross-validation
```

[DspTap](https://github.com/tap/DspTap) (vendored at `submodules/dsptap`)
provides the Kaiser prototype design, the float/Q15/Q31 sample-format
traits, the measured FIR dot-product kernels, the row-sum quantization,
and the analysis instruments shared by the tests and notebooks.
[RatioTap](https://github.com/tap/RatioTap) is the synchronous sibling:
exactly one rational ratio pair (160/147 up, 147/160 down — 44.1 ↔ 48 kHz),
one clock, speed-first.

Which converter you need is a property of the **clock topology**, never
inferred from a float ratio:

- Same nominal rate on both sides, independent oscillators (ppm drift) —
this library.
- 44.1 ↔ 48 kHz on one clock (file conversion, a single interface) —
RatioTap.
- 44.1 ↔ 48 kHz across *independent* oscillators (a Bluetooth chip on its
own crystal) — both, composed: RatioTap converts the *number*, this
library absorbs the *clock*. RatioTap's `bluetooth_bridge` example is
the documented recipe (+200 ppm crystal, servo locked, 997 Hz recovered
exactly, 2.0 ms total latency).

The two converters check each other: RatioTap's suite cross-validates its
output against this library's async engine at −109 dB (down) / −99 dB (up)
over every polyphase phase.

## Limitations

- Near-unity ratios only (±`max_deviation_ppm`, default 1000 ppm). No
44.1 ↔ 48 kHz conversion.
44.1 ↔ 48 kHz conversion — that job belongs to
[RatioTap](https://github.com/tap/RatioTap), and 44.1 ↔ 48 across
independent clocks to the composition of the two (see
[Position in the Tap family](#position-in-the-tap-family)).
- The rate estimate is derived from FIFO counts only. With block-quantized
transfer its instantaneous value wobbles at the block-beat frequency
(see `converter_status.ppm` vs. a few seconds of averaging), and ultra-quiet servo
Expand Down
7 changes: 6 additions & 1 deletion book/src/part0/two-crystals.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,12 @@ chips solve. It handles *only* the near-unity case: two domains at
nominally the same rate, within ±1000 ppm by default. It will never
convert 44.1 kHz to 48 kHz — the README lists this first among its
limitations, and `docs/COMPARISON.md` is blunt that for genuine rate
*conversion* you should put soxr or libsamplerate in the chain.
*conversion* you should put a synchronous resampler in the chain: soxr,
libsamplerate, or — for exactly the 44.1 ↔ 48 pair, built on the same
DspTap substrate as this library and cross-validated against its engine —
the family's own [RatioTap](https://github.com/tap/RatioTap). Part V
returns to that composition and its decision rule: RatioTap converts the
*number*, the ASRC absorbs the *clock*.

Here is what the restriction buys. A general-ratio converter must be able
to place output samples anywhere relative to input samples, at any
Expand Down
45 changes: 45 additions & 0 deletions book/src/part5/scaling.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,44 @@ samples*, which is three times as many milliseconds at 16 kHz (1.5 ms vs
slower — the 16 kHz test runs 120 s where the 48 kHz one ran 40 s, the
same number of samples and of time constants.

### A different rate on each side belongs to RatioTap

Everything above scales the *common* nominal rate; it does not license
44.1 ↔ 48. With the default configuration the servo clamps its estimate
at ±1.5 × `max_deviation_ppm` — ±1500 ppm against an 8.8% gap — and the
near-unity datapath has none of the band-limiting machinery a genuine
downward conversion needs. That conversion is a different problem —
synchronous, rational, known at compile time — and in the Tap family it
has a different engine: [RatioTap](https://github.com/tap/RatioTap),
built on the same DspTap substrate this book has been reading (the same
Kaiser design path, sample traits, and dot kernels), with its entire
optimization budget spent on exactly one ratio pair, 160/147 up and
147/160 down.

The decision rule is worth stating the way RatioTap's README states it,
because the failure mode is choosing an engine from a float: **which
converter applies is a property of the clock topology, never inferred
from the ratio.** A 44.1 kHz file rendered for a 48 kHz interface is
RatioTap alone — one clock, a number to convert. Two 48 kHz devices on
separate crystals is this library alone — one nominal rate, a clock to
absorb. A Bluetooth chip running 44.1 kHz on its own crystal into a
48 kHz host is both, composed: RatioTap converts the *number*, the ASRC
absorbs the *clock*.

The composition is not hypothetical, and it is measured. RatioTap's
`bluetooth_bridge` example is the documented recipe — +200 ppm on the
simulated crystal, servo locked, 997 Hz recovered exactly, 2.0 ms of
total latency through both stages — and the two engines check each
other: RatioTap's suite pins its output against this library's async
engine at −109 dB (down) / −99 dB (up) over every polyphase phase. Two
independent implementations of the same mathematics agreeing at the
noise floor is the strongest mutual check either repository runs.

The working code lives in RatioTap's repository, which is why this
section is prose rather than a code walk: the book's include-anchor
contract quotes only from this tree, and the recipe is RatioTap's to
keep honest. What this book owes you is the decision rule above.

## Blocks: feasibility, then observability

The block-size axis has two boundaries, one hard and one
Expand Down Expand Up @@ -308,6 +346,13 @@ jupyter nbconvert --execute notebooks/asrc_block_size_study.ipynb
# adaptive raise reports itself in effective_target_latency_frames instead
# of dropping out:
./build/examples/drifting_clocks

# The 44.1↔48 composition (RatioTap converts the number, this library
# absorbs the clock) runs from the sibling repository:
git clone --recurse-submodules https://github.com/tap/RatioTap
cmake -S RatioTap -B rt-build -DCMAKE_BUILD_TYPE=Release
cmake --build rt-build --target bluetooth_bridge -j 4
./rt-build/examples/bluetooth_bridge
```

The break-it-on-purpose suggestions are, as ever, the chapter in
Expand Down
5 changes: 4 additions & 1 deletion docs/COMPARISON.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ libsamplerate has no fixed-point path, so its cheapest option on such parts cost
beyond; SampleRateTap deliberately handles only clock *drift* around a
common nominal rate — that restriction is what buys the 48-tap datapath,
0.5 ms filter delay, and embedded-class compute. For genuine rate
*conversion*, put soxr/libsamplerate in the chain.
*conversion*, put a synchronous resampler in the chain —
soxr/libsamplerate, or for exactly 44.1↔48 the family's own
[RatioTap](https://github.com/tap/RatioTap), which cross-validates its
output against this library's engine.
- **Coarse-block operation is a different regime** (cent-scale low-rate FM
over a 53–61 dB floor — measured in
[the block-size study](../notebooks/asrc_block_size_study.ipynb)); the
Expand Down
Loading