fcb_looper/src/process_handler.rs
2025-06-06 19:24:21 +02:00

130 lines
4.6 KiB
Rust

use crate::*;
use crate::metronome::Metronome;
pub struct ProcessHandler<F: ChunkFactory> {
track: Track,
playback_position: usize,
pub ports: JackPorts,
chunk_factory: F,
metronome: Metronome,
}
impl<F: ChunkFactory> ProcessHandler<F> {
pub fn new(ports: JackPorts, mut chunk_factory: F, beep_samples: Arc<AudioChunk>) -> Result<Self> {
Ok(Self {
track: Track::new(&mut chunk_factory)?,
playback_position: 0,
ports,
chunk_factory,
metronome: Metronome::new(beep_samples),
})
}
/// Handle record/play toggle button (Button 1)
pub fn record_toggle(&mut self) -> Result<()> {
match self.track.state() {
TrackState::Idle => {
// Clear previous recording and start new recording
self.track.clear(&mut self.chunk_factory)?;
self.playback_position = 0;
self.track.set_state(TrackState::Recording);
}
TrackState::Recording => {
self.track.set_state(TrackState::Playing);
self.playback_position = 0;
}
TrackState::Playing => {
self.track.set_state(TrackState::Idle);
}
}
Ok(())
}
/// Handle play/mute toggle button (Button 2)
pub fn play_toggle(&mut self) -> Result<()> {
// Trigger beep for testing
self.metronome.trigger_beep();
match self.track.state() {
TrackState::Idle | TrackState::Recording => {
if self.track.len() > 0 {
self.track.set_state(TrackState::Playing);
self.playback_position = 0;
}
}
TrackState::Playing => {
self.track.set_state(TrackState::Idle);
}
}
Ok(())
}
}
impl<F: ChunkFactory> jack::ProcessHandler for ProcessHandler<F> {
fn process(&mut self, client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
// Process MIDI first
if midi::process_events(self, ps).is_err() {
return jack::Control::Quit;
}
let input_buffer = self.ports.audio_in.as_slice(ps);
let output_buffer = self.ports.audio_out.as_mut_slice(ps);
let click_track_buffer = self.ports.click_track_out.as_mut_slice(ps);
let jack_buffer_size = client.buffer_size() as usize;
// Process metronome/click track
if self.metronome.process_audio(click_track_buffer).is_err() {
return jack::Control::Quit;
}
let mut index = 0;
while index < jack_buffer_size {
match self.track.state() {
TrackState::Recording => {
// Recording - continue until manually stopped
let sample_count_to_append = jack_buffer_size - index;
let samples_to_append = &input_buffer[index..index + sample_count_to_append];
if self
.track
.append_samples(samples_to_append, &mut self.chunk_factory)
.is_err()
{
return jack::Control::Quit;
};
output_buffer[index..(index + sample_count_to_append)].fill(0.0);
index += sample_count_to_append;
}
TrackState::Playing => {
// Playback
let sample_count_to_play = jack_buffer_size - index;
let sample_count_to_play =
sample_count_to_play.min(self.track.len() - self.playback_position);
if self
.track
.copy_samples(
&mut output_buffer[index..(index + sample_count_to_play)],
self.playback_position,
)
.is_err()
{
return jack::Control::Quit;
}
index += sample_count_to_play;
self.playback_position += sample_count_to_play;
if self.playback_position >= self.track.len() {
self.playback_position = 0;
}
}
TrackState::Idle => {
// Idle - output silence
output_buffer[index..jack_buffer_size].fill(0.0);
break;
}
}
}
jack::Control::Continue
}
}