fcb_looper/osc/src/state.rs

189 lines
5.6 KiB
Rust

use crate::*;
#[derive(Clone, Debug, PartialEq, strum::Display, strum::EnumString)]
pub enum TrackState {
Empty,
Idle,
Playing,
Recording,
}
#[derive(Debug, Clone)]
pub struct Track {
pub state: TrackState,
pub volume: f32,
}
#[derive(Debug, Clone)]
pub struct Column<const ROWS: usize> {
pub tracks: [Track; ROWS],
pub beat_count: usize, // total beats in column (0 = no beats set)
pub current_beat: usize, // current beat position (0-based internally)
}
#[derive(Debug, Clone)]
pub struct State<const COLS: usize, const ROWS: usize> {
pub selected_column: usize, // 0-based (converted to 1-based for OSC)
pub selected_row: usize, // 0-based (converted to 1-based for OSC)
pub columns: [Column<ROWS>; COLS],
pub metronome_position: f32, // 0.0 - 1.0 position within current beat
pub metronome_timestamp: std::time::Instant, // When position was last updated
pub tempo: f32, // BPM
}
impl<const ROWS: usize> Column<ROWS> {
pub fn new() -> Self {
Self {
tracks: std::array::from_fn(|_| Track {
state: TrackState::Empty,
volume: 1.0,
}),
beat_count: 0,
current_beat: 0,
}
}
}
impl<const COLS: usize, const ROWS: usize> State<COLS, ROWS> {
pub fn new(tempo: f32) -> Self {
Self {
selected_column: 0,
selected_row: 0,
columns: std::array::from_fn(|_| Column::new()),
metronome_position: 0.0,
metronome_timestamp: std::time::Instant::now(),
tempo,
}
}
pub fn update(&mut self, message: &Message) {
match message {
Message::TrackStateChanged { column, row, state } => {
if *column < COLS && *row < ROWS {
self.columns[*column].tracks[*row].state = state.clone();
}
}
Message::TrackVolumeChanged {
column,
row,
volume,
} => {
if *column < COLS && *row < ROWS {
self.columns[*column].tracks[*row].volume = *volume;
}
}
Message::SelectedColumnChanged { column } => {
if *column < COLS {
self.selected_column = *column;
}
}
Message::SelectedRowChanged { row } => {
if *row < ROWS {
self.selected_row = *row;
}
}
Message::MetronomePosition { position } => {
self.set_metronome_position(*position);
}
Message::Tempo { bpm } => {
self.set_tempo(*bpm);
}
Message::ColumnBeatsChanged { column, beats } => {
self.set_column_beats(*column, *beats);
}
Message::ColumnBeatChanged { column, beat } => {
self.set_column_beat(*column, *beat);
}
}
}
pub fn create_state_dump(&self) -> Vec<Message> {
let mut messages = Vec::new();
// Send tempo
messages.push(Message::Tempo { bpm: self.tempo });
// Send current selections
messages.push(Message::SelectedColumnChanged {
column: self.selected_column,
});
messages.push(Message::SelectedRowChanged {
row: self.selected_row,
});
// Send column beat information and cell states/volumes
for (column, column_data) in self.columns.iter().enumerate() {
// Send column beat info
messages.push(Message::ColumnBeatsChanged {
column,
beats: column_data.beat_count,
});
messages.push(Message::ColumnBeatChanged {
column,
beat: column_data.current_beat,
});
// Send all cell states and volumes for this column
for (row, track) in column_data.tracks.iter().enumerate() {
messages.push(Message::TrackStateChanged {
column,
row,
state: track.state.clone(),
});
messages.push(Message::TrackVolumeChanged {
column,
row,
volume: track.volume,
});
}
}
messages
}
pub fn set_track_state(&mut self, column: usize, row: usize, state: TrackState) {
if column < COLS && row < ROWS {
self.columns[column].tracks[row].state = state;
}
}
pub fn set_selected_column(&mut self, column: usize) {
if column < COLS {
self.selected_column = column;
}
}
pub fn set_selected_row(&mut self, row: usize) {
if row < ROWS {
self.selected_row = row;
}
}
pub fn set_track_volume(&mut self, column: usize, row: usize, volume: f32) {
if column < COLS && row < ROWS {
self.columns[column].tracks[row].volume = volume;
}
}
pub fn set_metronome_position(&mut self, position: f32) {
self.metronome_position = position;
self.metronome_timestamp = std::time::Instant::now();
}
pub fn set_tempo(&mut self, bpm: f32) {
self.tempo = bpm;
}
pub fn set_column_beats(&mut self, column: usize, beats: usize) {
if column < COLS {
self.columns[column].beat_count = beats;
}
}
pub fn set_column_beat(&mut self, column: usize, beat: usize) {
if column < COLS {
self.columns[column].current_beat = beat;
}
}
}