The Matrix is looping
This commit is contained in:
parent
e239610909
commit
85984963f4
@ -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<jack::AudioIn>,
|
||||
@ -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,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use crate::*;
|
||||
|
||||
/// Process MIDI events
|
||||
pub fn process_events<F: ChunkFactory, const ROWS: usize>(
|
||||
process_handler: &mut ProcessHandler<F, ROWS>,
|
||||
pub fn process_events<F: ChunkFactory>(
|
||||
process_handler: &mut ProcessHandler<F>,
|
||||
ps: &jack::ProcessScope,
|
||||
) -> Result<()> {
|
||||
// First, collect all MIDI events into a fixed-size array
|
||||
@ -46,9 +46,27 @@ pub fn process_events<F: ChunkFactory, const ROWS: usize>(
|
||||
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()?;
|
||||
}
|
||||
|
||||
@ -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<AudioChunk>,
|
||||
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<AudioChunk>,
|
||||
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,
|
||||
};
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
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,
|
||||
chunk_factory: F,
|
||||
metronome: Metronome,
|
||||
post_record_controller: PostRecordController,
|
||||
column: Column<ROWS>,
|
||||
track_matrix: TrackMatrix<F, COLS, ROWS>,
|
||||
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(
|
||||
client: &jack::Client,
|
||||
ports: JackPorts,
|
||||
@ -19,31 +20,64 @@ impl<F: ChunkFactory, const ROWS: usize> ProcessHandler<F, ROWS> {
|
||||
state: &State,
|
||||
post_record_controller: PostRecordController,
|
||||
) -> Result<Self> {
|
||||
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<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 {
|
||||
if let Err(e) = self.process_with_error_handling(client, ps) {
|
||||
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(
|
||||
&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(())
|
||||
|
||||
96
audio_engine/src/track_matrix.rs
Normal file
96
audio_engine/src/track_matrix.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user