From 7e6a539296cc766ed3aee408eab62dd6bc5706a0 Mon Sep 17 00:00:00 2001 From: Geens Date: Sat, 21 Jun 2025 22:58:49 +0200 Subject: [PATCH] Fix and display metronome --- Cargo.lock | 1 + audio_engine/src/metronome.rs | 196 ++++++++++++++++------- audio_engine/src/notification_handler.rs | 5 + gui/src/main.rs | 2 +- gui/src/ui/app.rs | 19 +-- gui/src/ui/matrix.rs | 35 ++++ gui/src/ui/metronome.rs | 65 ++++++++ gui/src/ui/mod.rs | 4 + gui/src/ui/track.rs | 19 +-- osc/Cargo.toml | 1 + osc/src/error.rs | 11 +- osc/src/lib.rs | 2 +- osc/src/message.rs | 35 ++-- osc/src/state.rs | 24 ++- xtask/src/args.rs | 5 +- xtask/src/collect.rs | 2 +- xtask/src/main.rs | 6 +- xtask/src/run.rs | 32 ++-- xtask/src/workspace.rs | 12 +- 19 files changed, 345 insertions(+), 131 deletions(-) create mode 100644 gui/src/ui/matrix.rs create mode 100644 gui/src/ui/metronome.rs diff --git a/Cargo.lock b/Cargo.lock index 23469a1..6486ef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2540,6 +2540,7 @@ dependencies = [ name = "osc" version = "0.1.0" dependencies = [ + "log", "rosc", "strum", "thiserror", diff --git a/audio_engine/src/metronome.rs b/audio_engine/src/metronome.rs index df52783..d98cb4a 100644 --- a/audio_engine/src/metronome.rs +++ b/audio_engine/src/metronome.rs @@ -23,6 +23,16 @@ pub struct BufferTiming { pub beat_in_missed: Option, } +#[derive(Debug, Clone, PartialEq)] +pub struct PpqnTick { + /// Sample offset within the buffer where this tick occurs + pub sample_offset: usize, + /// PPQN tick index (0-23) + pub tick_index: usize, + /// Position within the beat (0.0 - 1.0) + pub position: f32, +} + impl Metronome { pub fn new(click_samples: Arc, state: &State) -> Self { Self { @@ -44,6 +54,9 @@ impl Metronome { let buffer_size = ps.n_frames(); let current_frame_time = ps.last_frame_time(); + // Process PPQN ticks for this buffer + self.process_ppqn_ticks(ps, ports, osc_controller)?; + // Calculate timing for this buffer let timing = self.calculate_timing(current_frame_time, buffer_size)?; @@ -52,9 +65,6 @@ impl Metronome { self.render_click(buffer_size, &timing, click_output); - // Process PPQN ticks for this buffer - self.process_ppqn_ticks(ps, ports, osc_controller)?; - Ok(timing) } @@ -66,63 +76,24 @@ impl Metronome { ) -> Result<()> { let buffer_size = ps.n_frames() as usize; - // Calculate the starting position for this buffer - let start_position = (self.frames_since_last_beat + self.frames_per_beat - buffer_size) - % self.frames_per_beat; + // Use the pure function to find tick (allocation-free) + if let Some(tick) = Self::find_next_ppqn_tick_in_buffer( + self.frames_since_last_beat, + self.frames_per_beat, + buffer_size, + ) { + // Send MIDI clock + let midi_clock_message = [0xF8]; + let mut midi_writer = ports.midi_out.writer(ps); + midi_writer + .write(&jack::RawMidi { + time: tick.sample_offset as u32, + bytes: &midi_clock_message, + }) + .map_err(|_| LooperError::Midi(std::panic::Location::caller()))?; - // Process PPQN ticks in the current buffer only - self.send_buffer_ppqn_ticks(ps, ports, osc_controller, start_position, buffer_size)?; - - Ok(()) - } - - fn send_buffer_ppqn_ticks( - &self, - ps: &jack::ProcessScope, - ports: &mut JackPorts, - osc_controller: &OscController, - buffer_start_position: usize, - buffer_size: usize, - ) -> Result<()> { - let frames_per_ppqn_tick = self.frames_per_beat / 24; - let mut current_position = buffer_start_position; - let mut frames_processed = 0; - - // Get MIDI output writer - let mut midi_writer = ports.midi_out.writer(ps); - - while frames_processed < buffer_size { - let frames_to_next_ppqn = - frames_per_ppqn_tick - (current_position % frames_per_ppqn_tick); - let frames_remaining_in_buffer = buffer_size - frames_processed; - - if frames_remaining_in_buffer >= frames_to_next_ppqn { - // We hit a PPQN tick within this buffer - let tick_frame_offset = frames_processed + frames_to_next_ppqn; - - // Ensure we don't hit the buffer boundary (JACK expects [0, buffer_size)) - if tick_frame_offset >= buffer_size { - break; - } - - current_position = (current_position + frames_to_next_ppqn) % self.frames_per_beat; - frames_processed = tick_frame_offset; - - // Send sample-accurate MIDI clock - let midi_clock_message = [0xF8]; // MIDI Clock byte - midi_writer - .write(&jack::RawMidi { - time: tick_frame_offset as u32, - bytes: &midi_clock_message, - }) - .map_err(|_| LooperError::Midi(std::panic::Location::caller()))?; - - // Send OSC position message - let ppqn_position = (current_position as f32) / (self.frames_per_beat as f32); - osc_controller.metronome_position_changed(ppqn_position)?; - } else { - break; - } + // Send OSC position message + osc_controller.metronome_position_changed(tick.position)?; } Ok(()) @@ -257,6 +228,40 @@ impl Metronome { beat_in_missed, }) } + + /// Pure function to find the next PPQN tick in a buffer (allocation-free) + /// Returns Some(tick) if there's a tick in this buffer, None otherwise + pub fn find_next_ppqn_tick_in_buffer( + frames_since_last_beat: usize, + frames_per_beat: usize, + buffer_size: usize, + ) -> Option { + const TICKS_PER_BEAT: usize = 24; + let frames_per_tick = frames_per_beat / TICKS_PER_BEAT; + + // Find distance to next tick boundary + let frames_since_last_tick = frames_since_last_beat % frames_per_tick; + let frames_to_next_tick = if frames_since_last_tick == 0 { + 0 // We're exactly on a tick + } else { + frames_per_tick - frames_since_last_tick + }; + + // Check if this tick occurs within our buffer + if frames_to_next_tick < buffer_size { + let absolute_tick_position = frames_since_last_beat + frames_to_next_tick; + let tick_index = (absolute_tick_position / frames_per_tick) % TICKS_PER_BEAT; + let position = (tick_index as f32) / (TICKS_PER_BEAT as f32); + + Some(PpqnTick { + sample_offset: frames_to_next_tick, + tick_index, + position, + }) + } else { + None + } + } } #[cfg(test)] @@ -451,4 +456,77 @@ mod tests { assert_eq!(result.missed_frames, 0); assert_eq!(metronome.frames_since_last_beat, 256); } + + // Tests for the pure PPQN calculation function + #[test] + fn test_ppqn_no_tick_in_buffer() { + let tick = Metronome::find_next_ppqn_tick_in_buffer(100, 960000, 128); + assert_eq!(tick, None); + } + + #[test] + fn test_ppqn_tick_at_buffer_start() { + let frames_per_beat = 960000; + + // Position exactly at tick boundary + let tick = Metronome::find_next_ppqn_tick_in_buffer(0, frames_per_beat, 128).unwrap(); + assert_eq!(tick.sample_offset, 0); + assert_eq!(tick.tick_index, 0); + assert_eq!(tick.position, 0.0); + } + + #[test] + fn test_ppqn_tick_in_middle_of_buffer() { + let frames_per_beat = 960000; + let frames_per_tick = frames_per_beat / 24; // 40000 + + // Position 64 frames before tick 1 + let frames_since_last_beat = frames_per_tick - 64; + let tick = + Metronome::find_next_ppqn_tick_in_buffer(frames_since_last_beat, frames_per_beat, 128) + .unwrap(); + + assert_eq!(tick.sample_offset, 64); + assert_eq!(tick.tick_index, 1); + assert_eq!(tick.position, 1.0 / 24.0); + } + + #[test] + fn test_ppqn_tick_wrapping() { + let frames_per_beat = 960000; + let frames_per_tick = frames_per_beat / 24; // 40000 + + // Position near end of beat cycle + let frames_since_last_beat = 23 * frames_per_tick - 64; // 64 frames before tick 23 + let tick = + Metronome::find_next_ppqn_tick_in_buffer(frames_since_last_beat, frames_per_beat, 128) + .unwrap(); + + assert_eq!(tick.sample_offset, 64); + assert_eq!(tick.tick_index, 23); + assert_eq!(tick.position, 23.0 / 24.0); + } + + #[test] + fn test_ppqn_real_world_scenario() { + // Your actual values + let frames_per_beat = 960000; + let frames_since_last_beat = 39936; + let buffer_size = 128; + + let tick = Metronome::find_next_ppqn_tick_in_buffer( + frames_since_last_beat, + frames_per_beat, + buffer_size, + ); + + if let Some(tick) = tick { + println!( + "Tick {} at offset {} (position {})", + tick.tick_index, tick.sample_offset, tick.position + ); + assert_eq!(tick.sample_offset, 64); // 40000 - 39936 = 64 + assert_eq!(tick.tick_index, 1); + } + } } diff --git a/audio_engine/src/notification_handler.rs b/audio_engine/src/notification_handler.rs index d7b3ba8..4c63f4c 100644 --- a/audio_engine/src/notification_handler.rs +++ b/audio_engine/src/notification_handler.rs @@ -76,4 +76,9 @@ impl jack::NotificationHandler for NotificationHandler { .expect("Could not send port registration notification"); }; } + + fn xrun(&mut self, _: &jack::Client) -> jack::Control { + log::error!("XRUN"); + jack::Control::Continue + } } diff --git a/gui/src/main.rs b/gui/src/main.rs index ff8f0cb..fd750b1 100644 --- a/gui/src/main.rs +++ b/gui/src/main.rs @@ -28,7 +28,7 @@ async fn main() -> Result<()> { // Logger simple_logger::SimpleLogger::new() - .with_level(log::LevelFilter::Error) + .with_level(log::LevelFilter::Info) .with_module_level("gui::osc_client", log::LevelFilter::Off) .init() .expect("Could not initialize logger"); diff --git a/gui/src/ui/app.rs b/gui/src/ui/app.rs index dd2c054..e370f50 100644 --- a/gui/src/ui/app.rs +++ b/gui/src/ui/app.rs @@ -11,23 +11,24 @@ impl App { impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let state = self.state_receiver.borrow_and_update().clone(); + ctx.style_mut(|style| { style.visuals.panel_fill = egui::Color32::from_rgb(17, 17, 17); style.visuals.window_fill = egui::Color32::from_rgb(17, 17, 17); style.spacing.item_spacing = egui::vec2(0.0, 0.0); }); + egui::CentralPanel::default().show(ctx, |ui| { - ui.columns(state.columns, |ui| { - for (i, column) in state.cells.iter().enumerate() { - let selected_row = if i == state.selected_column { - Some(state.selected_row) - } else { - None - }; - ui[i].add(super::Column::new(column, selected_row)); - } + ui.vertical(|ui| { + ui.add(super::Metronome::new(state.metronome_position)); + ui.add(super::Matrix::new( + &state.cells, + state.selected_column, + state.selected_row, + )); }); }); + ctx.request_repaint(); } } diff --git a/gui/src/ui/matrix.rs b/gui/src/ui/matrix.rs new file mode 100644 index 0000000..9ae7915 --- /dev/null +++ b/gui/src/ui/matrix.rs @@ -0,0 +1,35 @@ +pub struct Matrix<'a> { + cells: &'a Vec>, + selected_column: usize, + selected_row: usize, +} + +impl<'a> Matrix<'a> { + pub fn new( + cells: &'a Vec>, + selected_column: usize, + selected_row: usize, + ) -> Self { + Self { + cells, + selected_column, + selected_row, + } + } +} + +impl<'a> egui::Widget for Matrix<'a> { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + ui.columns(self.cells.len(), |ui| { + for (i, column) in self.cells.iter().enumerate() { + let selected_row = if i == self.selected_column { + Some(self.selected_row) + } else { + None + }; + ui[i].add(super::Column::new(column, selected_row)); + } + }); + ui.response() + } +} diff --git a/gui/src/ui/metronome.rs b/gui/src/ui/metronome.rs new file mode 100644 index 0000000..63d1fe0 --- /dev/null +++ b/gui/src/ui/metronome.rs @@ -0,0 +1,65 @@ +pub struct Metronome { + position: f32, // 0.0 - 1.0 +} + +impl Metronome { + pub fn new(position: f32) -> Self { + Self { + position: position.clamp(0.0, 1.0), + } + } +} + +impl egui::Widget for Metronome { + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let sf = ui.available_width() / 100.0; + + // Simple scaling values + let rail_height = sf * 1.0; + let circle_radius = sf * 2.0; + let h_padding = sf * 5.0; + let v_padding = sf * 3.0; + + // Colors + let rail_color = egui::Color32::from_rgb(51, 51, 51); // #333333 + let circle_color = egui::Color32::from_rgb(0, 255, 136); // #00ff88 + + // Calculate widget size + let width = ui.available_width(); + let height = rail_height + 2.0 * v_padding; + let size = egui::vec2(width, height); + + // Allocate the space we need + let (rect, response) = ui.allocate_exact_size(size, egui::Sense::hover()); + + // Calculate rail position (centered) + let rail_left = rect.min.x + h_padding; + let rail_right = rect.max.x - h_padding; + let rail_width = rail_right - rail_left; + let rail_center_y = rect.center().y; + let rail_top = rail_center_y - rail_height / 2.0; + let rail_bottom = rail_center_y + rail_height / 2.0; + + let rail_rect = egui::Rect::from_min_max( + egui::pos2(rail_left, rail_top), + egui::pos2(rail_right, rail_bottom), + ); + + // Calculate circle position + let circle_x = rail_left + self.position * rail_width; + let circle_center = egui::pos2(circle_x, rail_center_y); + + // Draw rail + ui.painter().rect_filled( + rail_rect, + egui::Rounding::same(rail_height / 2.0), + rail_color, + ); + + // Draw circle + ui.painter() + .circle_filled(circle_center, circle_radius, circle_color); + + response + } +} diff --git a/gui/src/ui/mod.rs b/gui/src/ui/mod.rs index 8f15e9f..390eb0d 100644 --- a/gui/src/ui/mod.rs +++ b/gui/src/ui/mod.rs @@ -1,8 +1,12 @@ mod app; mod column; +mod matrix; +mod metronome; mod track; mod ui_rows_ext; pub use app::App; use column::Column; +use matrix::Matrix; +use metronome::Metronome; use track::Track; diff --git a/gui/src/ui/track.rs b/gui/src/ui/track.rs index a287b84..c7bb1c5 100644 --- a/gui/src/ui/track.rs +++ b/gui/src/ui/track.rs @@ -66,25 +66,22 @@ impl<'a> egui::Widget for Track<'a> { let volume_bar_height = 2.0 * sf; let margin = 4.0 * sf; let frame_rect = frame_response.response.rect; - + // Calculate volume bar position (bottom of the frame, inside the margins) let inner_rect = frame_rect.shrink(5.0 * sf); // Match the outer_margin let volume_bar_rect = egui::Rect::from_min_max( egui::pos2( inner_rect.min.x + margin, - inner_rect.max.y - volume_bar_height - margin + inner_rect.max.y - volume_bar_height - margin, ), - egui::pos2( - inner_rect.max.x - margin, - inner_rect.max.y - margin - ) + egui::pos2(inner_rect.max.x - margin, inner_rect.max.y - margin), ); // Draw volume bar background ui.painter().rect_filled( volume_bar_rect, egui::Rounding::same(1.0 * sf), - egui::Color32::from_rgb(51, 51, 51) // #333333 + egui::Color32::from_rgb(51, 51, 51), // #333333 ); // Draw volume bar fill (only if volume > 0) @@ -93,17 +90,17 @@ impl<'a> egui::Widget for Track<'a> { if fill_width > 0.0 { let fill_rect = egui::Rect::from_min_size( volume_bar_rect.min, - egui::vec2(fill_width, volume_bar_rect.height()) + egui::vec2(fill_width, volume_bar_rect.height()), ); - + ui.painter().rect_filled( fill_rect, egui::Rounding::same(1.0 * sf), - egui::Color32::from_rgb(0, 255, 136) // #00ff88 + egui::Color32::from_rgb(0, 255, 136), // #00ff88 ); } } frame_response.response } -} \ No newline at end of file +} diff --git a/osc/Cargo.toml b/osc/Cargo.toml index 62b5383..60f578a 100644 --- a/osc/Cargo.toml +++ b/osc/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" [dependencies] strum = { version = "0.23", features = ["derive"] } +log.workspace = true rosc.workspace = true thiserror.workspace = true \ No newline at end of file diff --git a/osc/src/error.rs b/osc/src/error.rs index e76c6dc..a4680ef 100644 --- a/osc/src/error.rs +++ b/osc/src/error.rs @@ -10,8 +10,13 @@ pub trait AddressParseResult { fn address_part_result(self, context: &'static str) -> Result; } -impl AddressParseResult for std::result::Result { +impl AddressParseResult for std::result::Result { fn address_part_result(self, context: &'static str) -> Result { - self.map_err(|e| Error::AddressParseError(format!("failed to parse number in address part {}: {}", context, e))) + self.map_err(|e| { + Error::AddressParseError(format!( + "failed to parse number in address part {}: {}", + context, e + )) + }) } -} \ No newline at end of file +} diff --git a/osc/src/lib.rs b/osc/src/lib.rs index ad8589f..b16a13f 100644 --- a/osc/src/lib.rs +++ b/osc/src/lib.rs @@ -8,4 +8,4 @@ pub use error::Result; pub use message::Message; pub use state::State; pub use state::Track; -pub use state::TrackState; \ No newline at end of file +pub use state::TrackState; diff --git a/osc/src/message.rs b/osc/src/message.rs index a2213a9..c985219 100644 --- a/osc/src/message.rs +++ b/osc/src/message.rs @@ -77,6 +77,7 @@ impl Message { if let Some(rosc::OscType::String(state_str)) = args.first() { let state = state_str.parse::().unwrap_or(TrackState::Empty); + log::trace!("TrackStateChanged: column={column}, row={row}, state={state}"); return Ok(Some(Message::TrackStateChanged { column, row, state })); } } else if addr.starts_with("/looper/cell/") && addr.ends_with("/volume") { @@ -90,26 +91,30 @@ impl Message { let row: usize = parts[4].parse::().address_part_result("row")? - 1; // Convert to 0-based if let Some(rosc::OscType::Float(volume)) = args.first() { - return Ok(Some(Message::TrackVolumeChanged { - column, - row, - volume: *volume + log::trace!("TrackVolumeChanged: column={column}, row={row}, volume={volume}"); + return Ok(Some(Message::TrackVolumeChanged { + column, + row, + volume: *volume, })); } } else if addr == "/looper/selected/column" { if let Some(rosc::OscType::Int(column_1based)) = args.first() { + log::trace!("SelectedColumnChanged: column={column_1based}"); let column = (*column_1based as usize).saturating_sub(1); // Convert to 0-based return Ok(Some(Message::SelectedColumnChanged { column })); } } else if addr == "/looper/selected/row" { if let Some(rosc::OscType::Int(row_1based)) = args.first() { + log::trace!("SelectedRowChanged: row={row_1based}"); let row = (*row_1based as usize).saturating_sub(1); // Convert to 0-based return Ok(Some(Message::SelectedRowChanged { row })); } } else if addr == "/looper/metronome/position" { if let Some(rosc::OscType::Float(position)) = args.first() { - return Ok(Some(Message::MetronomePosition { - position: *position + log::trace!("MetronomePosition: position={position}"); + return Ok(Some(Message::MetronomePosition { + position: *position, })); } } @@ -121,15 +126,19 @@ impl Message { match self { Message::TrackStateChanged { column, row, state } => { let address = format!("/looper/cell/{}/{}/state", column + 1, row + 1); // 1-based indexing - + rosc::OscPacket::Message(rosc::OscMessage { addr: address, args: vec![rosc::OscType::String(state.to_string())], }) } - Message::TrackVolumeChanged { column, row, volume } => { + Message::TrackVolumeChanged { + column, + row, + volume, + } => { let address = format!("/looper/cell/{}/{}/volume", column + 1, row + 1); // 1-based indexing - + rosc::OscPacket::Message(rosc::OscMessage { addr: address, args: vec![rosc::OscType::Float(volume)], @@ -137,7 +146,7 @@ impl Message { } Message::SelectedColumnChanged { column } => { let address = "/looper/selected/column".to_string(); - + rosc::OscPacket::Message(rosc::OscMessage { addr: address, args: vec![rosc::OscType::Int((column + 1) as i32)], // 1-based indexing @@ -145,7 +154,7 @@ impl Message { } Message::SelectedRowChanged { row } => { let address = "/looper/selected/row".to_string(); - + rosc::OscPacket::Message(rosc::OscMessage { addr: address, args: vec![rosc::OscType::Int((row + 1) as i32)], // 1-based indexing @@ -153,7 +162,7 @@ impl Message { } Message::MetronomePosition { position } => { let address = "/looper/metronome/position".to_string(); - + rosc::OscPacket::Message(rosc::OscMessage { addr: address, args: vec![rosc::OscType::Float(position)], @@ -161,4 +170,4 @@ impl Message { } } } -} \ No newline at end of file +} diff --git a/osc/src/state.rs b/osc/src/state.rs index 9915df5..d3aa66a 100644 --- a/osc/src/state.rs +++ b/osc/src/state.rs @@ -16,8 +16,8 @@ pub struct Track { #[derive(Debug, Clone)] pub struct State { - pub selected_column: usize, // 0-based (converted to 1-based for OSC) - pub selected_row: usize, // 0-based (converted to 1-based for OSC) + pub selected_column: usize, // 0-based (converted to 1-based for OSC) + pub selected_row: usize, // 0-based (converted to 1-based for OSC) pub cells: Vec>, pub columns: usize, pub rows: usize, @@ -28,9 +28,17 @@ pub struct State { impl State { pub fn new(columns: usize, rows: usize) -> Self { let cells = (0..columns) - .map(|_| vec![Track { state: TrackState::Empty, volume: 1.0 }; rows]) + .map(|_| { + vec![ + Track { + state: TrackState::Empty, + volume: 1.0 + }; + rows + ] + }) .collect(); - + Self { selected_column: 0, selected_row: 0, @@ -49,7 +57,11 @@ impl State { self.cells[*column][*row].state = state.clone(); } } - Message::TrackVolumeChanged { column, row, volume } => { + Message::TrackVolumeChanged { + column, + row, + volume, + } => { if *column < self.columns && *row < self.rows { self.cells[*column][*row].volume = *volume; } @@ -128,4 +140,4 @@ impl State { self.metronome_position = position; self.metronome_timestamp = std::time::Instant::now(); } -} \ No newline at end of file +} diff --git a/xtask/src/args.rs b/xtask/src/args.rs index 0bd58b2..6a30d20 100644 --- a/xtask/src/args.rs +++ b/xtask/src/args.rs @@ -1,4 +1,4 @@ - use clap::*; +use clap::*; #[derive(Parser)] #[command(name = "fcb-looper")] @@ -6,11 +6,10 @@ pub struct Args { #[command(subcommand)] pub command: Option, - } #[derive(Subcommand)] pub enum Command { Run, Collect, -} \ No newline at end of file +} diff --git a/xtask/src/collect.rs b/xtask/src/collect.rs index 2397267..cd48bb4 100644 --- a/xtask/src/collect.rs +++ b/xtask/src/collect.rs @@ -21,4 +21,4 @@ pub async fn collect_dir_output(dir: &str, output: &str) { .output() .await .expect("Failed to collect audio_engine sources"); -} \ No newline at end of file +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 644a2ef..1b58275 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -9,7 +9,9 @@ use clap::Parser; async fn main() { let args = args::Args::parse(); - workspace::change_to_workspace_dir().await.expect("Failed to change to workspace directory"); + workspace::change_to_workspace_dir() + .await + .expect("Failed to change to workspace directory"); match args.command { Some(args::Command::Run) | None => { @@ -19,4 +21,4 @@ async fn main() { collect::collect().await; } } -} \ No newline at end of file +} diff --git a/xtask/src/run.rs b/xtask/src/run.rs index 39873b7..e90c393 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -16,14 +16,16 @@ pub async fn run() { // Set up signal handling for graceful shutdown let cleanup_handles = vec![ qjackctl_handle.clone(), - audio_engine_handle.clone(), + audio_engine_handle.clone(), gui_handle.clone(), simulator_handle.clone(), ]; let cleanup_token = cancel_token.clone(); - + tokio::spawn(async move { - tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c"); + tokio::signal::ctrl_c() + .await + .expect("Failed to listen for ctrl+c"); println!("Received Ctrl+C, shutting down..."); cleanup_token.cancel(); cleanup_processes(cleanup_handles).await; @@ -102,14 +104,18 @@ async fn cleanup_processes(handles: Vec) { 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"); + Command::new(jack) + .args(args) + .output() + .await + .expect("Failed to stop JACK daemon"); } async fn spawn_qjackctl() -> Child { @@ -146,24 +152,14 @@ async fn spawn_audio_engine() -> Child { async fn spawn_gui() -> Child { Command::new("cargo") - .args([ - "run", - "--release", - "--bin", - "gui", - ]) + .args(["run", "--release", "--bin", "gui"]) .spawn() .expect("Could not start gui") } async fn spawn_simulator() -> Child { Command::new("cargo") - .args([ - "run", - "--release", - "--bin", - "simulator", - ]) + .args(["run", "--release", "--bin", "simulator"]) .spawn() .expect("Could not start simulator") } @@ -173,4 +169,4 @@ async fn kill_process(child: &mut Child, name: &str) { Ok(()) => println!("Killed {}", name), Err(e) => eprintln!("Failed to kill {}: {}", name, e), } -} \ No newline at end of file +} diff --git a/xtask/src/workspace.rs b/xtask/src/workspace.rs index 21ba63d..938e49a 100644 --- a/xtask/src/workspace.rs +++ b/xtask/src/workspace.rs @@ -12,20 +12,24 @@ pub async fn change_to_workspace_dir() -> Result<(), Box> return Err(format!( "Failed to locate workspace: {}", String::from_utf8_lossy(&output.stderr) - ).into()); + ) + .into()); } let workspace_cargo_toml = String::from_utf8(output.stdout)?; let workspace_cargo_toml = workspace_cargo_toml.trim(); - + // Get the directory containing Cargo.toml let workspace_dir = PathBuf::from(workspace_cargo_toml) .parent() .ok_or("Failed to get workspace directory")? .to_path_buf(); - println!("Changing to workspace directory: {}", workspace_dir.display()); + println!( + "Changing to workspace directory: {}", + workspace_dir.display() + ); std::env::set_current_dir(&workspace_dir)?; Ok(()) -} \ No newline at end of file +}