1mod diarizer;
12#[cfg(not(target_family = "wasm"))]
13mod enrollment;
14#[cfg(all(feature = "silero", not(target_family = "wasm")))]
15mod labeler;
16mod profile;
17#[cfg(not(target_family = "wasm"))]
18mod store;
19
20use std::path::PathBuf;
21
22use crate::backend::{BackendError, ModelError};
23
24pub use diarizer::{CLUSTER_THRESHOLD, Diarizer, ENROLLED_THRESHOLD, settle};
25#[cfg(not(target_family = "wasm"))]
26pub use enrollment::{Enrolled, Enrollment, EnrollmentError, MIN_SAMPLES, centroid, consistency};
27#[cfg(all(feature = "silero", not(target_family = "wasm")))]
28pub use labeler::{
29 Heard, HeardUpdate, OWN_VOICE_THRESHOLD, SegmentLabeler, SegmentReader, Verdict,
30};
31#[cfg(any(test, feature = "test-support"))]
32pub use profile::enrolled_voice;
33pub use profile::{Profile, Role, SpeakerName, SpeakerNameError};
34#[cfg(not(target_family = "wasm"))]
35pub use store::{ProfileError, ProfileStore, Replace, StoredProfiles};
36
37pub trait Embedder {
43 fn embed(&mut self, window: &[f32]) -> Result<Option<Vec<f32>>, SpeakerError>;
50
51 fn dimension(&self) -> usize;
54}
55
56pub trait Engine: Embedder + Sized {
60 const NAME: &'static str;
61
62 type Config;
63
64 fn check_from(config: &Self::Config) -> Result<PathBuf, SpeakerError>;
66
67 fn load_from(config: Self::Config) -> Result<Self, SpeakerError>;
68}
69
70pub fn normalized(mut values: Vec<f32>) -> Option<Vec<f32>> {
73 let norm = values.iter().map(|value| value * value).sum::<f32>().sqrt();
74 if !norm.is_normal() {
75 return None;
76 }
77 for value in &mut values {
78 *value /= norm;
79 }
80 Some(values)
81}
82
83#[cfg(not(target_family = "wasm"))]
85pub fn validate_model_file(path: PathBuf, backend: &'static str) -> Result<PathBuf, SpeakerError> {
86 crate::backend::validate_model_file(path)
87 .map_err(|source| SpeakerError::Model { backend, source })
88}
89
90#[derive(Debug, thiserror::Error)]
91pub enum SpeakerError {
92 #[error("the {backend} model cannot be used")]
93 Model {
94 backend: &'static str,
95 #[source]
96 source: ModelError,
97 },
98
99 #[error("failed to load the {backend} model {model}")]
100 Load {
101 backend: &'static str,
102 model: String,
103 #[source]
104 source: BackendError,
105 },
106
107 #[error("{backend} inference failed")]
108 Inference {
109 backend: &'static str,
110 #[source]
111 source: BackendError,
112 },
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn normalizing_leaves_a_unit_vector_pointing_the_same_way() {
121 let unit = normalized(vec![3.0, 4.0]).expect("the vector has a direction");
122 assert!((unit[0] - 0.6).abs() < 1e-6);
123 assert!((unit[1] - 0.8).abs() < 1e-6);
124 }
125
126 #[test]
127 fn a_vector_without_direction_cannot_be_normalized() {
128 assert!(normalized(vec![0.0, 0.0]).is_none());
129 }
130}