lasprs 0.14.1

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
//! Simple example demonstrating recording data to a measurement file and then
//! reading it back to calculate the auto power of the first channel.
//!
//! By default, this example uses the system default input device (typically microphone).
//! To use the loopback device instead (for testing without audio hardware),
//! enable the "loopback-api" feature:
//!
//! cargo run --example simple_record_analyze --features "record,cpal-api,loopback-api"

use anyhow::{Result, anyhow};
use lasprs::ps::*;
use lasprs::{FreqWeighting, Overlap, WindowType, daq::*};
use std::{thread, time::Duration};

fn main() -> Result<()> {
    println!("Simple LaspRS Recording and Analysis Example");
    println!("==========================================");

    // Create a StreamMgr instance
    let mut stream_mgr = StreamMgr::new_with_devices();

    // Choose between loopback device (for testing) or default input device
    #[cfg(feature = "loopback-api")]
    {
        // Use loopback device if available - good for testing without microphone
        println!("Using loopback device (no audio hardware required)...");
        let devices = stream_mgr.getDeviceInfo();
        if let Some(loopback_device) = devices.iter().find(|d| d.device_name == "Loopback") {
            let config = DaqConfig::newFromDeviceInfo(loopback_device);
            stream_mgr.startStream(StreamType::Input, &config)?;
        } else {
            println!("Loopback device not found, using default input stream...");
            stream_mgr.startDefaultInputStream()?;
        }
    }
    #[cfg(not(feature = "loopback-api"))]
    {
        // Use system default input device (typically microphone)
        println!("Starting default input stream (system default input device)...");
        println!("Note: This will use your system microphone. Make some noise to see signal!");
        stream_mgr.startDefaultInputStream()?;
    }

    // Set up recording parameters
    let name = "simple_recording";
    let _ = std::fs::remove_file(name);
    let recording_duration = Duration::from_secs(2); // Short 2-second recording

    // Configure CPS settings for power spectrum computation
    let cps_settings = CPSSettings {
        nfft: 1024, // Smaller FFT for this simple example
        overlap: Overlap::NoOverlap {},
        window: WindowType::Hann,
        istart: None,
        istop: None,
    };

    let record_settings = RecordSettings::new_simple(name, Some(recording_duration))?;

    println!(
        "Recording for {} seconds to '{}'...",
        recording_duration.as_secs(),
        name
    );

    // Start recording
    let mut recording = Recording::new(record_settings, &mut stream_mgr)?;

    // Simple recording loop
    loop {
        match recording.status() {
            RecordStatus::Recording {
                recorded, pct_done, ..
            } => {
                print!("\rProgress: {:.0}% ({:.1}s)", pct_done * 100.0, recorded);
                std::io::Write::flush(&mut std::io::stdout()).unwrap();
            }
            RecordStatus::Finished { finishedrecording } => {
                println!("\nRecording done");
                if let Some(error) = &finishedrecording.error {
                    anyhow::bail!("Recording error: {}", error);
                }
                break;
            }
            _ => {}
        }
        thread::sleep(Duration::from_millis(50));
    }

    // Stop the input stream
    stream_mgr.stopInputStream()?;

    // Open and analyze the measurement file
    println!("Opening measurement file for analysis...");

    let measurement = recording
        .getMeasurement()
        .ok_or_else(|| anyhow!("Failed to get measurement"))?;

    // Access measurement data through the Arc<RwLock<_>>
    let (nchannels, samplerate, duration);
    let cps;
    {
        let mut measurement = measurement.write();

        println!("Measurement info:");
        nchannels = measurement.nchannels();
        samplerate = measurement.samplerate();
        duration = measurement.get_duration();

        println!("  Channels: {}", nchannels);
        println!("  Sample rate: {} Hz", samplerate);
        println!("  Duration: {:.2} s", duration);

        // Calculate auto power spectrum for first channel
        println!("Calculating auto power spectrum...");

        cps = measurement.CPS(
            &cps_settings,
            FreqWeighting::Z, // No frequency weighting
            Some(&[0]),       // First channel only
        )?;
    }

    // Extract auto power (diagonal element for channel 0)
    let powers = cps.ap(0);

    // Calculate total power and find peak
    let total_power: f64 = powers.iter().sum();
    let max_power = powers.iter().cloned().fold(0.0f64, f64::max);

    println!("Auto power results:");
    println!("  Total power: {:.3e}", total_power);
    println!("  Peak power: {:.3e}", max_power);
    println!("  Frequency bins: {}", powers.len());

    // Show first few frequency bins
    let freq_res = *samplerate as f64 / cps_settings.nfft as f64;
    println!("First 5 frequency bins:");
    for (i, &power) in powers.iter().take(5).enumerate() {
        println!("  {:.1} Hz: {:.3e}", i as f64 * freq_res, power);
    }

    // Clean up
    std::fs::remove_file(name)?;
    println!("Cleaned up recording file");

    Ok(())
}