///|
/// Online mean and variance accumulator using Welford's stable update.
pub struct RollingAccumulator {
mut count : Int
mut mean : Double
mut m2 : Double
}
///|
/// Construct an empty accumulator.
pub fn RollingAccumulator::new() -> RollingAccumulator {
{ count: 0, mean: 0.0, m2: 0.0 }
}
///|
/// Add one sample to an accumulator.
pub fn RollingAccumulator::push(
self : RollingAccumulator,
value : Double,
) -> Unit {
self.count = self.count + 1
let difference = value - self.mean
self.mean = self.mean + difference / self.count.to_double()
let second_difference = value - self.mean
self.m2 = self.m2 + difference * second_difference
}
///|
/// Return the number of samples seen.
pub fn RollingAccumulator::count(self : RollingAccumulator) -> Int {
self.count
}
///|
/// Return the current mean.
pub fn RollingAccumulator::mean(self : RollingAccumulator) -> Double {
self.mean
}
///|
/// Return the population variance.
pub fn RollingAccumulator::variance(self : RollingAccumulator) -> Double {
if self.count == 0 {
0.0
} else {
self.m2 / self.count.to_double()
}
}
///|
/// Return the current standard deviation.
pub fn RollingAccumulator::standard_deviation(
self : RollingAccumulator,
) -> Double {
self.variance().sqrt()
}
///|
/// Reset an accumulator without allocating a new object.
pub fn RollingAccumulator::reset(self : RollingAccumulator) -> Unit {
self.count = 0
self.mean = 0.0
self.m2 = 0.0
}
///|
/// Streaming STA/LTA detector state.
pub struct StreamingStaLta {
sta_samples : Int
lta_samples : Int
sta_buffer : Array[Double]
lta_buffer : Array[Double]
mut sta_sum : Double
mut lta_sum : Double
}
///|
/// Construct a streaming STA/LTA detector.
pub fn StreamingStaLta::new(
sta_samples : Int,
lta_samples : Int,
) -> StreamingStaLta {
{
sta_samples: if sta_samples < 1 {
1
} else {
sta_samples
},
lta_samples: if lta_samples < 1 {
1
} else {
lta_samples
},
sta_buffer: [],
lta_buffer: [],
sta_sum: 0.0,
lta_sum: 0.0,
}
}
///|
/// Push an amplitude and return the current recursive ratio.
pub fn StreamingStaLta::push(self : StreamingStaLta, value : Double) -> Double {
let amplitude = value.abs()
self.sta_buffer.push(amplitude)
self.sta_sum = self.sta_sum + amplitude
if self.sta_buffer.length() > self.sta_samples {
self.sta_sum = self.sta_sum - self.sta_buffer.remove(0)
}
self.lta_buffer.push(amplitude)
self.lta_sum = self.lta_sum + amplitude
if self.lta_buffer.length() > self.lta_samples {
self.lta_sum = self.lta_sum - self.lta_buffer.remove(0)
}
let sta = self.sta_sum / self.sta_buffer.length().to_double()
let lta = self.lta_sum / self.lta_buffer.length().to_double()
if lta == 0.0 {
1.0
} else {
sta / lta
}
}
///|
/// Clear streaming detector state.
pub fn StreamingStaLta::reset(self : StreamingStaLta) -> Unit {
self.sta_buffer.clear()
self.lta_buffer.clear()
self.sta_sum = 0.0
self.lta_sum = 0.0
}
///|
/// A fixed-size ring buffer for streaming waveform samples.
pub struct SampleRing {
capacity : Int
values : Array[Double]
}
///|
/// Construct a ring buffer.
pub fn SampleRing::new(capacity : Int) -> SampleRing {
{ capacity: if capacity < 1 { 1 } else { capacity }, values: [] }
}
///|
/// Add a sample, evicting the oldest value when full.
pub fn SampleRing::push(self : SampleRing, value : Double) -> Unit {
self.values.push(value)
if self.values.length() > self.capacity {
ignore(self.values.remove(0))
}
}
///|
/// Return a snapshot of the current ring content.
pub fn SampleRing::snapshot(self : SampleRing) -> Array[Double] {
self.values.copy()
}
///|
/// Return whether a ring has reached its configured capacity.
pub fn SampleRing::is_full(self : SampleRing) -> Bool {
self.values.length() >= self.capacity
}
///|
/// A streaming quality monitor with configurable batch size.
pub struct StreamingQualityMonitor {
batch_size : Int
mut batch : Array[Double]
reports : Array[QualityReport]
}
///|
/// Construct a streaming quality monitor.
pub fn StreamingQualityMonitor::new(
batch_size : Int,
) -> StreamingQualityMonitor {
{
batch_size: if batch_size < 1 {
1
} else {
batch_size
},
batch: [],
reports: [],
}
}
///|
/// Add a sample and optionally emit a report when a batch is complete.
pub fn StreamingQualityMonitor::push(
self : StreamingQualityMonitor,
value : Double,
sample_rate : Double,
) -> QualityReport? {
self.batch.push(value)
if self.batch.length() < self.batch_size {
return None
}
let trace = Trace::new("stream", "data", sample_rate, 0.0, self.batch)
let report = assess_quality(trace, 1.0e6, 0.0000001)
self.reports.push(report)
self.batch = []
Some(report)
}
///|
/// Return all completed quality reports.
pub fn StreamingQualityMonitor::reports(
self : StreamingQualityMonitor,
) -> Array[QualityReport] {
self.reports.copy()
}