///|
/// One analysis band in a configurable filter bank.
pub struct FilterBankBand {
name : String
low_frequency : Double
high_frequency : Double
filter : FirFilter?
}
///|
/// Configuration for a multi-band seismic filter bank.
pub struct FilterBankConfig {
sample_rate : Double
bands : Array[(String, Double, Double)]
taps : Int
window : FirWindow
}
///|
/// A bank of filtered trace outputs.
pub struct FilterBankResult {
station : String
channel : String
bands : Array[FilterBankBand]
traces : Array[Trace]
}
///|
/// Build standard monitoring bands from a Nyquist frequency.
pub fn standard_filter_bank(sample_rate : Double) -> FilterBankConfig {
let nyquist = sample_rate / 2.0
let high = nyquist.max(0.2)
{
sample_rate,
bands: [
("low", 0.1.min(high * 0.5), high * 0.25),
("mid", high * 0.2, high * 0.55),
("high", high * 0.5, high * 0.9),
],
taps: 81,
window: fir_hann(),
}
}
///|
/// Design all bands in a filter-bank configuration.
pub fn design_filter_bank(
config : FilterBankConfig,
) -> Result[Array[FilterBankBand], String] {
let result : Array[FilterBankBand] = Array::new()
for band in config.bands {
let filter = if band.1 <= 0.0 {
design_fir_lowpass(config.sample_rate, band.2, config.taps, config.window)
} else {
design_fir_bandpass(
config.sample_rate,
band.1,
band.2,
config.taps,
config.window,
)
}
match filter {
Ok(value) =>
result.push({
name: band.0,
low_frequency: band.1,
high_frequency: band.2,
filter: Some(value),
})
Err(message) => return Err(message)
}
}
Ok(result)
}
///|
/// Apply a designed filter bank to a trace.
pub fn apply_filter_bank(
trace : Trace,
bands : Array[FilterBankBand],
) -> FilterBankResult {
let traces : Array[Trace] = Array::new()
for band in bands {
match band.filter {
None => ()
Some(filter) =>
traces.push(
trace_with_data(trace, convolve_same(trace.data, filter.coefficients)),
)
}
}
{
station: trace.station,
channel: trace.channel,
bands: bands.copy(),
traces,
}
}
///|
/// Compute RMS values for all filter-bank outputs.
pub fn filter_bank_rms(result : FilterBankResult) -> Array[Double] {
result.traces.map(trace => rms_samples(trace.data))
}
///|
/// Return the strongest bank band by RMS amplitude.
pub fn strongest_filter_band(result : FilterBankResult) -> (String, Double)? {
let rms = filter_bank_rms(result)
if rms.is_empty() {
return None
}
let mut best = 0
for index = 1; index < rms.length(); index = index + 1 {
if rms[index] > rms[best] {
best = index
}
}
if best >= result.bands.length() {
None
} else {
Some((result.bands[best].name, rms[best]))
}
}
///|
/// Export filter-bank measurements as a CSV table.
pub fn filter_bank_to_csv(result : FilterBankResult) -> String {
let rms = filter_bank_rms(result)
let mut output = "station,channel,band,low_hz,high_hz,rms\n"
for index = 0
index < result.bands.length() && index < rms.length()
index = index + 1 {
let band = result.bands[index]
output = output +
result.station +
"," +
result.channel +
"," +
band.name +
"," +
band.low_frequency.to_string() +
"," +
band.high_frequency.to_string() +
"," +
rms[index].to_string() +
"\n"
}
output
}
///|
/// Format filter-bank configuration.
pub fn filter_bank_config_text(config : FilterBankConfig) -> String {
"sample_rate=" +
config.sample_rate.to_string() +
" bands=" +
config.bands.length().to_string() +
" taps=" +
config.taps.to_string()
}