From 4810b19eea9196a5d319b70f8770cafb7b67ed13 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Fri, 5 Dec 2025 19:32:32 +0100 Subject: [PATCH] A lot of pointless stuff --- Cargo.lock | 1 + engine/Cargo.toml | 1 + engine/src/audio_engine/coreaudio_ab.rs | 19 +++++ engine/src/audio_engine/dummy_ab.rs | 15 ++++ engine/src/audio_engine/jack_ab.rs | 107 ++++++++++++++++++++++-- engine/src/audio_engine/mod.rs | 10 +-- engine/src/control_pane/grpc/mod.rs | 47 +++++++---- lib/build.rs | 18 +++- lib/proto/audio_backend.proto | 37 ++++++-- lib/proto/track.proto | 19 +++++ 10 files changed, 238 insertions(+), 36 deletions(-) create mode 100644 engine/src/audio_engine/dummy_ab.rs create mode 100644 lib/proto/track.proto diff --git a/Cargo.lock b/Cargo.lock index dac5317..13696f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,7 @@ version = "0.1.0" dependencies = [ "clap", "coreaudio-rs", + "crossbeam-channel", "env_logger", "jack", "lib", diff --git a/engine/Cargo.toml b/engine/Cargo.toml index e401817..20aa243 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] clap = { version = "4.5.53", features = ["derive"] } coreaudio-rs = { version = "0.13.0", optional = true } +crossbeam-channel = "0.5.15" env_logger = "0.11.8" jack = {version = "0.13.3", optional = true } lib = { path = "../lib/" } diff --git a/engine/src/audio_engine/coreaudio_ab.rs b/engine/src/audio_engine/coreaudio_ab.rs index e69de29..f1d4ff6 100644 --- a/engine/src/audio_engine/coreaudio_ab.rs +++ b/engine/src/audio_engine/coreaudio_ab.rs @@ -0,0 +1,19 @@ +use crate::audio_engine::AudioBackend; + +pub(crate) struct CoreAudioBackend {} + +impl CoreAudioBackend {} + +#[cfg(feature = "coreaudio")] +impl AudioBackend for CoreAudioBackend { + fn start_client(&mut self) -> Result<(), Box> { + Ok(()) + } +} + +#[cfg(not(feature = "coreaudio"))] +impl AudioBackend for CoreAudioBackend { + fn start_client(&mut self) -> Result<(), Box> { + todo!() + } +} diff --git a/engine/src/audio_engine/dummy_ab.rs b/engine/src/audio_engine/dummy_ab.rs new file mode 100644 index 0000000..9ca550a --- /dev/null +++ b/engine/src/audio_engine/dummy_ab.rs @@ -0,0 +1,15 @@ +use crate::audio_engine::AudioBackend; + +struct DummyAudioBackend {} + +impl DummyAudioBackend { + fn new() -> Self { + Self { } + } +} + +impl AudioBackend for DummyAudioBackend { + fn start_client(&mut self) -> Result<(), Box> { + todo!() + } +} diff --git a/engine/src/audio_engine/jack_ab.rs b/engine/src/audio_engine/jack_ab.rs index d7c2670..2e6ca5a 100644 --- a/engine/src/audio_engine/jack_ab.rs +++ b/engine/src/audio_engine/jack_ab.rs @@ -1,24 +1,106 @@ +use log::{info, warn}; +use std::error::Error; use crate::audio_engine::AudioBackend; +use crossbeam_channel::bounded; + +#[cfg(feature = "jack")] +use jack; pub(crate) struct JackAudioBackend { pub(crate) feature_jack: bool, pub(crate) running: bool, + status: JackStatus, } impl JackAudioBackend { pub(crate) fn new() -> Self { - let feature_jack = cfg!(feature = "jack"); // TODO: It should be retrieved from the system let running = true; - Self { feature_jack, running } + let status = JackStatus::default(); + Self { + feature_jack, + running, + status, + } } } +#[cfg(feature = "jack")] +#[derive(Default)] +pub(crate) struct JackStatus { + client: Option, + status: Option, +} + #[cfg(feature = "jack")] impl AudioBackend for JackAudioBackend { - fn start_client(&self) { - todo!() + 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 + */ + Ok(()) } fn init_client(&self) { @@ -28,12 +110,21 @@ impl AudioBackend for JackAudioBackend { fn discover(&self) { todo!() } + + fn stop_client(&self) -> Result<(), Box> { + todo!() + } } +#[cfg(not(feature = "jack"))] +#[derive(Default)] +pub(crate) struct JackStatus {} + #[cfg(not(feature = "jack"))] impl AudioBackend for JackAudioBackend { - fn start_client(&self) { - unimplemented!() + fn start_client(&mut self) -> Result<(), Box> { + warn!("jack support is not enabled"); + Ok(()) } fn init_client(&self) { @@ -43,4 +134,8 @@ impl AudioBackend for JackAudioBackend { 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 a007167..577bd2f 100644 --- a/engine/src/audio_engine/mod.rs +++ b/engine/src/audio_engine/mod.rs @@ -1,12 +1,12 @@ +use std::error::Error; + pub(crate) mod jack_ab; +pub(crate) mod coreaudio_ab; +pub(crate) mod dummy_ab; pub(crate) trait AudioBackend { // Start a audio backend client // It should be executed either on the startup, // or when the audio backend is switched - fn start_client(&self); - // Initialization of the client should happen - // when a project is opened. - fn init_client(&self); - fn discover(&self); + fn start_client(&mut self) -> Result<(), Box>; } diff --git a/engine/src/control_pane/grpc/mod.rs b/engine/src/control_pane/grpc/mod.rs index 216509c..5278a0d 100644 --- a/engine/src/control_pane/grpc/mod.rs +++ b/engine/src/control_pane/grpc/mod.rs @@ -1,13 +1,11 @@ -use lib::termix::audio_backend::{ - self, Backend, BackendList, FILE_DESCRIPTOR_SET, - audio_backend_server::{AudioBackend, AudioBackendServer}, -}; +use std::result::Result; + +use lib::termix::{self, audio_backend::{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::jack_ab::JackAudioBackend, control_pane::ControlPane}; +use crate::{audio_engine::{AudioBackend, jack_ab::{self, JackAudioBackend}}, control_pane::ControlPane}; pub(crate) struct Grpc { pub(crate) port: i32, @@ -28,7 +26,7 @@ impl ControlPane for Grpc { server .add_service(audio_backend_reflections) - .add_service(AudioBackendServer::new(audio_backend)) + .add_service(AudioBackendRpcServer::new(audio_backend)) .serve(addr) .await?; @@ -40,34 +38,51 @@ impl ControlPane for Grpc { pub struct TermixAudioBackend {} #[tonic::async_trait] -impl AudioBackend for TermixAudioBackend { +impl AudioBackendRpc for TermixAudioBackend { async fn start_client( &self, - requesa: tonic::Request<()>, - ) -> std::result::Result, tonic::Status> { + request: tonic::Request, + ) -> Result, tonic::Status> { info!("starting the audio backend client"); + 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(); + }, + }; + Ok(Response::new(())) + } + + async fn stop_client( + &self, + _: tonic::Request<()>, + ) -> Result, tonic::Status> { todo!() } - async fn init_connection( &self, request: tonic::Request<()>, - ) -> std::result::Result, tonic::Status> { + ) -> Result, tonic::Status> { info!("initializing the connection to the audio backend"); todo!() } async fn get_available_backends( &self, - request: tonic::Request<()>, + _: tonic::Request<()>, ) -> Result, tonic::Status> { info!("discovering available backends"); + let mut response = BackendList::default(); let jack = JackAudioBackend::new(); if jack.feature_jack { - response.backends.push(Backend { - name: "jack".to_string(), - }); + response.backends.push(Backend { + name: "jack".to_string(), + }); } Ok(Response::new(response)) } diff --git a/lib/build.rs b/lib/build.rs index b928458..05f3006 100644 --- a/lib/build.rs +++ b/lib/build.rs @@ -1,17 +1,29 @@ -use std::{env, io::Result, path::PathBuf}; +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 { - // prost_build::compile_protos(&[path.unwrap().path()], &[proto_dir])?; + // let path_str = path.unwrap().path().to_str().unwrap().to_string(); + // let descriptor = format!("{}_descriptor.bin", path_str); + // 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)); //} - // + 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"]) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); Ok(()) } diff --git a/lib/proto/audio_backend.proto b/lib/proto/audio_backend.proto index fea129a..e63b401 100644 --- a/lib/proto/audio_backend.proto +++ b/lib/proto/audio_backend.proto @@ -3,12 +3,41 @@ package termix.audio_backend; import "google/protobuf/empty.proto"; -service AudioBackend { - rpc StartClient(google.protobuf.Empty) returns (google.protobuf.Empty); +service AudioBackendRPC { + // Stop the active audio server + rpc StopClient(google.protobuf.Empty) returns (google.protobuf.Empty); + // Start the audio server of choice + rpc StartClient(DesiredAudioBacked) returns (google.protobuf.Empty); + // Get information about the possible audio backend configuration options + rpc DescribeBackend(DesiredAudioBacked) returns (AudioBackendDescription); rpc InitConnection(google.protobuf.Empty) returns (google.protobuf.Empty); rpc GetAvailableBackends(google.protobuf.Empty) returns (BackendList); } +enum SupportedAudioBackends { + AB_UNSPECIFIED = 0; + AB_JACK = 1; + AB_COREAUDIO = 2; +} + +message DesiredAudioBacked { + SupportedAudioBackends backend = 1; + CoreAudioOptions core_audio_opts = 2; +} + +message AudioBackendDescription { + CoreAudioAvailableOptions core_audio_description = 1; +} + +message CoreAudioAvailableOptions { + repeated string input_devices = 1; + repeated string output_devices = 2; +} + +message CoreAudioOptions { + string input_device = 1; +} + message BackendList { repeated Backend backends = 1; } @@ -16,7 +45,3 @@ message BackendList { message Backend { string name = 1; } - -message Shirt { - google.protobuf.Empty dummy = 1; -} diff --git a/lib/proto/track.proto b/lib/proto/track.proto new file mode 100644 index 0000000..08145f9 --- /dev/null +++ b/lib/proto/track.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package termix.track; + +import "google/protobuf/empty.proto"; + +service TrackOp { + rpc Create(Track) returns (google.protobuf.Empty); + rpc List(google.protobuf.Empty) returns (Tracks); +} + +message Track { + string name = 1; +} + +message Tracks { + repeated Track tracks = 1; +} + +