aede_core/
audio_features.rs

1//! Versioned original-audio features beside the catalogue, never inside source files or personal opinions.
2use crate::playback::stream::PcmTrack;
3use aede_moods::{Extractor, Features, RATE};
4use std::path::Path;
5use std::time::{Duration, Instant};
6mod cache;
7pub use cache::{InputIdentity, Record, load, save};
8/// Decode/resample with existing Aède and FlacCompagnon primitives into bounded descriptor state.
9/// Checkpoints may wait for foreground playback, or refuse cancellation/source replacement.
10/// The active computation budget excludes checkpoint waiting; blocked OS reads retain platform limits.
11pub fn extract(
12    path: &Path,
13    mut checkpoint: impl FnMut() -> Result<(), String>,
14) -> Result<Features, String> {
15    checkpoint()?;
16    let mut track = PcmTrack::open_native(path).map_err(|e| e.to_string())?;
17    let format = track.source_format();
18    if !(1..=2).contains(&format.channels()) || !(8000..=192000).contains(&format.sample_rate()) {
19        return Err("source outside native mono/stereo acoustic profile".into());
20    }
21    track.set_output_rate(RATE).map_err(|e| e.to_string())?;
22    let mut extractor = Extractor::new(usize::from(format.channels()))?;
23    let mut active = Duration::ZERO;
24    loop {
25        checkpoint()?;
26        let step = Instant::now();
27        let block = track
28            .read_block::<std::convert::Infallible>(|_| Ok(()))
29            .map_err(|e| e.to_string())?;
30        active += step.elapsed();
31        let Some(block) = block else {
32            break;
33        };
34        let mut parked = Duration::ZERO;
35        let step = Instant::now();
36        extractor.push(block.samples, || {
37            let before = Instant::now();
38            let result = checkpoint();
39            parked += before.elapsed();
40            result
41        })?;
42        active += step.elapsed().saturating_sub(parked);
43        if active > Duration::from_secs(180) {
44            return Err("acoustic active computation budget exceeded".into());
45        }
46    }
47    checkpoint()?;
48    extractor.finish()
49}
50#[cfg(test)]
51#[path = "audio_features_tests.rs"]
52mod tests;