audio: Implement save/restore of jack port configuration
This commit is contained in:
+86
-6
@@ -1,5 +1,6 @@
|
|||||||
use jack::{AudioIn, AudioOut, ClientOptions};
|
use jack::{AudioIn, AudioOut, ClientOptions, NotificationHandler};
|
||||||
use oximedia_metering::vu_meter::VuMeter;
|
use oximedia_metering::vu_meter::VuMeter;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::*;
|
use tokio::sync::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -38,10 +39,70 @@ pub struct TtsOutStream {
|
|||||||
pub sample_rate: u32
|
pub sample_rate: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
struct AudioConfig {
|
||||||
|
mic_in_connections: Vec<String>,
|
||||||
|
tts_out_connections: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioConfig {
|
||||||
|
pub fn load() -> Self {
|
||||||
|
if let Ok(contents) = std::fs::read_to_string("audio.json") {
|
||||||
|
serde_json::from_str(contents.as_str()).unwrap()
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Notify {
|
||||||
|
config: AudioConfig,
|
||||||
|
mic_port: jack::Port<jack::Unowned>,
|
||||||
|
tts_port: jack::Port<jack::Unowned>,
|
||||||
|
log: mpsc::Sender<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotificationHandler for Notify {
|
||||||
|
fn ports_connected(
|
||||||
|
&mut self,
|
||||||
|
client: &jack::Client,
|
||||||
|
port_id_a: jack::PortId,
|
||||||
|
port_id_b: jack::PortId,
|
||||||
|
are_connected: bool,
|
||||||
|
) {
|
||||||
|
let port_a = client.port_by_id(port_id_a).unwrap();
|
||||||
|
let port_b = client.port_by_id(port_id_b).unwrap();
|
||||||
|
|
||||||
|
let (stream_name, other_port, target_cfg) = if port_b == self.mic_port {
|
||||||
|
("Microphone input", port_a, &mut self.config.mic_in_connections)
|
||||||
|
} else if port_a == self.tts_port {
|
||||||
|
("TTS output", port_b, &mut self.config.tts_out_connections)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(port_name) = other_port.name() {
|
||||||
|
if are_connected {
|
||||||
|
self.log.blocking_send(format!("{} connected to {}", stream_name, port_name)).unwrap();
|
||||||
|
target_cfg.push(port_name);
|
||||||
|
} else {
|
||||||
|
self.log.blocking_send(format!("{} disconnected from {}", stream_name, port_name)).unwrap();
|
||||||
|
target_cfg.retain(|x| { x != &port_name} );
|
||||||
|
}
|
||||||
|
|
||||||
|
let save_data = serde_json::to_string_pretty(&self.config).unwrap();
|
||||||
|
std::fs::write("audio.json", save_data).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputControl, MicStream, TtsOutStream) {
|
pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputControl, MicStream, TtsOutStream) {
|
||||||
|
|
||||||
let (exit_tx, exit_rx) = oneshot::channel();
|
let (exit_tx, exit_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
let config = AudioConfig::load();
|
||||||
|
|
||||||
let (mic_audio_sink, mic_audio_src) = mpsc::channel(32);
|
let (mic_audio_sink, mic_audio_src) = mpsc::channel(32);
|
||||||
let (tts_audio_sink, mut tts_audio_src) = mpsc::channel(32);
|
let (tts_audio_sink, mut tts_audio_src) = mpsc::channel(32);
|
||||||
let (volume_sink, volume_src) = watch::channel(0.);
|
let (volume_sink, volume_src) = watch::channel(0.);
|
||||||
@@ -51,11 +112,31 @@ pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputCo
|
|||||||
let mut tts_port = client.register_port("tts-out", AudioOut::default()).unwrap();
|
let mut tts_port = client.register_port("tts-out", AudioOut::default()).unwrap();
|
||||||
let rate = client.sample_rate();
|
let rate = client.sample_rate();
|
||||||
|
|
||||||
if let Ok(_) = client.connect_ports_by_name("mixxx-mic-1:capture_MONO", mic_port.name().unwrap().as_str()) {
|
let mic_name = mic_port.name().unwrap();
|
||||||
messages.send("Connected to audio.".into()).await.unwrap();
|
let tts_name = tts_port.name().unwrap();
|
||||||
|
|
||||||
|
for port in &config.mic_in_connections {
|
||||||
|
if let Ok(_) = client.connect_ports_by_name(&port, &mic_name) {
|
||||||
|
messages.send(format!("Connected mic to {}", port)).await.unwrap();
|
||||||
} else {
|
} else {
|
||||||
messages.send("Failed to reconnect to audio.".into()).await.unwrap();
|
messages.send(format!("Failed to reconnect mic to {}.", port)).await.unwrap();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for port in &config.tts_out_connections {
|
||||||
|
if let Ok(_) = client.connect_ports_by_name(&tts_name, &port) {
|
||||||
|
messages.send(format!("Connected TTS output to {}", port)).await.unwrap();
|
||||||
|
} else {
|
||||||
|
messages.send(format!("Failed to reconnect TTS output to {}.", port)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let notifier = Notify {
|
||||||
|
config,
|
||||||
|
mic_port: mic_port.clone_unowned(),
|
||||||
|
tts_port: tts_port.clone_unowned(),
|
||||||
|
log: messages.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let mut meter = VuMeter::new(rate.into(), 1, None);
|
let mut meter = VuMeter::new(rate.into(), 1, None);
|
||||||
let mut tts_output_buf = vec![];
|
let mut tts_output_buf = vec![];
|
||||||
@@ -78,7 +159,6 @@ pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputCo
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if let Ok(mut next_outbuf) = tts_audio_src.try_recv() {
|
if let Ok(mut next_outbuf) = tts_audio_src.try_recv() {
|
||||||
tts_output_buf.append(&mut next_outbuf);
|
tts_output_buf.append(&mut next_outbuf);
|
||||||
}
|
}
|
||||||
@@ -100,7 +180,7 @@ pub async fn start_audio_input(messages: &mpsc::Sender<String>) -> (AudioInputCo
|
|||||||
});
|
});
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let async_client = client.activate_async((), handler).unwrap();
|
let async_client = client.activate_async(notifier, handler).unwrap();
|
||||||
|
|
||||||
exit_rx.await.unwrap();
|
exit_rx.await.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ mod tts;
|
|||||||
mod prediction;
|
mod prediction;
|
||||||
mod audio;
|
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.
|
// TODO: We should be able to delete entries from the conversation, or at least go back and edit something I said.
|
||||||
// TODO: I want a "mark" command or keyboard shortcut, that inserts a marker into the log, so I know where to come back for the next speaking segment.
|
// TODO: I want a "mark" command or keyboard shortcut, that inserts a marker into the log, so I know where to come back for the next speaking segment.
|
||||||
// TODO: If we insert text without speaking, this should be indicated visually somehow
|
// TODO: If we insert text without speaking, this should be indicated visually somehow
|
||||||
|
|||||||
Reference in New Issue
Block a user