188 lines
5.7 KiB
Rust
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(())
|
|
}
|
|
}
|