use crate::types::{Profile, SampleRate};
use std::path::PathBuf;
#[derive(Clone, Debug)]
pub struct PipelineConfig {
pub profile: Profile,
pub sample_rate: SampleRate,
pub clusterer: ClustererKind,
pub max_speakers: u8,
pub min_cluster_size: usize,
pub resegment_overlap: bool,
pub disable_seg_overlap: bool,
pub majority_local_map: bool,
pub min_speech_secs: f32,
pub max_gap_secs: f32,
pub embedder_pool_size: usize,
pub execution_provider: ExecutionProvider,
pub vbx_plda_dir: Option<PathBuf>,
pub embed_window_secs: Option<f32>,
pub binarization: Option<crate::segmentation::BinarizationConfig>,
pub as_norm: Option<crate::clusterer::AsNormConfig>,
pub domain: Option<crate::clusterer::DomainProfile>,
}
impl Default for PipelineConfig {
fn default() -> Self {
Self {
profile: Profile::Balanced,
sample_rate: SampleRate::new(16000).unwrap_or_default(),
clusterer: ClustererKind::Ahc {
threshold: crate::types::DEFAULT_AHC_THRESHOLD,
},
max_speakers: 20,
min_cluster_size: 1,
resegment_overlap: true,
disable_seg_overlap: false,
majority_local_map: false,
min_speech_secs: 0.25,
max_gap_secs: 0.5,
embedder_pool_size: default_pool_size(),
execution_provider: ExecutionProvider::auto(),
vbx_plda_dir: None,
embed_window_secs: None,
binarization: None,
as_norm: None,
domain: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClustererKind {
NmeSc,
Ahc {
threshold: f32,
},
Vbx,
}
#[cfg(feature = "infer")]
pub use crate::onnx::ExecutionProvider;
#[cfg(not(feature = "infer"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ExecutionProvider {
Cpu,
CoreMl,
Nnapi,
Cuda,
XnnPack,
}
#[cfg(not(feature = "infer"))]
impl ExecutionProvider {
pub fn auto() -> Self {
Self::Cpu
}
pub fn is_available(self) -> bool {
matches!(self, Self::Cpu)
}
}
fn default_pool_size() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.clamp(1, 4)
}
#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Profile;
#[test]
fn pipeline_config_default_is_balanced() {
let cfg = PipelineConfig::default();
assert_eq!(cfg.profile, Profile::Balanced);
assert_eq!(cfg.sample_rate.get(), 16000);
assert!(matches!(
cfg.clusterer,
ClustererKind::Ahc {
threshold
} if (threshold - crate::types::DEFAULT_AHC_THRESHOLD).abs() < f32::EPSILON
));
assert_eq!(cfg.max_speakers, 20);
assert_eq!(cfg.min_cluster_size, 1);
assert!(cfg.resegment_overlap);
assert!(!cfg.disable_seg_overlap);
assert!(!cfg.majority_local_map);
assert!((cfg.min_speech_secs - 0.25).abs() < f32::EPSILON);
assert!((cfg.max_gap_secs - 0.5).abs() < f32::EPSILON);
assert!(cfg.embedder_pool_size >= 1);
assert!(cfg.embedder_pool_size <= 4);
assert!(cfg.as_norm.is_none());
assert!(cfg.domain.is_none());
}
#[test]
fn default_ahc_threshold_matches_shared_constant() {
let cfg = PipelineConfig::default();
match cfg.clusterer {
ClustererKind::Ahc { threshold } => {
assert!((threshold - crate::types::DEFAULT_AHC_THRESHOLD).abs() < f32::EPSILON);
assert!((threshold - Profile::Balanced.default_threshold()).abs() < f32::EPSILON);
}
other => panic!("expected default AHC clusterer, got {other:?}"),
}
}
#[test]
fn clusterer_kind_ahc_with_threshold() {
let k = ClustererKind::Ahc { threshold: 0.7 };
if let ClustererKind::Ahc { threshold } = k {
assert!((threshold - 0.7).abs() < f32::EPSILON);
} else {
panic!("expected Ahc variant");
}
}
#[test]
fn execution_provider_auto_returns_some_variant() {
let ep = ExecutionProvider::auto();
let _ = ep;
}
#[test]
fn clusterer_kind_nme_sc_and_vbx_variants_are_distinct() {
assert_eq!(ClustererKind::NmeSc, ClustererKind::NmeSc);
assert_eq!(ClustererKind::Vbx, ClustererKind::Vbx);
assert_ne!(ClustererKind::NmeSc, ClustererKind::Vbx);
assert_ne!(ClustererKind::NmeSc, ClustererKind::Ahc { threshold: 0.5 });
}
#[test]
fn default_pool_size_stays_within_clamp() {
let n = default_pool_size();
assert!((1..=4).contains(&n));
}
}