audio_samples 2.0.0

A typed audio processing library for Rust that treats audio as a first-class, invariant-preserving object rather than an unstructured numeric buffer.
Documentation
#[cfg(not(all(feature = "plotting", feature = "statistics")))]
fn main() {
    eprintln!("error: This example requires the `plotting` and `statistics` features.");
    std::process::exit(1);
}

/// Render a plot to in-memory HTML and assert it is a non-empty Plotly
/// document — avoids writing files to disk while still verifying the plot.
#[cfg(all(feature = "plotting", feature = "statistics"))]
fn assert_plot_html<P: audio_samples::operations::PlotUtils>(
    plot: &P,
    name: &str,
) -> audio_samples::AudioSampleResult<()> {
    let html = plot.html()?;
    assert!(
        !html.is_empty() && html.contains("plotly"),
        "{name} spectral-overlay HTML should be a non-empty Plotly document"
    );
    println!("Rendered {name} spectral overlay ({} bytes)", html.len());
    Ok(())
}

#[cfg(all(feature = "plotting", feature = "statistics"))]
fn main() -> audio_samples::AudioSampleResult<()> {
    use audio_samples::operations::AudioPlotting;
    use audio_samples::operations::plotting::dsp_overlays;
    use audio_samples::operations::plotting::spectrograms::SpectrogramPlotParams;
    use audio_samples::{AudioSamples, chirp, sample_rate};
    use std::time::Duration;

    println!("=== Spectral Overlays Example ===\n");

    let audio: AudioSamples<'_, f32> = chirp::<f32>(
        200.0,                        // start frequency
        2000.0,                       // end frequency
        Duration::from_secs_f32(4.0), // duration
        sample_rate!(44100),
        0.5, // amplitude
    );
    println!("Generated chirp signal: 200 Hz → 2000 Hz sweep over 4 seconds");

    // Window parameters for spectral analysis
    let window_size = audio_samples::nzu!(2048); // ~46ms at 44.1kHz
    let hop_size = audio_samples::nzu!(512); // ~12ms hop

    println!("\nSpectral analysis parameters:");
    println!(
        "  Window size: {} samples (~{:.1}ms)",
        window_size,
        window_size.get() as f64 / 44100.0 * 1000.0
    );
    println!(
        "  Hop size: {} samples (~{:.1}ms)",
        hop_size,
        hop_size.get() as f64 / 44100.0 * 1000.0
    );

    // Example 1: Spectrogram with spectral centroid overlay
    println!("\n--- Spectral Centroid Overlay ---");
    let (centroid_times, centroid_values) =
        dsp_overlays::compute_windowed_spectral_centroid(&audio.borrow(), window_size, hop_size);

    println!(
        "Computed spectral centroid for {} windows",
        centroid_times.len()
    );
    println!(
        "Centroid range: {:.0} Hz to {:.0} Hz",
        centroid_values
            .iter()
            .cloned()
            .fold(f64::INFINITY, f64::min),
        centroid_values
            .iter()
            .cloned()
            .fold(f64::NEG_INFINITY, f64::max)
    );

    let spectrogram_plot = audio
        .plot_spectrogram(&SpectrogramPlotParams::mel_db())?
        .add_spectral_centroid(centroid_times.clone(), centroid_values.clone(), None);

    assert_plot_html(&spectrogram_plot, "centroid")?;
    println!("  White line with cyan markers = Spectral Centroid");

    // Example 2: Spectrogram with spectral rolloff overlay (85%)
    println!("\n--- Spectral Rolloff Overlay ---");
    let rolloff_percent = 0.85; // 85% energy cutoff
    let (rolloff_times, rolloff_values) = dsp_overlays::compute_windowed_spectral_rolloff(
        &audio.borrow(),
        window_size,
        hop_size,
        rolloff_percent,
    );

    println!(
        "Computed spectral rolloff ({}%) for {} windows",
        (rolloff_percent * 100.0) as u32,
        rolloff_times.len()
    );
    println!(
        "Rolloff range: {:.0} Hz to {:.0} Hz",
        rolloff_values.iter().cloned().fold(f64::INFINITY, f64::min),
        rolloff_values
            .iter()
            .cloned()
            .fold(f64::NEG_INFINITY, f64::max)
    );

    let rolloff_plot = audio
        .plot_spectrogram(&SpectrogramPlotParams::mel_db())?
        .add_spectral_rolloff(rolloff_times.clone(), rolloff_values.clone(), None);

    assert_plot_html(&rolloff_plot, "rolloff")?;
    println!("  White line with cyan markers = Spectral Rolloff (85%)");

    // Example 3: Combined - both centroid and rolloff
    println!("\n--- Combined Spectral Features ---");

    let combined_plot = audio
        .plot_spectrogram(&SpectrogramPlotParams::mel_db())?
        .add_spectral_centroid(centroid_times, centroid_values.clone(), Some("Centroid"))
        .add_spectral_rolloff(rolloff_times, rolloff_values, Some("Rolloff 85%"));

    assert_plot_html(&combined_plot, "combined")?;
    println!("  Shows both Centroid and Rolloff tracks with legend");

    // Example 4: Different rolloff percentages comparison
    println!("\n--- Multiple Rolloff Percentages ---");

    let (rolloff_50_times, rolloff_50_values) = dsp_overlays::compute_windowed_spectral_rolloff(
        &audio.borrow(),
        window_size,
        hop_size,
        0.50,
    );
    let (rolloff_85_times, rolloff_85_values) = dsp_overlays::compute_windowed_spectral_rolloff(
        &audio.borrow(),
        window_size,
        hop_size,
        0.85,
    );
    let (rolloff_95_times, rolloff_95_values) = dsp_overlays::compute_windowed_spectral_rolloff(
        &audio.borrow(),
        window_size,
        hop_size,
        0.95,
    );
    let comparison_plot = audio
        .plot_spectrogram(&SpectrogramPlotParams::mel_db())?
        .add_spectral_rolloff(rolloff_50_times, rolloff_50_values, Some("Rolloff 50%"))
        .add_spectral_rolloff(rolloff_85_times, rolloff_85_values, Some("Rolloff 85%"))
        .add_spectral_rolloff(rolloff_95_times, rolloff_95_values, Some("Rolloff 95%"));

    assert_plot_html(&comparison_plot, "rolloff_comparison")?;

    // The chirp sweeps 200 Hz -> 2000 Hz, so the spectral centroid must rise
    // over time: the last window's centroid should exceed the first window's.
    assert!(
        centroid_values.len() >= 2,
        "expected multiple centroid windows for a 4 s chirp"
    );
    assert!(
        centroid_values.last().unwrap() > centroid_values.first().unwrap(),
        "spectral centroid should rise across an ascending chirp: {} !> {}",
        centroid_values.last().unwrap(),
        centroid_values.first().unwrap()
    );
    Ok(())
}