The Matrix is looping

This commit is contained in:
Geens 2025-06-12 13:37:19 +02:00
parent e239610909
commit 85984963f4
5 changed files with 177 additions and 57 deletions

View File

@ -14,6 +14,7 @@ mod post_record_handler;
mod process_handler; mod process_handler;
mod state; mod state;
mod track; mod track;
mod track_matrix;
use std::sync::Arc; use std::sync::Arc;
@ -36,6 +37,7 @@ use post_record_handler::PostRecordHandler;
use process_handler::ProcessHandler; use process_handler::ProcessHandler;
use state::State; use state::State;
use track::Track; use track::Track;
use track_matrix::TrackMatrix;
pub struct JackPorts { pub struct JackPorts {
pub audio_in: jack::Port<jack::AudioIn>, pub audio_in: jack::Port<jack::AudioIn>,
@ -70,7 +72,7 @@ async fn main() {
let (mut post_record_handler, post_record_controller) = let (mut post_record_handler, post_record_controller) =
PostRecordHandler::new().expect("Could not create post-record handler"); PostRecordHandler::new().expect("Could not create post-record handler");
let process_handler = ProcessHandler::<_, 5>::new( let process_handler = ProcessHandler::new(
&jack_client, &jack_client,
ports, ports,
allocator, allocator,

View File

@ -1,8 +1,8 @@
use crate::*; use crate::*;
/// Process MIDI events /// Process MIDI events
pub fn process_events<F: ChunkFactory, const ROWS: usize>( pub fn process_events<F: ChunkFactory>(
process_handler: &mut ProcessHandler<F, ROWS>, process_handler: &mut ProcessHandler<F>,
ps: &jack::ProcessScope, ps: &jack::ProcessScope,
) -> Result<()> { ) -> Result<()> {
// First, collect all MIDI events into a fixed-size array // First, collect all MIDI events into a fixed-size array
@ -46,9 +46,27 @@ pub fn process_events<F: ChunkFactory, const ROWS: usize>(
22 => { 22 => {
process_handler.handle_button_3()?; process_handler.handle_button_3()?;
} }
23 => {
process_handler.handle_button_4()?;
}
24 => { 24 => {
process_handler.handle_button_5()?; 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 => { 30 => {
process_handler.handle_button_up()?; process_handler.handle_button_up()?;
} }

View File

@ -4,6 +4,7 @@ use std::path::PathBuf;
/// Request to process a recorded chunk chain with sync offset /// Request to process a recorded chunk chain with sync offset
#[derive(Debug)] #[derive(Debug)]
pub struct PostRecordRequest { pub struct PostRecordRequest {
pub column: usize,
pub row: usize, pub row: usize,
pub chunk_chain: Arc<AudioChunk>, pub chunk_chain: Arc<AudioChunk>,
pub sync_offset: usize, pub sync_offset: usize,
@ -13,6 +14,7 @@ pub struct PostRecordRequest {
/// Response containing the consolidated buffer /// Response containing the consolidated buffer
#[derive(Debug)] #[derive(Debug)]
pub struct PostRecordResponse { pub struct PostRecordResponse {
pub column: usize,
pub row: usize, pub row: usize,
pub consolidated_buffer: Box<[f32]>, pub consolidated_buffer: Box<[f32]>,
} }
@ -28,12 +30,14 @@ impl PostRecordController {
/// Send a post-record processing request (RT-safe) /// Send a post-record processing request (RT-safe)
pub fn send_request( pub fn send_request(
&self, &self,
column: usize,
row: usize, row: usize,
chunk_chain: Arc<AudioChunk>, chunk_chain: Arc<AudioChunk>,
sync_offset: usize, sync_offset: usize,
sample_rate: usize, sample_rate: usize,
) -> Result<()> { ) -> Result<()> {
let request = PostRecordRequest { let request = PostRecordRequest {
column,
row, row,
chunk_chain, chunk_chain,
sync_offset, sync_offset,
@ -117,6 +121,7 @@ impl PostRecordHandler {
// Step 2: Send consolidated buffer back to RT thread immediately // Step 2: Send consolidated buffer back to RT thread immediately
let response = PostRecordResponse { let response = PostRecordResponse {
column: request.column,
row: request.row, row: request.row,
consolidated_buffer, consolidated_buffer,
}; };

View File

@ -1,16 +1,17 @@
use crate::*; use crate::*;
pub struct ProcessHandler<F: ChunkFactory, const ROWS: usize> { const COLS: usize = 5;
const ROWS: usize = 5;
pub struct ProcessHandler<F: ChunkFactory> {
pub ports: JackPorts, pub ports: JackPorts,
chunk_factory: F,
metronome: Metronome, metronome: Metronome,
post_record_controller: PostRecordController, track_matrix: TrackMatrix<F, COLS, ROWS>,
column: Column<ROWS>,
selected_row: usize, selected_row: usize,
scratch_pad: Box<[f32]>, selected_column: usize,
} }
impl<F: ChunkFactory, const ROWS: usize> ProcessHandler<F, ROWS> { impl<F: ChunkFactory> ProcessHandler<F> {
pub fn new( pub fn new(
client: &jack::Client, client: &jack::Client,
ports: JackPorts, ports: JackPorts,
@ -19,31 +20,64 @@ impl<F: ChunkFactory, const ROWS: usize> ProcessHandler<F, ROWS> {
state: &State, state: &State,
post_record_controller: PostRecordController, post_record_controller: PostRecordController,
) -> Result<Self> { ) -> Result<Self> {
let track_matrix = TrackMatrix::new(
client,
chunk_factory,
state,
post_record_controller,
)?;
Ok(Self { Ok(Self {
ports, ports,
chunk_factory,
metronome: Metronome::new(beep_samples, state), metronome: Metronome::new(beep_samples, state),
post_record_controller, track_matrix,
column: Column::new(state.metronome.frames_per_beat),
selected_row: 0, 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<()> { 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<()> { 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<()> { pub fn handle_button_3(&mut self) -> Result<()> {
Ok(()) Ok(())
} }
pub fn handle_button_4(&mut self) -> Result<()> {
Ok(())
}
pub fn handle_button_5(&mut self) -> Result<()> { 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<()> { pub fn handle_button_up(&mut self) -> Result<()> {
@ -61,7 +95,7 @@ impl<F: ChunkFactory, const ROWS: usize> ProcessHandler<F, ROWS> {
} }
} }
impl<F: ChunkFactory, const ROWS: usize> jack::ProcessHandler for ProcessHandler<F, ROWS> { impl<F: ChunkFactory> jack::ProcessHandler for ProcessHandler<F> {
fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control { fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
if let Err(e) = self.process_with_error_handling(client, ps) { if let Err(e) = self.process_with_error_handling(client, ps) {
log::error!("Error processing audio: {}", e); log::error!("Error processing audio: {}", e);
@ -72,59 +106,24 @@ impl<F: ChunkFactory, const ROWS: usize> jack::ProcessHandler for ProcessHandler
} }
} }
impl<F: ChunkFactory, const ROWS: usize> ProcessHandler<F, ROWS> { impl<F: ChunkFactory> ProcessHandler<F> {
fn process_with_error_handling( fn process_with_error_handling(
&mut self, &mut self,
client: &jack::Client, client: &jack::Client,
ps: &jack::ProcessScope, ps: &jack::ProcessScope,
) -> Result<()> { ) -> 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 // Process metronome and get beat timing information
let timing = self.metronome.process(ps, &mut self.ports)?; 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 // Process MIDI
midi::process_events(self, ps)?; midi::process_events(self, ps)?;
// Process audio // Process audio
let input_buffer = self.ports.audio_in.as_slice(ps); self.track_matrix.process(
let output_buffer = self.ports.audio_out.as_mut_slice(ps); client,
output_buffer.fill(0.0); ps,
&mut self.ports,
self.column.process(
&timing, &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(()) Ok(())

View File

@ -0,0 +1,96 @@
use crate::*;
pub struct TrackMatrix<F: ChunkFactory, const COLS: usize, const ROWS: usize> {
chunk_factory: F,
post_record_controller: PostRecordController,
columns: [Column<ROWS>; COLS],
scratch_pad: Box<[f32]>,
}
impl<F: ChunkFactory, const COLS: usize, const ROWS: usize> TrackMatrix<F, COLS, ROWS> {
pub fn new(
client: &jack::Client,
chunk_factory: F,
state: &State,
post_record_controller: PostRecordController,
) -> Result<Self> {
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(())
}
}