use std::{fmt, rc::Rc, sync::Arc};
use gpui::{App, Context, SharedString, Subscription, WeakEntity};
use super::{SpeechState, state::defer_session_update};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AudioFormat {
sample_rate: u32,
channels: u16,
}
impl AudioFormat {
pub fn new(sample_rate: u32, channels: u16) -> Self {
Self {
sample_rate,
channels,
}
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn channels(&self) -> u16 {
self.channels
}
}
impl Default for AudioFormat {
fn default() -> Self {
Self::new(16_000, 1)
}
}
#[derive(Debug, Clone)]
pub enum SpeechError {
PermissionDenied,
NoInputDevice,
Unsupported,
Input(Arc<anyhow::Error>),
Recognizer(Arc<anyhow::Error>),
}
impl SpeechError {
pub fn input(error: impl Into<anyhow::Error>) -> Self {
Self::Input(Arc::new(error.into()))
}
pub fn recognizer(error: impl Into<anyhow::Error>) -> Self {
Self::Recognizer(Arc::new(error.into()))
}
}
impl fmt::Display for SpeechError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PermissionDenied => f.write_str("microphone access was denied"),
Self::NoInputDevice => f.write_str("no audio input device is available"),
Self::Unsupported => f.write_str("speech input is not supported"),
Self::Input(error) => write!(f, "audio input failed: {error:#}"),
Self::Recognizer(error) => write!(f, "speech recognition failed: {error:#}"),
}
}
}
impl std::error::Error for SpeechError {}
pub trait SpeechRecognizer: 'static {
fn audio_format(&self) -> AudioFormat {
AudioFormat::default()
}
fn is_available(&self, _cx: &App) -> bool {
true
}
fn start(
&self,
sink: SpeechSink,
cx: &mut App,
) -> Result<Box<dyn RecognitionSession>, SpeechError>;
}
impl<T: SpeechRecognizer + ?Sized> SpeechRecognizer for Rc<T> {
fn audio_format(&self) -> AudioFormat {
(**self).audio_format()
}
fn is_available(&self, cx: &App) -> bool {
(**self).is_available(cx)
}
fn start(
&self,
sink: SpeechSink,
cx: &mut App,
) -> Result<Box<dyn RecognitionSession>, SpeechError> {
(**self).start(sink, cx)
}
}
pub trait RecognitionSession: 'static {
fn push_audio(&mut self, samples: &[i16], cx: &mut App);
fn finish(&mut self, cx: &mut App);
}
#[derive(Clone)]
pub struct SpeechSink {
pub(super) state: WeakEntity<SpeechState>,
pub(super) session: usize,
}
impl SpeechSink {
pub fn ready(&self, cx: &mut App) {
self.apply(cx, |state, cx| state.on_ready(cx));
}
pub fn hypothesis(&self, text: impl Into<SharedString>, cx: &mut App) {
let text = text.into();
self.apply(cx, move |state, cx| state.on_hypothesis(text, cx));
}
pub fn phrase(&self, text: impl Into<SharedString>, cx: &mut App) {
let text = text.into();
self.apply(cx, move |state, cx| state.on_phrase(text, cx));
}
pub fn finish(&self, cx: &mut App) {
self.apply(cx, |state, cx| state.on_finish(cx));
}
pub fn error(&self, error: SpeechError, cx: &mut App) {
self.apply(cx, move |state, cx| state.on_error(error, cx));
}
fn apply(
&self,
cx: &mut App,
f: impl FnOnce(&mut SpeechState, &mut Context<SpeechState>) + 'static,
) {
defer_session_update(self.state.clone(), self.session, cx, f);
}
}
pub trait AudioInput: 'static {
fn start(
&self,
format: AudioFormat,
sink: AudioSink,
cx: &mut App,
) -> Result<Subscription, SpeechError>;
}
impl<T: AudioInput + ?Sized> AudioInput for Rc<T> {
fn start(
&self,
format: AudioFormat,
sink: AudioSink,
cx: &mut App,
) -> Result<Subscription, SpeechError> {
(**self).start(format, sink, cx)
}
}
#[derive(Clone)]
pub struct AudioSink {
pub(super) state: WeakEntity<SpeechState>,
pub(super) session: usize,
}
impl AudioSink {
pub fn push(&self, samples: Vec<i16>, cx: &mut App) {
self.apply(cx, move |state, cx| state.on_audio(&samples, cx));
}
pub fn error(&self, error: SpeechError, cx: &mut App) {
self.apply(cx, move |state, cx| state.on_error(error, cx));
}
fn apply(
&self,
cx: &mut App,
f: impl FnOnce(&mut SpeechState, &mut Context<SpeechState>) + 'static,
) {
defer_session_update(self.state.clone(), self.session, cx, f);
}
}