WIP: Preparing the codebase, nothing important
Some checks failed
ci/woodpecker/push/code_tests Pipeline failed
ci/woodpecker/push/pre_commit_test Pipeline failed

This commit is contained in:
2025-11-22 20:31:38 +01:00
committed by Nikolai Rodionov
parent 213c5b1a47
commit 87ee669502
35 changed files with 2830 additions and 10 deletions

22
engine/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "engine"
version = "0.1.0"
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/" }
log = "0.4.28"
prost = "0.14.1"
tokio = { version = "1.48.0", features = ["rt-multi-thread"] }
tonic = "0.14.2"
tonic-prost = "0.14.2"
tonic-reflection = "0.14.2"
[features]
jack = ["dep:jack"]
coreaudio = ["dep:coreaudio-rs"]

View File

@@ -0,0 +1,26 @@
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<dyn std::error::Error>> {
Ok(())
}
fn describe_backend() -> Result<super::BackendDescription, Box<dyn std::error::Error>> {
todo!()
}
}
#[cfg(not(feature = "coreaudio"))]
impl AudioBackend for CoreAudioBackend {
fn start_client(&mut self) -> Result<(), Box<dyn std::error::Error>> {
todo!()
}
fn describe_backend() -> Result<super::BackendDescription, Box<dyn std::error::Error>> {
todo!()
}
}

View File

@@ -0,0 +1,19 @@
use crate::audio_engine::AudioBackend;
struct DummyAudioBackend {}
impl DummyAudioBackend {
fn new() -> Self {
Self {}
}
}
impl AudioBackend for DummyAudioBackend {
fn start_client(&mut self) -> Result<(), Box<dyn std::error::Error>> {
todo!()
}
fn describe_backend() -> Result<super::BackendDescription, Box<dyn std::error::Error>> {
todo!()
}
}

View File

@@ -0,0 +1,75 @@
use crate::audio_engine::AudioBackend;
use crossbeam_channel::bounded;
use log::{info, warn};
use std::error::Error;
#[cfg(feature = "jack")]
use jack;
#[cfg(feature = "jack")]
use jack::ClientOptions;
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;
let status = JackStatus::default();
Self {
feature_jack,
running,
status,
}
}
}
#[cfg(feature = "jack")]
#[derive(Default)]
pub(crate) struct JackStatus {
client: Option<jack::Client>,
status: Option<jack::ClientStatus>,
}
#[cfg(feature = "jack")]
impl AudioBackend for JackAudioBackend {
fn start_client(&mut self) -> Result<(), Box<dyn Error>> {
Ok(())
}
// Get the possible input and output ports to use them for the session initialization
fn describe_backend() -> Result<super::BackendDescription, Box<dyn Error>> {
use jack::{Client, PortFlags};
use crate::audio_engine::BackendDescription;
let (client, _) = Client::new("list_ports", ClientOptions::empty())?;
let ports_in = client.ports(None, None, PortFlags::IS_INPUT);
let ports_out = client.ports(None, None, PortFlags::IS_OUTPUT);
let output = BackendDescription {
audio_devices_out: ports_out,
audio_devices_in: ports_in,
};
Ok(output)
}
}
#[cfg(not(feature = "jack"))]
#[derive(Default)]
pub(crate) struct JackStatus {}
#[cfg(not(feature = "jack"))]
impl AudioBackend for JackAudioBackend {
fn start_client(&mut self) -> Result<(), Box<dyn Error>> {
warn!("jack support is not enabled");
Ok(())
}
fn describe_backend() -> Result<super::BackendDescription, Box<dyn Error>> {
unimplemented!("jack is disabled")
}
}

View File

@@ -0,0 +1,17 @@
use std::error::Error;
pub(crate) mod coreaudio_ab;
pub(crate) mod dummy_ab;
pub(crate) mod jack_ab;
pub(crate) struct BackendDescription {
pub(crate) audio_devices_out: Vec<String>,
pub(crate) audio_devices_in: Vec<String>,
}
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(&mut self) -> Result<(), Box<dyn Error>>;
fn describe_backend() -> Result<BackendDescription, Box<dyn Error>>;
}

