Looper controlled by buttons

This commit is contained in:
2025-06-29 15:25:51 +02:00
parent d1b88ff921
commit 72115f3783
9 changed files with 638 additions and 211 deletions

View File

@@ -12,4 +12,5 @@ pub struct Args {
pub enum Command {
Run,
Collect,
Mapper,
}

View File

@@ -1,5 +1,6 @@
mod args;
mod collect;
mod mapper;
mod run;
mod workspace;
@@ -20,5 +21,8 @@ async fn main() {
Some(args::Command::Collect) => {
collect::collect().await;
}
Some(args::Command::Mapper) => {
mapper::mapper().await;
}
}
}

153
xtask/src/mapper.rs Normal file
View File

@@ -0,0 +1,153 @@
use std::sync::Arc;
use tokio::process::{Child, Command};
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
type ProcessHandle = Arc<Mutex<Option<Child>>>;
pub async fn mapper() {
let qjackctl_handle: ProcessHandle = Arc::new(Mutex::new(None));
let firmware_handle: ProcessHandle = Arc::new(Mutex::new(None));
let mapper_handle: ProcessHandle = Arc::new(Mutex::new(None));
let cancel_token = CancellationToken::new();
let cleanup_token = cancel_token.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c()
.await
.expect("Failed to listen for ctrl+c");
println!("Received Ctrl+C, shutting down...");
cleanup_token.cancel();
});
// Start processes
println!("Starting qjackctl...");
let qjackctl = spawn_qjackctl().await;
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
println!("Starting firmware debugger in new terminal...");
let firmware = spawn_firmware_in_terminal().await;
println!("Starting mapper...");
let mapper = spawn_mapper().await;
// Store handles for cleanup
*qjackctl_handle.lock().await = Some(qjackctl);
*firmware_handle.lock().await = Some(firmware);
*mapper_handle.lock().await = Some(mapper);
// Get references back for waiting (firmware not monitored since gnome-terminal exits immediately)
let mut qjackctl = qjackctl_handle.lock().await.take().unwrap();
let mut mapper = mapper_handle.lock().await.take().unwrap();
// Wait for any process to exit or cancellation
tokio::select! {
result = qjackctl.wait() => {
println!("qjackctl exited: {:?}", result);
kill_process(&mut mapper, "mapper").await;
cleanup_processes(vec![firmware_handle]).await;
stop_jack_daemon().await;
}
result = mapper.wait() => {
println!("mapper exited: {:?}", result);
kill_process(&mut qjackctl, "qjackctl").await;
cleanup_processes(vec![firmware_handle]).await;
stop_jack_daemon().await;
}
_ = cancel_token.cancelled() => {
println!("Shutdown requested");
kill_process(&mut mapper, "mapper").await;
kill_process(&mut qjackctl, "qjackctl").await;
cleanup_processes(vec![firmware_handle]).await;
stop_jack_daemon().await;
}
}
println!("All processes stopped");
}
async fn cleanup_processes(handles: Vec<ProcessHandle>) {
for handle in handles {
if let Some(mut child) = handle.lock().await.take() {
let _ = child.kill().await;
}
}
}
async fn stop_jack_daemon() {
println!("Stopping JACK daemon...");
#[cfg(target_os = "windows")]
let (jack, args) = ("taskkill", vec!["/f", "/im", "jackd.exe"]);
#[cfg(target_os = "linux")]
let (jack, args) = ("pkill", vec!["-f", "jackd"]);
Command::new(jack)
.args(args)
.output()
.await
.expect("Failed to stop JACK daemon");
}
async fn spawn_qjackctl() -> Child {
#[cfg(target_os = "windows")]
let qjackctl = "C:\\Program Files\\JACK2\\qjackctl\\qjackctl.exe";
#[cfg(target_os = "linux")]
let qjackctl = "qjackctl";
Command::new(qjackctl)
.arg("/start-server")
.spawn()
.expect("Could not start qjackctl")
}
async fn spawn_firmware_in_terminal() -> Child {
#[cfg(target_os = "windows")]
{
Command::new("C:\\Program Files\\Git\\git-bash.exe")
.args(["-c", "cd firmware && cargo run; read -p 'Press Enter to close...'"])
.spawn()
.expect("Could not start firmware debugger in git-bash")
}
#[cfg(target_os = "linux")]
{
Command::new("gnome-terminal")
.args([
"--title=Firmware Debugger - FCB Looper",
"--",
"bash", "-c",
"cd firmware && echo 'Starting firmware debugger...' && cargo run; echo 'Firmware debugger exited. Press Enter to close...'; read"
])
.spawn()
.expect("Could not start firmware debugger in gnome-terminal")
}
}
async fn spawn_mapper() -> Child {
Command::new("cargo")
.args(["run", "--package", "mapper"])
.stdin(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.spawn()
.expect("Could not start mapper")
}
async fn kill_process(child: &mut Child, name: &str) {
match child.kill().await {
Ok(()) => println!("Stopped {}", name),
Err(e) => {
// Don't print error if process already exited
let error_msg = e.to_string().to_lowercase();
if !error_msg.contains("no such process") &&
!error_msg.contains("not found") &&
!error_msg.contains("invalid argument") {
eprintln!("Failed to stop {}: {}", name, e);
}
}
}
}

View File

@@ -124,13 +124,8 @@ async fn spawn_qjackctl() -> Child {
#[cfg(target_os = "linux")]
let qjackctl = "qjackctl";
#[cfg(target_os = "windows")]
let qjackctl_arg = "/start-server";
#[cfg(target_os = "linux")]
let qjackctl_arg = "--start-server";
Command::new(qjackctl)
.arg(qjackctl_arg)
.arg("/start-server")
.spawn()
.expect("Could not start qjackctl")
}