use crate::display_state::{DisplayState, TrackState}; use anyhow::{anyhow, Result}; use rosc::{OscMessage, OscPacket, OscType}; #[derive(Debug, Clone)] pub enum StateUpdate { TrackStateChanged { column: usize, // 0-based row: usize, // 0-based state: TrackState, }, SelectedColumnChanged { column: usize, // 0-based }, SelectedRowChanged { row: usize, // 0-based }, } impl StateUpdate { pub fn from_osc_packet(packet: OscPacket) -> Result> { match packet { OscPacket::Message(msg) => Self::from_osc_message(msg), OscPacket::Bundle(_) => { // For now, we don't handle bundles Ok(None) } } } pub fn apply_to_state(&self, state: &mut DisplayState) { match self { StateUpdate::TrackStateChanged { column, row, state: track_state, } => { state.update_track_state(*column, *row, track_state.clone()); } StateUpdate::SelectedColumnChanged { column } => { state.update_selected_column(*column); } StateUpdate::SelectedRowChanged { row } => { state.update_selected_row(*row); } } } fn from_osc_message(msg: OscMessage) -> Result> { let OscMessage { addr, args } = msg; if addr.starts_with("/looper/cell/") && addr.ends_with("/state") { // Parse: /looper/cell/{column}/{row}/state let parts: Vec<&str> = addr.split('/').collect(); if parts.len() != 6 { return Err(anyhow!("Invalid cell state address format: {}", addr)); } let column: usize = parts[3].parse::()? - 1; // Convert to 0-based let row: usize = parts[4].parse::()? - 1; // Convert to 0-based if let Some(OscType::String(state_str)) = args.first() { let state = Self::track_state_from_osc_string(state_str); return Ok(Some(StateUpdate::TrackStateChanged { column, row, state })); } } else if addr == "/looper/selected/column" { if let Some(OscType::Int(column_1based)) = args.first() { let column = (*column_1based as usize).saturating_sub(1); // Convert to 0-based return Ok(Some(StateUpdate::SelectedColumnChanged { column })); } } else if addr == "/looper/selected/row" { if let Some(OscType::Int(row_1based)) = args.first() { let row = (*row_1based as usize).saturating_sub(1); // Convert to 0-based return Ok(Some(StateUpdate::SelectedRowChanged { row })); } } // Unknown or unsupported message Ok(None) } fn track_state_from_osc_string(s: &str) -> TrackState { match s { "empty" => TrackState::Empty, "ready" => TrackState::Ready, "recording" => TrackState::Recording, "playing" => TrackState::Playing, _ => TrackState::Empty, // Default fallback } } }