lasprs 0.14.3

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
Documentation
use super::*;
use crate::{
    config::*,
    ps::{ApsMode, ApsSettings, AvPowerSpectra, CPSSettings},
    *,
};
use anyhow::{Result, bail};
use crossbeam::channel::{Receiver, Sender};
use ndarray::ArcArray2;
use std::{
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
    time::Duration,
};

/// Status of the Cross Power Spectra computation thread
#[derive(Debug, Clone)]
pub enum CPSComputeStatus {
    /// Waiting for data to be available
    Waiting,
    /// Computing / updating Cross Power Spectra
    Computing,
    /// Done computing Cross Power Spectra
    Done(Option<Array3<Cflt>>),
}

/// Internal message for interacting with the Cross Power Spectra computation thread
enum CPSMessage {
    /// New time data to process
    NewTimeData(ArcArray2<Flt>),

    /// Stop waiting for new time data blocks for computing Cross Power Spectra.
    /// This breaks the waiting loop for messages on the channel, finishes and
    /// returns the result.
    StopHere,
}
/// Handle to the computation thread for computing Cross Power Spectra.
#[derive(Debug)]
pub struct CPSThread {
    settings: CPSSettings,
    tx: Sender<CPSMessage>,
    consumer: triple_buffer::Output<CPSComputeStatus>,
    killFlag: Arc<AtomicBool>,
}

impl CPSThread {
    /// Create a new Cross Power Spectra computation thread
    ///
    /// # Arguments
    ///
    /// * `fs` - The sample rate of the input data
    /// * `settings` - The settings for the Cross Power Spectra computation
    /// * `queue_size` - The size of the queue for the computation thread. If
    ///   None, the queue will be unbounded.
    pub fn new(fs: StrictlyPositive, settings: CPSSettings, queue_size: Option<usize>) -> Self {
        let (tx, rx) = if let Some(size) = queue_size {
            crossbeam::channel::bounded(size)
        } else {
            crossbeam::channel::unbounded()
        };
        let (mut producer, consumer) = triple_buffer::triple_buffer(&CPSComputeStatus::Waiting);

        // Flag to communicate to the thread that we are not interested in the
        // results anymore
        let killFlag = Arc::new(AtomicBool::new(false));

        // Copies for storing in struct, and for passing to thread.
        let killflag2 = killFlag.clone();
        let settings2 = settings;

        let apssettings = ApsSettings {
            mode: ApsMode::AllAveraging {},
            overlap: settings.overlap,
            windowType: settings.window,
            freqWeightingType: FreqWeighting::Z,
            nfft: settings.nfft,
            fs,
        };
        spawn(
            move || {
                let mut aps = AvPowerSpectra::new(apssettings);
                producer.write(CPSComputeStatus::Waiting);
                'msgloop: while let Ok(msg) = rx.recv() {
                    if killFlag.load(Ordering::Relaxed) {
                        // The main thread is not interested in the result
                        // (anymore). We stop the computation perform early
                        // return
                        producer.write(CPSComputeStatus::Done(None));
                        return;
                    }
                    match msg {
                        CPSMessage::NewTimeData(block) => {
                            producer.write(CPSComputeStatus::Computing);
                            aps.compute_last(&block);
                        }
                        CPSMessage::StopHere => {
                            break 'msgloop;
                        }
                    }
                }
                producer.write(CPSComputeStatus::Done(
                    aps.get_result().map(|r| r.into_inner()),
                ));
            },
            ThreadPriority::Low,
        );

        Self {
            consumer,
            tx,
            settings: settings2,
            killFlag: killflag2,
        }
    }

    /// Get the settings used for the computation.
    pub fn getSettings(&self) -> &CPSSettings {
        &self.settings
    }

    /// Stop waiting for new data, perform computation until the last block is
    /// processed.
    pub fn stopHere(&self) {
        self.tx.send(CPSMessage::StopHere).unwrap();
    }

    /// Push new time series data to the computation thread.
    pub fn push(&self, data: ArcArray2<Flt>) {
        self.tx.send(CPSMessage::NewTimeData(data)).unwrap();
    }
    /// Get the status of the computation thread.
    pub fn get_status(&mut self) -> &CPSComputeStatus {
        self.consumer.read()
    }

    /// Wait till all blocks in queue are processed, then the thread stops and
    /// the result is returned.
    pub fn join(&mut self) -> (CPSSettings, Option<Array3<Cflt>>) {
        self.stopHere();
        while !matches!(self.get_status(), CPSComputeStatus::Done(_)) {
            std::thread::sleep(std::time::Duration::from_millis(10));
        }
        let res = {
            let CPSComputeStatus::Done(res) = &self.get_status() else {
                panic!("BUG: Unexpected thread status!");
            };
            res.clone()
        };
        (self.settings, res)
    }
}
impl Drop for CPSThread {
    fn drop(&mut self) {
        // Set kill flag to stop the thread
        self.killFlag.store(true, Ordering::Relaxed);
        // Send one last message to wake up the thread. If already stopped, this
        // will be ignored, but we get a senderror back because the receiver is
        // already dropped.
        let _ = self.tx.send(CPSMessage::StopHere);
        // Wait for thread to finish
        while !matches!(self.get_status(), CPSComputeStatus::Done(_)) {
            std::thread::sleep(std::time::Duration::from_millis(10));
        }
    }
}