Audio data model
Every audio buffer in the recorder flows through one type:
AudioChunk — a
timestamped slice of normalized f32 samples with its
AudioFormat. The
GStreamer capture path (M-MEDIA.5), the deterministic mock sources
(M-MEDIA.4), the histogram quantizer (M-MEDIA.8), and the live
microphone path (M-MEDIA.15) all produce / consume this type.
Why normalized f32
- It's what every downstream visualization wants —
AudioHistogram's RMS / peak math runs cleaner on floats than on integers. - It's what GStreamer's
audioconvert ! audio/x-raw,format=F32LEproduces natively — capture pipelines don't have to re-quantize. - Future device-capture backends (
cpal,coreaudio-rs) emitf32as their preferred shape too — no buffer layout churn at the seam.
The SampleFormat enum
exists so capture-side code can declare its input layout
(F32 / I16 / U8) before normalization. Internally,
AudioChunk::samples is always &[f32].
Interleave order — planar-per-frame
Stereo: [L₀, R₀, L₁, R₁, …]. Mono: [s₀, s₁, …]. Matches GStreamer
raw-audio, cpal, coreaudio-rs. No re-layout needed at the
capture seam.
Validation
[AudioChunk::new] rejects:
samples.len() % channels != 0— each frame must carry exactly one sample per channel.channels == 0.sample_rate == 0.
These are the three "is this a meaningful chunk?" checks. The remaining shape questions (clipping, NaN, DC offset) are visualization concerns, not data-model concerns.
Derived metrics
AudioChunk::peak() and AudioChunk::rms() are pre-computed
shortcuts used by M-MEDIA.8 (histogram quantization) and capture-side
regression checks. They run in O(n) over the buffer; cache the
result if you need it more than once per chunk.
Quick start
#![allow(unused)] fn main() { use media::audio::{AudioChunk, AudioFormat}; use media::clock::MediaTime; let fmt = AudioFormat::mono_f32(48_000); let samples = vec![0.0_f32; 48_000]; // 1.0 s of silence at 48 kHz. let chunk = AudioChunk::new(fmt, samples, MediaTime::ZERO).expect("valid"); assert_eq!(chunk.frame_count(), 48_000); assert!((chunk.duration().as_seconds() - 1.0).abs() < 1e-9); }