fcb_looper/gui/src/osc/message.rs

92 lines
3.2 KiB
Rust

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<Option<Self>> {
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<Option<Self>> {
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::<usize>()? - 1; // Convert to 0-based
let row: usize = parts[4].parse::<usize>()? - 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
}
}
}