188 lines
5.7 KiB
Rust

use crate::*;
pub struct Column<const ROWS: usize> {
frames_per_beat: usize,
tracks: [Track; ROWS],
playback_position: usize,
}
impl<const ROWS: usize> Column<ROWS> {
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(())
}
}