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 { 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 { 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; 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 Column { pub fn new() -> Self { Self { tracks: std::array::from_fn(|_| Track { state: TrackState::Empty, volume: 1.0, }), beat_count: 0, current_beat: 0, } } } impl State { 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 { 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; } } }