View File

@@ -0,0 +1,9 @@
use crate::control_pane::ControlPane;
struct Dummy {}
impl ControlPane for Dummy {
async fn start_server(&self) -> Result<(), Box<dyn std::error::Error>> {
todo!()
}
}

View File

@@ -0,0 +1,125 @@
use std::result::Result;
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, Status, transport::Server};
use tonic_reflection::server;
use crate::{
audio_engine::{
AudioBackend,
jack_ab::{self, JackAudioBackend},
},
control_pane::ControlPane,
};
pub(crate) struct Grpc {
pub(crate) port: i32,
pub(crate) enable_reflections: bool,
}
impl ControlPane for Grpc {
async fn start_server(&self) -> Result<(), Box<dyn std::error::Error>> {
info!("starting the grpc server on port {}", self.port);
// TODO: Use the port from self
let addr = "[::1]:50051".parse()?;
let mut server = Server::builder();
let audio_backend_reflections = server::Builder::configure()
.register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET)
.build_v1()
.unwrap();
let audio_backend = TermixAudioBackend::default();
server
.add_service(audio_backend_reflections)
.add_service(AudioBackendRpcServer::new(audio_backend))
.serve(addr)
.await?;
Ok(())
}
}
#[derive(Debug, Default)]
pub struct TermixAudioBackend {}
#[tonic::async_trait]
impl AudioBackendRpc for TermixAudioBackend {
async fn stop_client(
&self,
_: tonic::Request<()>,
) -> Result<tonic::Response<()>, tonic::Status> {
todo!()
}
async fn start_client(
&self,
request: tonic::Request<DesiredAudioBacked>,
) -> Result<tonic::Response<()>, 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();
}
SupportedAudioBackends::AbCoreaudio => todo!(),
};
Ok(Response::new(()))
}
async fn describe_backend(
&self,
request: tonic::Request<DesiredAudioBacked>,
) -> Result<tonic::Response<AudioBackendDescription>, tonic::Status> {
info!("Describing the audio backend");
match request.get_ref().backend() {
SupportedAudioBackends::AbUnspecified => return Err(Status::not_found("backend os not specified")),
SupportedAudioBackends::AbCoreaudio => todo!(),
SupportedAudioBackends::AbJack => {
let backend_desc= match jack_ab::JackAudioBackend::describe_backend(){
Ok(desc) => desc,
Err(err) => return Err(Status::internal(err.to_string())),
};
Ok(Response::new(AudioBackendDescription{
core_audio_description: None,
input_devices: backend_desc.audio_devices_in,
output_devices: backend_desc.audio_devices_out
}))
}
}
}
async fn init_connection(
&self,
request: tonic::Request<()>,
) -> Result<tonic::Response<()>, tonic::Status> {
info!("initializing the connection to the audio backend");
todo!()
}
async fn get_available_backends(
&self,
_: tonic::Request<()>,
) -> Result<Response<BackendList>, 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(),
});
}
Ok(Response::new(response))
}
}

View File

@@ -0,0 +1,6 @@
pub(crate) mod dummy;
pub(crate) mod grpc;
pub(crate) trait ControlPane {
async fn start_server(&self) -> Result<(), Box<dyn std::error::Error>>;
}

29
engine/src/main.rs Normal file
View File

@@ -0,0 +1,29 @@
pub(crate) mod audio_engine;
pub(crate) mod control_pane;
use crate::control_pane::ControlPane;
use clap::Parser;
/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(long, default_value_t = 50051)]
grpc_port: i32,
#[arg(long, default_value_t = true)]
grpc_enable_reflections: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let args = Args::parse();
let grpc_control_pane = control_pane::grpc::Grpc {
port: args.grpc_port,
enable_reflections: args.grpc_enable_reflections,
};
grpc_control_pane.start_server().await?;
Ok(())
}