rustriff_lib/services/round_trip_latency_session/mod.rs
1//! Round-trip latency measurement using dedicated CPAL streams.
2//!
3//! # Overview
4//!
5//! Round-trip latency is the total time a sound takes to travel from the software output,
6//! through the DAC, over the physical audio cable (or internal loopback), back in through
7//! the ADC, and arrive at the input ring buffer where the software can read it.
8//!
9//! This module measures that latency by:
10//!
11//! 1. **Opening dedicated streams** — a private input/output pair, completely separate from
12//! the main loopback in [`AudioService`]. The main engine is unaffected and can stay
13//! running during the measurement.
14//! 2. **Warming up** — sleeping for `stream_warmup` after stream creation so the OS audio
15//! stack and hardware buffers reach a stable steady state before any timing begins.
16//! 3. **Calibrating the noise floor** — collecting [`CALIBRATION_SAMPLES`] silent ambient
17//! samples from the input to measure the peak background noise. The detection threshold
18//! is derived from that peak so a genuine echo can be distinguished from ambient noise.
19//! 4. **Emitting impulses** — writing a single full-amplitude sample ([`IMPULSE_AMPLITUDE`])
20//! to the output ring buffer while recording the exact wall-clock time.
21//! 5. **Detecting the echo** — reading incoming input samples and waiting for the first
22//! sample that crosses the derived threshold. A guard window of [`GUARD_SAMPLES`] is
23//! skipped immediately after the impulse to avoid detecting the outgoing signal itself
24//! or very early electrical bleed-through.
25//! 6. **Averaging** — repeating the impulse/echo cycle [`IMPULSE_COUNT`] times and
26//! returning the mean of all measured round-trip durations to smooth out callback jitter.
27//!
28//! # Physical setup requirement
29//!
30//! The output of the audio interface must be physically (or virtually) connected back to its
31//! input for the echo to be heard. On a typical guitar-amp setup this is the instrument
32//! cable path: software → DAC → speaker/DI → microphone/DI → ADC → software. On a
33//! development machine a virtual loopback driver (e.g. VB-Audio Cable) can be used instead.
34//!
35//! # Isolation from the main audio engine
36//!
37//! [`RoundTripLatencySession::run`] is a **blocking, self-contained call**. It opens its
38//! own CPAL streams via the same [`AudioHandlerTrait`] the main engine uses, but does **not**
39//! share ring buffers, threads, or any state with [`AudioService`]. The caller
40//! (`measure_round_trip_latency` command) releases the `Mutex<AudioService>` lock before
41//! spawning the measurement thread, so the UI and all other commands remain fully responsive
42//! during the several-second measurement window.
43//!
44//! # Module layout
45//!
46//! | File | Contents |
47//! |---|---|
48//! | `constants.rs` | All tuning constants (`CALIBRATION_SAMPLES`, `IMPULSE_COUNT`, etc.) |
49//! | `measurement_state.rs` | `RoundTripTickOutcome`, `RoundTripMeasurementPhase`, `RoundTripMeasurementState` + unit tests |
50//! | `session.rs` | `RoundTripLatencySession::run` — stream lifecycle and measurement loop |
51//!
52//! [`AudioService`]: crate::services::audio_service::AudioService
53//! [`AudioHandlerTrait`]: crate::infrastructure::audio_handler::AudioHandlerTrait
54//! [`CALIBRATION_SAMPLES`]: constants::CALIBRATION_SAMPLES
55//! [`IMPULSE_AMPLITUDE`]: constants::IMPULSE_AMPLITUDE
56//! [`GUARD_SAMPLES`]: constants::GUARD_SAMPLES
57//! [`IMPULSE_COUNT`]: constants::IMPULSE_COUNT
58
59pub mod constants;
60pub mod measurement_state;
61pub mod session;
62
63// Re-export the public surface so callers can use the old import paths unchanged.
64pub use constants::IMPULSE_AMPLITUDE;
65pub use measurement_state::{
66 RoundTripMeasurementPhase, RoundTripMeasurementState, RoundTripTickOutcome,
67};
68pub use session::RoundTripLatencySession;