1use crate::cognitive::CognitiveOutputSpeech;
2use crate::cognitive_output_audio::CognitiveOutputAudio;
3use crate::peer_input_audio::{PEER_INPUT_AUDIO_CHUNK_DURATION, PeerInputAudio};
4use crate::plugin::Plugin;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8
9pub fn calculate_chunk_indices(base_index: u64, start_secs: f64, end_secs: f64) -> (u64, u64) {
17 let chunk_duration_secs = PEER_INPUT_AUDIO_CHUNK_DURATION.as_secs_f64();
18 let start_index = base_index + (start_secs / chunk_duration_secs).floor() as u64;
19 let end_index = base_index + (end_secs / chunk_duration_secs).ceil() as u64;
20 (start_index, end_index)
21}
22
23#[derive(Clone)]
25pub struct SpeechDetected(Arc<tokio::sync::Notify>);
26
27impl SpeechDetected {
28 pub fn new(notify: Arc<tokio::sync::Notify>) -> Self {
29 Self(notify)
30 }
31 pub fn notify(&self) {
32 self.0.notify_waiters();
33 }
34}
35
36#[derive(
38 Serialize,
39 Deserialize,
40 PartialEq,
41 Eq,
42 Hash,
43 Debug,
44 Clone,
45 JsonSchema,
46 derive_more::Display,
47 derive_more::From,
48 derive_more::Deref,
49)]
50pub struct SpeakerId(pub String);
51
52impl SpeakerId {
53 pub fn new(speaker_id: String) -> Self {
54 Self(speaker_id)
55 }
56}
57
58#[derive(Deserialize, Serialize, Default, Clone, Debug, JsonSchema)]
60pub struct Word {
61 pub start_index: Option<u64>,
63 pub end_index: Option<u64>,
65 pub word: String,
67 pub speaker_hint: Option<String>,
69}
70
71#[derive(Deserialize, Serialize, Default, Clone, Debug, JsonSchema)]
73pub struct SpeechTranscript {
74 pub start_index: u64,
76 pub end_index: u64,
78 pub transcript: String,
80 pub words: Vec<Word>,
82}
83
84#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
86pub enum InputVoiceAudio {
87 Voice(PeerInputAudioIndexed),
89 NoVoice(PeerInputAudioIndexed),
91}
92
93#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
95pub struct PeerInputAudioIndexed {
96 pub audio: PeerInputAudio,
98 pub index: u64,
100}
101
102#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
103pub struct CosineSimilarity(pub f32);
104
105#[derive(Clone, Debug)]
106pub struct WordOverlap {
107 pub start_index: u64,
108 pub end_index: u64,
109 pub overlaps: std::collections::HashMap<InternalSpeaker, u64>,
110 pub word: String,
111}
112
113pub type SpeakerHeuristicCallback = std::sync::Arc<
114 dyn Fn(&[WordOverlap], &[SpeakerSegment]) -> Vec<Option<SpeakerId>> + Send + Sync,
115>;
116
117#[derive(Serialize, Deserialize, Debug, Clone)]
118pub enum InternalSpeaker {
119 Unknown(Option<(SpeakerId, CosineSimilarity)>),
120 Recognized(SpeakerId),
121}
122
123impl PartialEq for InternalSpeaker {
124 fn eq(&self, other: &Self) -> bool {
125 match (self, other) {
126 (InternalSpeaker::Unknown(a), InternalSpeaker::Unknown(b)) => match (a, b) {
127 (Some((id_a, score_a)), Some((id_b, score_b))) => {
128 id_a == id_b && score_a.0 == score_b.0
129 }
130 (None, None) => true,
131 _ => false,
132 },
133 (InternalSpeaker::Recognized(a), InternalSpeaker::Recognized(b)) => a == b,
134 _ => false,
135 }
136 }
137}
138
139impl Eq for InternalSpeaker {}
140
141impl std::hash::Hash for InternalSpeaker {
142 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
143 match self {
144 InternalSpeaker::Unknown(opt) => {
145 0_u8.hash(state);
146 if let Some((id, score)) = opt {
147 1_u8.hash(state);
148 id.hash(state);
149 score.0.to_bits().hash(state);
150 } else {
151 0_u8.hash(state);
152 }
153 }
154 InternalSpeaker::Recognized(id) => {
155 1_u8.hash(state);
156 id.hash(state);
157 }
158 }
159 }
160}
161
162impl From<InternalSpeaker> for crate::peer_input::Speaker {
163 fn from(val: InternalSpeaker) -> Self {
164 match val {
165 InternalSpeaker::Unknown(None) => crate::peer_input::Speaker::Unknown(None),
166 InternalSpeaker::Unknown(Some((id, _))) => crate::peer_input::Speaker::Unknown(Some(
167 SpeakerId(format!("Maybe {}", id).to_string()),
168 )),
169 InternalSpeaker::Recognized(id) => {
170 crate::peer_input::Speaker::Recognized(SpeakerId(id.to_string()))
171 }
172 }
173 }
174}
175
176#[derive(Clone, Debug, Serialize, Deserialize)]
177pub struct SpeakerSegment {
178 pub speaker: InternalSpeaker,
179 pub start_index: u64,
180 pub end_index: u64,
181}
182
183impl From<InputVoiceAudio> for PeerInputAudio {
184 fn from(input_voice_audio: InputVoiceAudio) -> Self {
185 match input_voice_audio {
186 InputVoiceAudio::Voice(peer_input_audio) => peer_input_audio.audio,
187 InputVoiceAudio::NoVoice(peer_input_audio) => peer_input_audio.audio,
188 }
189 }
190}
191
192impl From<InputVoiceAudio> for [i32; crate::peer_input_audio::PEER_INPUT_AUDIO_CHUNK_SIZE] {
193 fn from(input_voice_audio: InputVoiceAudio) -> Self {
194 let peer_input_audio: PeerInputAudio = input_voice_audio.into();
195 peer_input_audio.into()
196 }
197}
198
199use crate::sync::{broadcast, mpsc};
200use async_trait::async_trait;
201#[async_trait]
202pub trait STTPlugin: Plugin + Send + Sync {
203 async fn start(
204 &self,
205 audio_rx: mpsc::Receiver<InputVoiceAudio>,
206 transcript_tx: mpsc::Sender<SpeechTranscript>,
207 speech_detected: SpeechDetected,
208 ) -> Result<(), String>;
209}
210#[async_trait]
211pub trait TTSPlugin: Plugin + Send + Sync {
212 async fn start(
213 &self,
214 speech_rx: broadcast::Receiver<CognitiveOutputSpeech>,
215 audio_tx: mpsc::Sender<CognitiveOutputAudio>,
216 ) -> Result<(), String>;
217}
218#[async_trait]
219pub trait DiarizationPlugin: Plugin + Send + Sync {
220 async fn start(
221 &self,
222 audio_rx: broadcast::Receiver<InputVoiceAudio>,
223 segment_tx: mpsc::Sender<SpeakerSegment>,
224 ) -> Result<(), String>;
225
226 fn heuristic(&self) -> Option<crate::speech_to_text::SpeakerHeuristicCallback> {
227 None
228 }
229}