///|
/// Mask calculation mode for Harmonic-Percussive Source Separation.
pub(all) enum MaskMode {
Soft
Hard
} derive(Eq, Debug)
///|
/// Configuration parameters for Harmonic-Percussive Source Separation (HPSS).
pub(all) struct HpssConfig {
kernel_harm : Int
kernel_perc : Int
power : Double
margin_harm : Double
margin_perc : Double
mask_mode : MaskMode
} derive(Eq, Debug)
///|
/// Default HPSS configuration (librosa-aligned default sizes: 31 time frames, 31 freq bins, power 2.0).
pub fn HpssConfig::default() -> HpssConfig {
{
kernel_harm: 31,
kernel_perc: 31,
power: 2.0,
margin_harm: 1.0,
margin_perc: 1.0,
mask_mode: Soft,
}
}
///|
/// Result of Harmonic-Percussive Source Separation.
pub(all) struct HpssResult {
harmonic : @types.FeatureMatrix
percussive : @types.FeatureMatrix
harmonic_mask : @types.FeatureMatrix
percussive_mask : @types.FeatureMatrix
} derive(Debug)
///|
/// In-place insertion sort on double array slice [0.. Unit {
for i in 1..= 0 && buf[j] > key {
buf[j + 1] = buf[j]
j = j - 1
}
buf[j + 1] = key
}
}
///|
/// 1D median filter with edge clamping boundary conditions.
pub fn median_filter_1d(
arr : Array[Double],
kernel_size : Int,
) -> Array[Double] {
let len = arr.length()
if len <= 1 || kernel_size <= 1 {
return arr.copy()
}
let mut k = kernel_size
if k % 2 == 0 {
k = k + 1
}
if k > len {
k = if len % 2 == 1 { len } else { len - 1 }
}
if k <= 1 {
return arr.copy()
}
let radius = k / 2
let window_buf : Array[Double] = Array::make(k, 0.0)
let out : Array[Double] = Array::make(len, 0.0)
for i in 0..= len {
idx = len - 1
}
window_buf[j] = arr[idx]
}
sort_in_place(window_buf, k)
out[i] = window_buf[radius]
}
out
}
///|
/// Median filter along time axis (each frequency row filtered across time columns).
pub fn median_filter_time(
matrix : @types.FeatureMatrix,
kernel_size : Int,
) -> @types.FeatureMatrix {
let n_rows = matrix.n_rows
let n_cols = matrix.n_cols
let res = @types.FeatureMatrix::zeros(n_rows, n_cols)
for r in 0.. @types.FeatureMatrix {
let n_rows = matrix.n_rows
let n_cols = matrix.n_cols
let res = @types.FeatureMatrix::zeros(n_rows, n_cols)
let col_data : Array[Double] = Array::make(n_rows, 0.0)
for c in 0.. HpssResult {
let n_rows = spec.n_rows
let n_cols = spec.n_cols
let h_med = median_filter_time(spec, config.kernel_harm)
let p_med = median_filter_freq(spec, config.kernel_perc)
let harmonic = @types.FeatureMatrix::zeros(n_rows, n_cols)
let percussive = @types.FeatureMatrix::zeros(n_rows, n_cols)
let mask_h = @types.FeatureMatrix::zeros(n_rows, n_cols)
let mask_p = @types.FeatureMatrix::zeros(n_rows, n_cols)
let eps = 1.0e-12
for r in 0.. {
let h_pow = if h_val <= 0.0 {
0.0
} else {
@math.pow(h_val, config.power)
}
let p_pow = if p_val <= 0.0 {
0.0
} else {
@math.pow(p_val, config.power)
}
if h_pow + p_pow <= eps {
(0.5, 0.5)
} else {
let h_denom = h_pow +
p_pow * @math.pow(config.margin_harm, config.power)
let p_denom = p_pow +
h_pow * @math.pow(config.margin_perc, config.power)
(h_pow / h_denom, p_pow / p_denom)
}
}
Hard =>
(
if h_val >= p_val * config.margin_harm {
1.0
} else {
0.0
},
if p_val > h_val * config.margin_perc {
1.0
} else {
0.0
},
)
}
mask_h.set(r, c, mh)
mask_p.set(r, c, mp)
harmonic.set(r, c, s * mh)
percussive.set(r, c, s * mp)
}
}
{ harmonic, percussive, harmonic_mask: mask_h, percussive_mask: mask_p, }
}
///|
/// High-level HPSS directly from single-channel AudioBuffer.
pub fn hpss_from_audio(
audio : @types.AudioBuffer,
config : @types.FeatureConfig,
hpss_config : HpssConfig,
) -> Result[HpssResult, @types.MelError] {
match @stft.stft_spectrogram(audio.samples, config) {
Ok(spec) => Ok(hpss(spec, hpss_config))
Err(e) => Err(e)
}
}
///|
/// Compute frame-wise harmonic energy ratio: E_harm / (E_harm + E_perc).
pub fn harmonic_energy_ratio(result : HpssResult) -> Array[Double] {
let n_rows = result.harmonic.n_rows
let n_cols = result.harmonic.n_cols
let ratios : Array[Double] = Array::make(n_cols, 0.0)
let eps = 1.0e-12
for c in 0.. Array[Double] {
let n_rows = result.percussive.n_rows
let n_cols = result.percussive.n_cols
let ratios : Array[Double] = Array::make(n_cols, 0.0)
let eps = 1.0e-12
for c in 0..