Skip to main content

aria_kit/audio/
mod.rs

1//! Audio capture: the sources, the recordings they write, and the canonical
2//! 16 kHz mono format shared by everything that reads them. A [`Clip`] is
3//! finished audio at its own rate.
4//!
5//! [`RecordingReader`] builds on every target. Everything that opens a
6//! microphone, a model, or a file is native.
7
8mod clip;
9mod recording;
10mod stop;
11
12#[cfg(not(target_family = "wasm"))]
13mod capture;
14#[cfg(not(target_family = "wasm"))]
15mod resample;
16#[cfg(not(target_family = "wasm"))]
17mod spool;
18#[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
19pub(crate) mod testing;
20#[cfg(not(target_family = "wasm"))]
21mod wav;
22
23use std::time::Duration;
24
25pub use clip::{Clip, ClipError};
26pub use recording::{CaptureError, RecordedAudio, Recording, RecordingReader, RecordingWriter};
27#[cfg(not(target_family = "wasm"))]
28pub use resample::{ResampleError, Resampler};
29pub use stop::{StopSignal, Stopper};
30
31#[cfg(not(target_family = "wasm"))]
32pub use capture::{
33    AudioCapture, MicrophoneError, RecordingMode, ReplayPace, SilenceDuration,
34    SilenceDurationError, WavReplayCapture, WavReplayError,
35};
36#[cfg(all(feature = "microphone", not(target_family = "wasm")))]
37pub use capture::{
38    Microphone, MicrophoneBuilder, PreparedMicrophone, WakeWordControl, WakeWordEvent,
39    WakeWordFailure, WakeWordMonitor, WakeWordSuspension, check_default_input_device,
40};
41#[cfg(not(target_family = "wasm"))]
42pub use spool::{SpoolError, remove_all_spools};
43#[cfg(all(not(target_family = "wasm"), any(test, feature = "test-support")))]
44pub use testing::{canonical_fixture, fixture_of};
45#[cfg(not(target_family = "wasm"))]
46pub use wav::{WavError, read_wav, write_wav};
47
48pub const CANONICAL_SAMPLE_RATE: u32 = 16_000;
49
50/// The longest audio a spool writes or `from_wav_file` reads. hound sums a
51/// 16-bit mono WAV's RIFF size as data bytes plus 36 in a `u32`, so one more
52/// sample wraps it. That is about 37 hours.
53#[cfg(not(target_family = "wasm"))]
54pub(crate) const MAX_RECORDING_SAMPLES: u64 = (u32::MAX as u64 - 36) / 2;
55#[cfg(not(target_family = "wasm"))]
56pub(crate) const MAX_RECORDING_DURATION: Duration =
57    Duration::from_nanos(MAX_RECORDING_SAMPLES * (1_000_000_000 / CANONICAL_SAMPLE_RATE as u64));
58pub const CHANNELS: u16 = 1;
59
60/// What a 16-bit sample divides by to land in -1.0..1.0.
61pub(crate) const PCM_SCALE: f32 = 32_768.0;
62
63/// The nearest 16-bit sample to `sample`, saturating outside -1.0..1.0.
64#[cfg(not(target_family = "wasm"))]
65pub fn pcm16_from_f32(sample: f32) -> i16 {
66    (sample * PCM_SCALE)
67        .round()
68        .clamp(-PCM_SCALE, PCM_SCALE - 1.0) as i16
69}
70
71pub fn f32_from_pcm16(sample: i16) -> f32 {
72    f32::from(sample) / PCM_SCALE
73}
74
75/// How many canonical samples `duration` spans, rounded down. Saturates at
76/// `u64::MAX`.
77pub const fn canonical_samples(duration: Duration) -> u64 {
78    let samples = samples_at(duration, CANONICAL_SAMPLE_RATE);
79    if samples > u64::MAX as u128 {
80        u64::MAX
81    } else {
82        samples as u64
83    }
84}
85
86/// How long `samples` canonical samples last.
87pub fn canonical_duration(samples: u64) -> Duration {
88    duration_at(samples, CANONICAL_SAMPLE_RATE)
89}
90
91pub(crate) const fn samples_at(duration: Duration, sample_rate: u32) -> u128 {
92    let rate = sample_rate as u128;
93    duration.as_secs() as u128 * rate + duration.subsec_nanos() as u128 * rate / 1_000_000_000
94}
95
96pub(crate) fn duration_at(samples: u64, sample_rate: u32) -> Duration {
97    let rate = u64::from(sample_rate);
98    Duration::from_secs(samples / rate)
99        + Duration::from_nanos(samples % rate * 1_000_000_000 / rate)
100}