diff --git a/Cargo.lock b/Cargo.lock index 13696f1..9116f32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -833,6 +833,13 @@ dependencies = [ "log", ] +[[package]] +name = "jack-playback" +version = "0.1.0" +dependencies = [ + "jack", +] + [[package]] name = "jack-sine" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 92f2530..dc4e6c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] resolver = "3" -members = ["engine", "examples/jack-sine", "lib", "tui"] +members = ["engine", "examples/jack-playback", "examples/jack-sine", "lib", "tui"] [workspace.dependencies] diff --git a/engine/src/audio_engine/dummy_ab.rs b/engine/src/audio_engine/dummy_ab.rs index 9ca550a..a26dcff 100644 --- a/engine/src/audio_engine/dummy_ab.rs +++ b/engine/src/audio_engine/dummy_ab.rs @@ -4,7 +4,7 @@ struct DummyAudioBackend {} impl DummyAudioBackend { fn new() -> Self { - Self { } + Self {} } } diff --git a/engine/src/audio_engine/jack_ab.rs b/engine/src/audio_engine/jack_ab.rs index 2e6ca5a..105e554 100644 --- a/engine/src/audio_engine/jack_ab.rs +++ b/engine/src/audio_engine/jack_ab.rs @@ -1,7 +1,7 @@ -use log::{info, warn}; -use std::error::Error; use crate::audio_engine::AudioBackend; use crossbeam_channel::bounded; +use log::{info, warn}; +use std::error::Error; #[cfg(feature = "jack")] use jack; @@ -35,84 +35,40 @@ pub(crate) struct JackStatus { #[cfg(feature = "jack")] impl AudioBackend for JackAudioBackend { - fn start_client(&mut self) -> Result<(), Box> { - info!("starting the jack client"); - let (client, status) = - jack::Client::new("termix_engine", jack::ClientOptions::default())?; - //self.status.client = Some(client); - self.status.status = Some(status); - - let out_port = client.register_port("termix_output", jack::AudioOut::default())?; - - let (tx, rx) = bounded(1_000_000); - struct State { - out_port: jack::Port, - rx: crossbeam_channel::Receiver, - frequency: f64, - frame_t: f64, - time: f64, - } - let process = jack::contrib::ClosureProcessHandler::with_state( - State { - out_port, - rx, - frequency: 220.0, - frame_t: 1.0 / client.sample_rate() as f64, - time: 0.0, - }, - |state, _, ps| -> jack::Control { - // Get output buffer - let out = state.out_port.as_mut_slice(ps); - - // Check frequency requests - while let Ok(f) = state.rx.try_recv() { - state.time = 0.0; - state.frequency = f; - } - - // Write output - for v in out.iter_mut() { - let x = state.frequency * state.time * 2.0 * std::f64::consts::PI; - let y = x.sin(); - *v = y as f32; - state.time += state.frame_t; - } - - // Continue as normal - jack::Control::Continue - }, - move |_, _, _| jack::Control::Continue, - ); - let active_client = client.activate_async((), process).unwrap(); - active_client - .as_client() - .connect_ports_by_name("termix_engine:termix_output", "system:playback_1") - .unwrap(); - active_client - .as_client() - .connect_ports_by_name("termix_engine:termix_output", "system:playback_2") - .unwrap(); - /* TODO: - * - Create a port for the main output - * - Define the jack options - * - buffer size - * - sample rate - * - Define the callback function - * - Activate the client - */ + fn start_client(&mut self) -> Result<(), Box> { Ok(()) } +} - fn init_client(&self) { - todo!() - } +#[cfg(feature = "jack")] +#[cfg(test)] +mod tests { + use jack::ClientOptions; - fn discover(&self) { - todo!() - } + use super::*; - fn stop_client(&self) -> Result<(), Box> { - todo!() + #[test] + fn start_jack_client() { + let (client, _status) = + jack::Client::new("list_clients_example", ClientOptions::NO_START_SERVER) + .expect("Failed to create JACK client"); + + // Get all ports, then extract the unique client names + let ports = client.ports(None, None, jack::PortFlags::empty()); + + let mut clients: Vec = ports + .iter() + .filter_map(|port| port.split(':').next().map(|s| s.to_string())) + .collect(); + + clients.sort(); + clients.dedup(); + + println!("JACK clients:"); + for c in clients { + assert_eq!(c, "test"); + println!(" - {}", c); + } } } @@ -126,16 +82,4 @@ impl AudioBackend for JackAudioBackend { warn!("jack support is not enabled"); Ok(()) } - - fn init_client(&self) { - unimplemented!() - } - - fn discover(&self) { - unimplemented!() - } - - fn stop_client(&self) -> Result<(), Box> { - todo!() - } } diff --git a/engine/src/audio_engine/mod.rs b/engine/src/audio_engine/mod.rs index 577bd2f..f767cef 100644 --- a/engine/src/audio_engine/mod.rs +++ b/engine/src/audio_engine/mod.rs @@ -1,8 +1,8 @@ use std::error::Error; -pub(crate) mod jack_ab; pub(crate) mod coreaudio_ab; pub(crate) mod dummy_ab; +pub(crate) mod jack_ab; pub(crate) trait AudioBackend { // Start a audio backend client diff --git a/engine/src/control_pane/grpc/mod.rs b/engine/src/control_pane/grpc/mod.rs index 5278a0d..7fee493 100644 --- a/engine/src/control_pane/grpc/mod.rs +++ b/engine/src/control_pane/grpc/mod.rs @@ -1,11 +1,24 @@ use std::result::Result; -use lib::termix::{self, audio_backend::{Backend, BackendList, DesiredAudioBacked, FILE_DESCRIPTOR_SET, SupportedAudioBackends, audio_backend_rpc_server::{AudioBackendRpc, AudioBackendRpcServer}}}; +use lib::termix::{ + self, + audio_backend::{ + AudioBackendDescription, Backend, BackendList, DesiredAudioBacked, FILE_DESCRIPTOR_SET, + SupportedAudioBackends, + audio_backend_rpc_server::{AudioBackendRpc, AudioBackendRpcServer}, + }, +}; use log::info; use tonic::{Response, transport::Server}; use tonic_reflection::server; -use crate::{audio_engine::{AudioBackend, jack_ab::{self, JackAudioBackend}}, control_pane::ControlPane}; +use crate::{ + audio_engine::{ + AudioBackend, + jack_ab::{self, JackAudioBackend}, + }, + control_pane::ControlPane, +}; pub(crate) struct Grpc { pub(crate) port: i32, @@ -39,6 +52,13 @@ pub struct TermixAudioBackend {} #[tonic::async_trait] impl AudioBackendRpc for TermixAudioBackend { + async fn stop_client( + &self, + _: tonic::Request<()>, + ) -> Result, tonic::Status> { + todo!() + } + async fn start_client( &self, request: tonic::Request, @@ -47,21 +67,23 @@ impl AudioBackendRpc for TermixAudioBackend { match request.get_ref().backend() { SupportedAudioBackends::AbUnspecified => { unimplemented!("unsupported backend"); - }, + } SupportedAudioBackends::AbJack => { info!("trying to use JACK as the backend"); let mut ab = jack_ab::JackAudioBackend::new(); ab.start_client(); - }, + } + SupportedAudioBackends::AbCoreaudio => todo!(), }; Ok(Response::new(())) } - - async fn stop_client( + + async fn describe_backend( &self, - _: tonic::Request<()>, - ) -> Result, tonic::Status> { - todo!() + request: tonic::Request, + ) -> Result, tonic::Status> { + info!("Describing the audio backend"); + todo!(); } async fn init_connection( &self, @@ -76,7 +98,7 @@ impl AudioBackendRpc for TermixAudioBackend { _: tonic::Request<()>, ) -> Result, tonic::Status> { info!("discovering available backends"); - + let mut response = BackendList::default(); let jack = JackAudioBackend::new(); if jack.feature_jack { diff --git a/examples/jack-playback/Cargo.toml b/examples/jack-playback/Cargo.toml new file mode 100644 index 0000000..f58f715 --- /dev/null +++ b/examples/jack-playback/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "jack-playback" +version = "0.1.0" +edition = "2024" + +[dependencies] +jack = "0.13.3" diff --git a/examples/jack-playback/src/main.rs b/examples/jack-playback/src/main.rs new file mode 100644 index 0000000..d2bf545 --- /dev/null +++ b/examples/jack-playback/src/main.rs @@ -0,0 +1,5 @@ +fn main() { + // 1. Create client + let (client, _status) = + jack::Client::new("rust_jack_simple", jack::ClientOptions::default()).unwrap(); +} diff --git a/lib/build.rs b/lib/build.rs index 05f3006..018f0c3 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -2,7 +2,7 @@ use std::{env, fs, io::Result, path::PathBuf}; fn main() -> Result<()> { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - + //let proto_dir = "proto"; //let paths = fs::read_dir(proto_dir).unwrap(); //for path in paths { @@ -13,13 +13,13 @@ fn main() -> Result<()> { // .compile_protos(&["proto/audio_backend.proto"], &["proto"]) // .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); //} - + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); tonic_prost_build::configure() .file_descriptor_set_path(out_dir.join("audio_backend_descriptor.bin")) .compile_protos(&["proto/audio_backend.proto"], &["proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); - + tonic_prost_build::configure() .file_descriptor_set_path(out_dir.join("track_descriptor.bin")) .compile_protos(&["proto/track.proto"], &["proto"])