///|
/// Represents a detected seismic event window.
pub struct Trigger {
on_index : Int
off_index : Int
on_time : Double
off_time : Double
}
///|
/// Compute the classic STA/LTA (Short-Term Average / Long-Term Average) ratio
/// using an efficient O(N) sliding-window recursive algorithm.
pub fn classic_sta_lta(
data : Array[Double],
n_sta : Int,
n_lta : Int,
) -> Array[Double] {
let n = data.length()
let ratio = Array::make(n, 1.0)
if n == 0 || n_sta <= 0 || n_lta <= 0 || n_lta <= n_sta {
return ratio
}
let mut sum_sta = 0.0
let mut sum_lta = 0.0
// Initialize sums for the first n_lta samples
for i = 0; i < n_lta; i = i + 1 {
let val = data[i].abs()
sum_lta = sum_lta + val
if i >= n_lta - n_sta {
sum_sta = sum_sta + val
}
}
let sta_denom = n_sta.to_double()
let lta_denom = n_lta.to_double()
let initial_lta = sum_lta / lta_denom
ratio[n_lta - 1] = if initial_lta == 0.0 {
1.0
} else {
sum_sta / sta_denom / initial_lta
}
// Slide the window across the remaining samples
for i = n_lta; i < n; i = i + 1 {
let val_in = data[i].abs()
// Slide LTA window
let val_out_lta = data[i - n_lta].abs()
sum_lta = sum_lta + val_in - val_out_lta
// Slide STA window
let val_out_sta = data[i - n_sta].abs()
sum_sta = sum_sta + val_in - val_out_sta
let lta = sum_lta / lta_denom
let sta = sum_sta / sta_denom
ratio[i] = if lta == 0.0 { 1.0 } else { sta / lta }
}
ratio
}
///|
/// Detect event trigger windows based on onset/offset thresholds.
pub fn trigger_onset(
ratio : Array[Double],
on_threshold : Double,
off_threshold : Double,
dt : Double,
start_time : Double,
) -> Array[Trigger] {
let n = ratio.length()
let triggers = Array::new()
let mut active = false
let mut on_idx = 0
for i = 0; i < n; i = i + 1 {
let r = ratio[i]
if !active && r >= on_threshold {
active = true
on_idx = i
} else if active && r < off_threshold {
active = false
let off_idx = i
let on_time = start_time + on_idx.to_double() * dt
let off_time = start_time + off_idx.to_double() * dt
triggers.push({ on_index: on_idx, off_index: off_idx, on_time, off_time })
}
}
// Close the final trigger if it is still active at the end of the trace
if active {
let off_idx = n - 1
let on_time = start_time + on_idx.to_double() * dt
let off_time = start_time + off_idx.to_double() * dt
triggers.push({ on_index: on_idx, off_index: off_idx, on_time, off_time })
}
triggers
}