audio: move audio task into separate audio module

This commit is contained in:
2026-06-08 15:04:04 +02:00
parent aba2194032
commit 34d58e5d66
3 changed files with 93 additions and 91 deletions
+88
View File
@@ -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
View File
@@ -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
View File
@@ -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
})
}