Skip to main content

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;