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 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,
|
||||||
|
|||||||
@ -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()?;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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(())
|
||||||
|
|||||||
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