diff --git a/audio_engine/src/main.rs b/audio_engine/src/main.rs index baad401..1688733 100644 --- a/audio_engine/src/main.rs +++ b/audio_engine/src/main.rs @@ -14,6 +14,7 @@ mod post_record_handler; mod process_handler; mod state; mod track; +mod track_matrix; use std::sync::Arc; @@ -36,6 +37,7 @@ use post_record_handler::PostRecordHandler; use process_handler::ProcessHandler; use state::State; use track::Track; +use track_matrix::TrackMatrix; pub struct JackPorts { pub audio_in: jack::Port, @@ -70,7 +72,7 @@ async fn main() { let (mut post_record_handler, post_record_controller) = PostRecordHandler::new().expect("Could not create post-record handler"); - let process_handler = ProcessHandler::<_, 5>::new( + let process_handler = ProcessHandler::new( &jack_client, ports, allocator, diff --git a/audio_engine/src/midi.rs b/audio_engine/src/midi.rs index 104d024..7207044 100644 --- a/audio_engine/src/midi.rs +++ b/audio_engine/src/midi.rs @@ -1,8 +1,8 @@ use crate::*; /// Process MIDI events -pub fn process_events( - process_handler: &mut ProcessHandler, +pub fn process_events( + process_handler: &mut ProcessHandler, ps: &jack::ProcessScope, ) -> Result<()> { // First, collect all MIDI events into a fixed-size array @@ -46,9 +46,27 @@ pub fn process_events( 22 => { process_handler.handle_button_3()?; } + 23 => { + process_handler.handle_button_4()?; + } 24 => { process_handler.handle_button_5()?; } + 25 => { + process_handler.handle_button_6()?; + } + 26 => { + process_handler.handle_button_7()?; + } + 27 => { + process_handler.handle_button_8()?; + } + 28 => { + process_handler.handle_button_9()?; + } + 29 => { + process_handler.handle_button_10()?; + } 30 => { process_handler.handle_button_up()?; } diff --git a/audio_engine/src/post_record_handler.rs b/audio_engine/src/post_record_handler.rs index 26ffd24..e900fe7 100644 --- a/audio_engine/src/post_record_handler.rs +++ b/audio_engine/src/post_record_handler.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; /// Request to process a recorded chunk chain with sync offset #[derive(Debug)] pub struct PostRecordRequest { + pub column: usize, pub row: usize, pub chunk_chain: Arc, pub sync_offset: usize, @@ -13,6 +14,7 @@ pub struct PostRecordRequest { /// Response containing the consolidated buffer #[derive(Debug)] pub struct PostRecordResponse { + pub column: usize, pub row: usize, pub consolidated_buffer: Box<[f32]>, } @@ -28,12 +30,14 @@ impl PostRecordController { /// Send a post-record processing request (RT-safe) pub fn send_request( &self, + column: usize, row: usize, chunk_chain: Arc, sync_offset: usize, sample_rate: usize, ) -> Result<()> { let request = PostRecordRequest { + column, row, chunk_chain, sync_offset, @@ -117,6 +121,7 @@ impl PostRecordHandler { // Step 2: Send consolidated buffer back to RT thread immediately let response = PostRecordResponse { + column: request.column, row: request.row, consolidated_buffer, }; diff --git a/audio_engine/src/process_handler.rs b/audio_engine/src/process_handler.rs index d523207..497100b 100644 --- a/audio_engine/src/process_handler.rs +++ b/audio_engine/src/process_handler.rs @@ -1,16 +1,17 @@ use crate::*; -pub struct ProcessHandler { +const COLS: usize = 5; +const ROWS: usize = 5; + +pub struct ProcessHandler { pub ports: JackPorts, - chunk_factory: F, metronome: Metronome, - post_record_controller: PostRecordController, - column: Column, + track_matrix: TrackMatrix, selected_row: usize, - scratch_pad: Box<[f32]>, + selected_column: usize, } -impl ProcessHandler { +impl ProcessHandler { pub fn new( client: &jack::Client, ports: JackPorts, @@ -19,31 +20,64 @@ impl ProcessHandler { state: &State, post_record_controller: PostRecordController, ) -> Result { + let track_matrix = TrackMatrix::new( + client, + chunk_factory, + state, + post_record_controller, + )?; Ok(Self { ports, - chunk_factory, metronome: Metronome::new(beep_samples, state), - post_record_controller, - column: Column::new(state.metronome.frames_per_beat), + track_matrix, selected_row: 0, - scratch_pad: vec![0.0; client.buffer_size() as usize].into_boxed_slice(), + selected_column: 0, }) } pub fn handle_button_1(&mut self) -> Result<()> { - self.column.handle_record_button(self.selected_row) + self.track_matrix.handle_record_button(self.selected_column, self.selected_row) } pub fn handle_button_2(&mut self) -> Result<()> { - self.column.handle_play_button(self.selected_row) + self.track_matrix.handle_play_button(self.selected_column, self.selected_row) } pub fn handle_button_3(&mut self) -> Result<()> { Ok(()) } + pub fn handle_button_4(&mut self) -> Result<()> { + Ok(()) + } + pub fn handle_button_5(&mut self) -> Result<()> { - self.column.handle_clear_button(self.selected_row) + self.track_matrix.handle_clear_button(self.selected_column, self.selected_row) + } + + pub fn handle_button_6(&mut self) -> Result<()> { + self.selected_column = 0; + Ok(()) + } + + pub fn handle_button_7(&mut self) -> Result<()> { + self.selected_column = 1; + Ok(()) + } + + pub fn handle_button_8(&mut self) -> Result<()> { + self.selected_column = 2; + Ok(()) + } + + pub fn handle_button_9(&mut self) -> Result<()> { + self.selected_column = 3; + Ok(()) + } + + pub fn handle_button_10(&mut self) -> Result<()> { + self.selected_column = 4; + Ok(()) } pub fn handle_button_up(&mut self) -> Result<()> { @@ -61,7 +95,7 @@ impl ProcessHandler { } } -impl jack::ProcessHandler for ProcessHandler { +impl jack::ProcessHandler for ProcessHandler { fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control { if let Err(e) = self.process_with_error_handling(client, ps) { log::error!("Error processing audio: {}", e); @@ -72,59 +106,24 @@ impl jack::ProcessHandler for ProcessHandler } } -impl ProcessHandler { +impl ProcessHandler { fn process_with_error_handling( &mut self, client: &jack::Client, ps: &jack::ProcessScope, ) -> Result<()> { - // Check for consolidation response - if let Some(response) = self.post_record_controller.try_recv_response() { - self.column - .set_consolidated_buffer(response.row, response.consolidated_buffer)?; - } - // Process metronome and get beat timing information let timing = self.metronome.process(ps, &mut self.ports)?; - // Handle xruns - if timing.missed_frames > 0 { - self.column.handle_xrun( - &timing, - &mut self.chunk_factory, - |row, chunk, sync_offset| { - self.post_record_controller.send_request( - row, - chunk, - sync_offset, - client.sample_rate(), - ) - }, - )?; - } - // Process MIDI midi::process_events(self, ps)?; // Process audio - let input_buffer = self.ports.audio_in.as_slice(ps); - let output_buffer = self.ports.audio_out.as_mut_slice(ps); - output_buffer.fill(0.0); - - self.column.process( + self.track_matrix.process( + client, + ps, + &mut self.ports, &timing, - input_buffer, - output_buffer, - &mut self.scratch_pad, - &mut self.chunk_factory, - |row, chunk, sync_offset| { - self.post_record_controller.send_request( - row, - chunk, - sync_offset, - client.sample_rate(), - ) - }, )?; Ok(()) diff --git a/audio_engine/src/track_matrix.rs b/audio_engine/src/track_matrix.rs new file mode 100644 index 0000000..16492dc --- /dev/null +++ b/audio_engine/src/track_matrix.rs @@ -0,0 +1,96 @@ +use crate::*; + +pub struct TrackMatrix { + chunk_factory: F, + post_record_controller: PostRecordController, + columns: [Column; COLS], + scratch_pad: Box<[f32]>, +} + +impl TrackMatrix { + pub fn new( + client: &jack::Client, + chunk_factory: F, + state: &State, + post_record_controller: PostRecordController, + ) -> Result { + let columns = std::array::from_fn(|_| Column::new(state.metronome.frames_per_beat)); + Ok(Self { + chunk_factory, + post_record_controller, + columns, + scratch_pad: vec![0.0; client.buffer_size() as usize].into_boxed_slice(), + }) + } + + pub fn handle_record_button(&mut self, selected_column: usize, selected_row: usize) -> Result<()> { + self.columns[selected_column].handle_record_button(selected_row) + } + + pub fn handle_play_button(&mut self, selected_column: usize, selected_row: usize) -> Result<()> { + self.columns[selected_column].handle_play_button(selected_row) + } + + pub fn handle_clear_button(&mut self, selected_column: usize, selected_row: usize) -> Result<()> { + self.columns[selected_column].handle_clear_button(selected_row) + } + + pub fn process( + &mut self, + client: &jack::Client, + ps: &jack::ProcessScope, + ports: &mut JackPorts, + timing: &BufferTiming, + ) -> Result<()> { + // Check for consolidation response + if let Some(response) = self.post_record_controller.try_recv_response() { + self.columns[response.column] + .set_consolidated_buffer(response.row, response.consolidated_buffer)?; + } + + // Handle xruns + if timing.missed_frames > 0 { + for (i, column) in &mut self.columns.iter_mut().enumerate() { + column.handle_xrun( + &timing, + &mut self.chunk_factory, + |row, chunk, sync_offset| { + self.post_record_controller.send_request( + i, + row, + chunk, + sync_offset, + client.sample_rate(), + ) + }, + )?; + } + } + + // Process audio + let input_buffer = ports.audio_in.as_slice(ps); + let output_buffer = ports.audio_out.as_mut_slice(ps); + output_buffer.fill(0.0); + + for (i, column) in &mut self.columns.iter_mut().enumerate() { + column.process( + &timing, + input_buffer, + output_buffer, + &mut self.scratch_pad, + &mut self.chunk_factory, + |row, chunk, sync_offset| { + self.post_record_controller.send_request( + i, + row, + chunk, + sync_offset, + client.sample_rate(), + ) + }, + )?; + } + + Ok(()) + } +}