use crate::*; pub struct Column { frames_per_beat: usize, tracks: [Track; ROWS], playback_position: usize, } impl Column { pub fn new(frames_per_beat: usize) -> Self { Self { frames_per_beat, tracks: core::array::from_fn(|_| Track::new()), playback_position: 0, } } pub fn len(&self) -> usize { for track in &self.tracks { if track.is_recording() { continue; } let len = track.len(); if len > 0 { return len; } } return 0; } pub fn idle(&self) -> bool { for track in &self.tracks { if !track.is_idle() { return false; } } return true; } pub fn handle_record_button( &mut self, last_volume_setting: f32, controllers: &TrackControllers, ) -> Result<()> { let len = self.len(); let track = &mut self.tracks[controllers.row()]; if track.is_recording() { if len > 0 { track.clear(); } else { track.play(); } } else { if len > 0 { let sync_offset = len - self.playback_position; track.record_auto_stop(len, sync_offset, last_volume_setting); } else { track.record(last_volume_setting); } } Ok(()) } pub fn handle_play_button(&mut self, controllers: &TrackControllers) -> Result<()> { let track = &mut self.tracks[controllers.row()]; if track.len() > 0 && track.is_idle() { track.play(); } else if !track.is_idle() { track.stop(); } Ok(()) } pub fn handle_clear_button(&mut self, controllers: &TrackControllers) -> Result<()> { let track = &mut self.tracks[controllers.row()]; track.clear(); Ok(()) } pub fn handle_volume_update( &mut self, new_volume: f32, controllers: &TrackControllers, ) -> Result<()> { self.tracks[controllers.row()].set_volume(new_volume, controllers) } pub fn set_consolidated_buffer(&mut self, row: usize, buffer: Box<[f32]>) -> Result<()> { self.tracks[row].set_consolidated_buffer(buffer) } pub fn handle_xrun( &mut self, timing: &BufferTiming, chunk_factory: &mut impl ChunkFactory, controllers: &ColumnControllers, ) -> Result<()> { for (row, track) in self.tracks.iter_mut().enumerate() { let track_controllers = controllers.to_track_controllers(row); track.handle_xrun( timing.beat_in_missed, timing.missed_frames, chunk_factory, &track_controllers, )?; } Ok(()) } pub fn process( &mut self, timing: &BufferTiming, input_buffer: &[f32], output_buffer: &mut [f32], scratch_pad: &mut [f32], chunk_factory: &mut impl ChunkFactory, controllers: &ColumnControllers, ) -> Result<()> { let old_len = self.len(); if self.idle() { if let Some(beat_index) = timing.beat_in_buffer { // When starting from idle, always start at beat 0 (beginning of first beat) // regardless of where in the buffer the beat occurs if old_len == 0 { self.playback_position = 0; // Start at beat 0 for first recording } else { let idle_time = input_buffer.len() - beat_index as usize; self.playback_position = old_len - idle_time; } } } for (row, track) in self.tracks.iter_mut().enumerate() { let track_controllers = controllers.to_track_controllers(row); track.process( self.playback_position, timing.beat_in_buffer, input_buffer, scratch_pad, chunk_factory, &track_controllers, )?; for (output_val, scratch_pad_val) in output_buffer.iter_mut().zip(scratch_pad.iter()) { *output_val += *scratch_pad_val; } } let new_len = self.len(); if old_len == 0 && new_len > 0 { let beats = new_len / self.frames_per_beat; controllers.send_column_beats_changed(beats)?; } // Update playback position if new_len > 0 { self.playback_position = (self.playback_position + input_buffer.len()) % new_len; } else { // During recording of first track, don't use modulo - let position grow // so that beat calculation works correctly self.playback_position += input_buffer.len(); } // Send beat change message if a beat occurred in this buffer if timing.beat_in_buffer.is_some() { // Calculate which beat we're entering (after the beat that just occurred) let current_beat = if new_len > 0 { // When column has defined length, calculate beat within that loop let column_beats = new_len / self.frames_per_beat; (self.playback_position / self.frames_per_beat) % column_beats } else { // During recording of first track, calculate beat based on absolute position // The beat that just occurred is the beat we're now in self.playback_position / self.frames_per_beat }; controllers.send_column_beat_changed(current_beat)?; } Ok(()) } }