lasprs 0.8.0

Library for Acoustic Signal Processing (Rust edition, with optional Python bindings via pyo3)
Documentation
//! Contains the actual implementation logic for updating the PPM status.
use super::{ppmdropspeed::FromToUsize, ClipState, PPMDropSpeed, ALMOST_CLIPPED_REL_AMP};
use crate::config::*;
use crate::daq::StreamMetaData;
use crate::math::{max, maxabs, min};
use crate::rt::ppm::{
    CLIP_INDICATOR_WAIT_S, LEVEL_THRESHOLD_FOR_HIGH_LEVEL, LEVEL_THRESHOLD_FOR_LOW_LEVEL,
    PEAK_LEVEL_HOLD_TIME_S,
};
use ndarray::ArrayView1;
use std::time::Instant;

#[inline]
fn level(pwr: Flt) -> Flt {
    10. * pwr.log10()
}

/// To be used internally only
#[derive(Copy, Clone, Debug)]
pub struct PPMChannelStatus {
    /// Counter for incoming data blocks
    block_ctr: usize,

    /// Decay constant for powers \[-\]
    alpha: Flt,

    // Signal range within a value should lie
    range: (Flt, Flt),
    // The max distance from 0 to both sides, + and -
    abs_range_sq: Flt,

    // Current signal power
    signal_pwr: Flt,

    // Current peak power.
    signal_peak_pwr: Flt,

    /// Current signal level
    pub signal_level: Flt,

    /// Current peak level
    pub peak_level: Flt,

    /// Current state of the signal level
    pub clipstate: ClipState,

    peak_hold_blocks: usize,
    clip_hold_blocks: usize,

    /// Store when last signal clip happened in time
    clip_moment: Option<usize>,

    /// Storage for moment when signal has peaked.
    signal_peak_moment: usize,
}
impl PPMChannelStatus {
    /// Create new PPM Channel status for a single channel.
    ///
    /// # Args
    ///
    /// - `meta`: Stream meta data
    /// - `chno`: Channel number to work on
    /// - `dropSpeed`: Speed at which the level drops when no further nonzero
    ///   input is provided.
    pub fn new(meta: &StreamMetaData, chno: usize, dropSpeed: PPMDropSpeed) -> Self {
        let chmeta = &meta.channelInfo[chno];
        let range = chmeta.range;

        let dropspeed_val = dropSpeed.to_usize() as Flt; // 10/ln(10)*tau = dropSpeed
                                                         // or tau = dropSpeed*ln(10)/10
                                                         // And the pole is at -tau.
        let pole = -dropspeed_val * Flt::log(10., 10.) / 10.;
        let alpha = Flt::exp(meta.framesPerBlock as Flt * pole / meta.samplerate);
        // dbg!(pole, alpha);

        let abs_range = if range.0.abs() > range.1.abs() {
            range.0.abs()
        } else {
            range.1.abs()
        };
        let clip_hold_blocks =
            (CLIP_INDICATOR_WAIT_S * meta.samplerate / meta.framesPerBlock as Flt) as usize;
        let peak_hold_blocks =
            (PEAK_LEVEL_HOLD_TIME_S * meta.samplerate / meta.framesPerBlock as Flt) as usize;

        Self {
            alpha,
            range,
            abs_range_sq: abs_range.powi(2),
            clip_hold_blocks,
            peak_hold_blocks,
            signal_level: -Flt::INFINITY,
            peak_level: -Flt::INFINITY,
            block_ctr: 0,
            signal_pwr: 0.,
            signal_peak_pwr: 0.,
            clipstate: ClipState::LowLevel,
            clip_moment: None,
            signal_peak_moment: 0,
        }
    }
    /// Proces the new block of data. Updates the (internal) state of the PPM
    pub fn process<'a, T>(&mut self, chdata: T)
    where
        T: Into<ArrayView1<'a, Flt>>,
    {
        let chdata = chdata.into();

        // Drop current levels
        self.signal_pwr *= self.alpha;
        if self.block_ctr > self.peak_hold_blocks + self.signal_peak_moment {
            self.signal_peak_pwr *= self.alpha;
        }

        let min_val = min(chdata);
        let max_val = max(chdata);
        // Calculate new signal power
        let this_block_pwr = {
            // Calculate update
            let abs_min_pwr = min_val.powi(2);
            let abs_max_pwr = max_val.powi(2);
            if abs_max_pwr > abs_min_pwr {
                abs_max_pwr
            } else {
                abs_min_pwr
            }
        };

        // Update signal
        if this_block_pwr > self.signal_pwr {
            self.signal_pwr = this_block_pwr;
        }
        // Update signal peak holder
        if this_block_pwr > self.signal_peak_pwr {
            self.signal_peak_pwr = this_block_pwr;
            self.signal_peak_moment = self.block_ctr;
        }

        // Convert to levels
        self.peak_level = level(self.signal_peak_pwr / self.abs_range_sq);
        self.signal_level = level(self.signal_pwr / self.abs_range_sq);

        // Check whether a clip occured in the current block
        let hasclipped = min_val <= ALMOST_CLIPPED_REL_AMP * self.range.0
            || max_val >= ALMOST_CLIPPED_REL_AMP * self.range.1;

        // Update the clip state
        self.clipstate = if hasclipped {
            self.clip_moment = Some(self.block_ctr);
            ClipState::Clipped
        } else if let Some(clip_moment) = self.clip_moment {
            if self.block_ctr > self.clip_hold_blocks + clip_moment {
                self.clip_moment = None;
            }
            // Still clipped
            ClipState::Clipped
        } else {
            let high_level = self.signal_level > LEVEL_THRESHOLD_FOR_HIGH_LEVEL;
            let low_level = self.signal_level < LEVEL_THRESHOLD_FOR_LOW_LEVEL;
            if high_level {
                ClipState::HighLevel
            } else if low_level {
                ClipState::LowLevel
            } else {
                ClipState::LevelFine
            }
        };
        // Finally: update block counter
        self.block_ctr += 1;
    }
}