audio: move audio task into separate audio module
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
use jack::{AudioIn, ClientOptions};
|
||||
use oximedia_metering::vu_meter::VuMeter;
|
||||
use tokio::sync::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JackClientRef {
|
||||
killswitch: Option<oneshot::Sender<()>>
|
||||
}
|
||||
|
||||
impl Drop for JackClientRef {
|
||||
fn drop(&mut self) {
|
||||
self.killswitch.take().unwrap().send(()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AudioInputControl {
|
||||
volume_src: watch::Receiver<f64>,
|
||||
_jack_client: JackClientRef
|
||||
}
|
||||
|
||||
impl AudioInputControl {
|
||||
pub async fn next(&mut self) -> f64 {
|
||||
self.volume_src.changed().await.unwrap();
|
||||
*self.volume_src.borrow_and_update()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MicStream {
|
||||
pub src: mpsc::Receiver<Vec<f32>>,
|
||||
pub sample_rate: u32
|
||||
}
|
||||
|
||||
pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputControl, MicStream) {
|
||||
|
||||
let (exit_tx, exit_rx) = oneshot::channel();
|
||||
|
||||
let (mic_audio_sink, mic_audio_src) = mpsc::channel(32);
|
||||
let (volume_sink, volume_src) = watch::channel(0.);
|
||||
|
||||
let (client, _status) = jack::Client::new("Eva-Cohost", ClientOptions::default() | ClientOptions::SESSION_ID).unwrap();
|
||||
let mic_port = client.register_port("microphone-in", AudioIn::default()).unwrap();
|
||||
let rate = client.sample_rate();
|
||||
|
||||
if let Ok(_) = client.connect_ports_by_name("mixxx-mic-1:capture_MONO", mic_port.name().unwrap().as_str()) {
|
||||
messages.send("Connected to audio.".into()).await.unwrap();
|
||||
} else {
|
||||
messages.send("Failed to reconnect to audio.".into()).await.unwrap();
|
||||
}
|
||||
|
||||
let mut meter = VuMeter::new(rate.into(), 1, None);
|
||||
|
||||
let handler = jack::contrib::ClosureProcessHandler::new(move |_client, scope| {
|
||||
if mic_port.connected_count().unwrap() > 0 {
|
||||
let buf: Vec<_> = mic_port.as_slice(scope).iter().copied().collect();
|
||||
meter.process_interleaved(&buf);
|
||||
mic_audio_sink.blocking_send(buf).unwrap();
|
||||
|
||||
volume_sink.send_if_modified(|v| {
|
||||
let next_vu = meter.channel_vu(0).unwrap();
|
||||
if *v != next_vu {
|
||||
*v = next_vu;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
jack::Control::Continue
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let async_client = client.activate_async((), handler).unwrap();
|
||||
|
||||
exit_rx.await.unwrap();
|
||||
|
||||
async_client.deactivate().unwrap();
|
||||
});
|
||||
|
||||
(AudioInputControl {
|
||||
volume_src,
|
||||
_jack_client: JackClientRef { killswitch: Some(exit_tx) }
|
||||
}, MicStream {
|
||||
sample_rate: rate,
|
||||
src: mic_audio_src
|
||||
})
|
||||
}
|
||||
+3
-2
@@ -13,13 +13,14 @@ use futures::{StreamExt, future::FutureExt};
|
||||
use ratatui::prelude::*;
|
||||
use tui_skeleton::{AnimationMode, SkeletonText};
|
||||
|
||||
use crate::{prediction::{BandcampResult, PossibleResponse}, scene::{ConversationEntry, Scene, StageActions, StageDirection}, transcription::{AudioInputControl, TranscriptionControl, start_audio_input}, tts::{TtsControl, start_tts}};
|
||||
use crate::{audio::{AudioInputControl, start_audio_input}, prediction::{BandcampResult, PossibleResponse}, scene::{ConversationEntry, Scene, StageActions, StageDirection}, transcription::TranscriptionControl, tts::{TtsControl, start_tts}};
|
||||
|
||||
mod scene;
|
||||
mod events;
|
||||
mod transcription;
|
||||
mod tts;
|
||||
mod prediction;
|
||||
mod audio;
|
||||
|
||||
// TODO: We should have a separate 'state.json' file, which remembers jack connections, and the world time for the show to end. Then we only update the 'time remaining' field in the scene and only deal with relative durations inside the scene data
|
||||
// TODO: We should be able to delete entries from the conversation, or at least go back and edit something I said.
|
||||
@@ -643,7 +644,7 @@ async fn main() {
|
||||
transcription_result = app.transcription.next() => {
|
||||
app.next_actions.push(ConversationEntry::User(transcription_result));
|
||||
app.regenerate_responses();
|
||||
}
|
||||
},
|
||||
maybe_event = event => {
|
||||
match maybe_event {
|
||||
Some(Ok(event)) => {
|
||||
|
||||
+2
-89
@@ -1,12 +1,10 @@
|
||||
use std::{io::Read, sync::{Arc, Mutex}};
|
||||
|
||||
use async_openai::{Client, config::OpenAIConfig, types::{InputSource, audio::{AudioInput, CreateTranscriptionRequest}}};
|
||||
use jack::{AudioIn, ClientOptions};
|
||||
use oximedia_metering::vu_meter::VuMeter;
|
||||
use tempfile::SpooledData;
|
||||
use tokio::sync::{mpsc, oneshot, watch};
|
||||
use tokio::sync::{mpsc, watch};
|
||||
|
||||
use crate::events::AudioRecordRequest;
|
||||
use crate::{audio::MicStream, events::AudioRecordRequest};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TranscriptionControl {
|
||||
@@ -28,36 +26,6 @@ impl TranscriptionControl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JackClientRef {
|
||||
killswitch: Option<oneshot::Sender<()>>
|
||||
}
|
||||
|
||||
impl Drop for JackClientRef {
|
||||
fn drop(&mut self) {
|
||||
self.killswitch.take().unwrap().send(()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AudioInputControl {
|
||||
volume_src: watch::Receiver<f64>,
|
||||
_jack_client: JackClientRef
|
||||
}
|
||||
|
||||
impl AudioInputControl {
|
||||
pub async fn next(&mut self) -> f64 {
|
||||
self.volume_src.changed().await.unwrap();
|
||||
*self.volume_src.borrow_and_update()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MicStream {
|
||||
src: mpsc::Receiver<Vec<f32>>,
|
||||
sample_rate: u32
|
||||
}
|
||||
|
||||
struct RcFile<T>(Arc<Mutex<T>>);
|
||||
|
||||
impl<T: std::io::Write> std::io::Write for RcFile<T> {
|
||||
@@ -151,58 +119,3 @@ pub async fn start_transcription(mut mic_src: MicStream) -> TranscriptionControl
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputControl, MicStream) {
|
||||
|
||||
let (exit_tx, exit_rx) = oneshot::channel();
|
||||
|
||||
let (mic_audio_sink, mic_audio_src) = mpsc::channel(32);
|
||||
let (volume_sink, volume_src) = watch::channel(0.);
|
||||
|
||||
let (client, _status) = jack::Client::new("Eva-Cohost", ClientOptions::default() | ClientOptions::SESSION_ID).unwrap();
|
||||
let mic_port = client.register_port("microphone-in", AudioIn::default()).unwrap();
|
||||
let rate = client.sample_rate();
|
||||
|
||||
if let Ok(_) = client.connect_ports_by_name("mixxx-mic-1:capture_MONO", mic_port.name().unwrap().as_str()) {
|
||||
messages.send("Connected to audio.".into()).await.unwrap();
|
||||
} else {
|
||||
messages.send("Failed to reconnect to audio.".into()).await.unwrap();
|
||||
}
|
||||
|
||||
let mut meter = VuMeter::new(rate.into(), 1, None);
|
||||
|
||||
let handler = jack::contrib::ClosureProcessHandler::new(move |_client, scope| {
|
||||
if mic_port.connected_count().unwrap() > 0 {
|
||||
let buf: Vec<_> = mic_port.as_slice(scope).iter().copied().collect();
|
||||
meter.process_interleaved(&buf);
|
||||
mic_audio_sink.blocking_send(buf).unwrap();
|
||||
|
||||
volume_sink.send_if_modified(|v| {
|
||||
let next_vu = meter.channel_vu(0).unwrap();
|
||||
if *v != next_vu {
|
||||
*v = next_vu;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
jack::Control::Continue
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let async_client = client.activate_async((), handler).unwrap();
|
||||
|
||||
exit_rx.await.unwrap();
|
||||
|
||||
async_client.deactivate().unwrap();
|
||||
});
|
||||
|
||||
(AudioInputControl {
|
||||
volume_src,
|
||||
_jack_client: JackClientRef { killswitch: Some(exit_tx) }
|
||||
}, MicStream {
|
||||
sample_rate: rate,
|
||||
src: mic_audio_src
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user