audio-resample-bsd 0.2.1

RT-safe audio resampling crate (rubato-based) — the only processing interface callable directly from the real-time audio thread
Documentation
# audio-resample-bsd

[![License: BSD-2-Clause](https://img.shields.io/badge/license-BSD--2--Clause-blue.svg)](./LICENSE)
[![Crates.io](https://img.shields.io/crates/v/audio-resample-bsd.svg)](https://crates.io/crates/audio-resample-bsd)

RT-safe audio resampling (rubato-based) — defines the **only processing
interface that may be called directly from the real-time (RT) audio thread**.

> **Note:** The doc comments on each public item are the primary reference.
> This README is an overview only.

## Role

A sample-rate conversion layer for real-time audio graphs. Codecs, DSP
primitives, and graph nodes all perform sample-rate conversion
(44.1k <-> 48k <-> 96k) through the [`Resampler`] trait defined here. The
rubato 4.0-backed implementation `RubatoResampler` satisfies that contract.

## Dependencies

| Dependency | Version | Purpose |
|------------|---------|---------|
| `audio-core-bsd` | 0.1.0 (path dep) | shared types (`AudioFrame`, `AudioError`) — planar layout compatibility |
| `rubato` | 4 (`Fft` synchronous, `FixedSync::Input`) | RT-safe `process_into_buffer` — zero-copy via `SequentialSlice` |
| `thiserror` | 2.0 | `ResampleError` derive |

> rubato 4.0 documents an allocation-free processing path and is the only DSP
> crate in this stack with an explicit RT-safety statement.

## Data layout — planar flat

Input and output are flat `&[f32]` buffers in **planar** layout:
`[ch0_frames..., ch1_frames...]`. This matches `audio_core_bsd::AudioFrame::samples`
exactly, so an `AudioFrame` can be passed directly with no extra copy or
rearrangement. The channel count is fixed when the implementation is constructed.

## Real-time safety boundary

Only [`Resampler::process_into_buffer`] may be called from the RT thread. On
this path, heap allocation / locking / panicking / system calls are all
forbidden (same principle as the `audio-core-bsd` `AudioNode` RT contract).
Every buffer is pre-allocated at construction.

[`Resampler::set_rate`] is **not RT-safe** — it rebuilds the inner resampler
(allocating), so it must be called from a configuration/worker thread, only
between audio cycles. The RT-alloc=0 guarantee applies only to the
`process_into_buffer` path.

## Verification

| Gate | Criterion | Measured |
|------|-----------|----------|
| **Audio Quality** (SNR) | round-trip 44.1k <-> 48k <-> 96k **SNR >= 90 dB** | round-trip **125-131 dB** (`tests/resample_snr.rs`) |
| **RT-safety** (alloc) | `process_into_buffer` **allocation == 0** | **alloc = 0** (`tests/rt_alloc_free.rs`, `CountingAllocator`) |
| **Property** | broad configs: no panic, finite output, bounded delay | 3 proptests pass (`tests/resample_property.rs`) |
| **Integration** | `AudioFrame` (planar) passed directly, zero-copy | 3 integration tests pass (`tests/audio_frame_integration.rs`) |
| **FreeBSD target** | `cargo check --target x86_64-unknown-freebsd` | **0 errors** (all-targets); native FreeBSD CI via `.github/workflows/freebsd-matrix.yml` |

```bash
cargo test         # 36 tests (22 unit + 3 integration + 3 property + 4 SNR + 4 RT-alloc)
cargo bench        # criterion — RT-path throughput / latency regression watch
cargo run --example resample_441_to_48k
```

## Status

**0.x — experimental.** The public API (`Resampler` / `ResampleError` /
`RubatoResampler`) is not yet frozen. Breaking changes are expected before a
1.0 release.

- edition: 2021
- MSRV: 1.85
- license: BSD-2-Clause

## License

BSD-2-Clause. See [`LICENSE`](./LICENSE).