fcb_looper/osc/src/message.rs

192 lines
7.2 KiB
Rust

use crate::*;
#[derive(Debug, Clone)]
pub enum Message {
TrackStateChanged {
column: usize,
row: usize,
state: TrackState,
},
TrackVolumeChanged {
column: usize,
row: usize,
volume: f32,
},
SelectedColumnChanged {
column: usize,
},
SelectedRowChanged {
row: usize,
},
MetronomePosition {
position: f32, // 0.0 - 1.0 position within current beat
},
Tempo {
bpm: f32,
},
}
impl Message {
pub fn from_osc_packet(packet: rosc::OscPacket) -> Result<Option<Self>> {
match packet {
rosc::OscPacket::Message(msg) => Self::from_osc_message(msg),
rosc::OscPacket::Bundle(_) => {
// For now, we don't handle bundles
Ok(None)
}
}
}
pub fn apply_to_state(&self, state: &mut State) {
match self {
Message::TrackStateChanged {
column,
row,
state: track_state,
} => {
state.set_track_state(*column, *row, track_state.clone());
}
Message::SelectedColumnChanged { column } => {
state.set_selected_column(*column);
}
Message::SelectedRowChanged { row } => {
state.set_selected_row(*row);
}
Message::TrackVolumeChanged {
column,
row,
volume,
} => {
state.set_track_volume(*column, *row, *volume);
}
Message::MetronomePosition { position } => {
state.set_metronome_position(*position);
}
Message::Tempo { bpm } => {
state.set_tempo(*bpm);
}
}
}
fn from_osc_message(msg: rosc::OscMessage) -> Result<Option<Self>> {
let rosc::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(Error::AddressParseError(addr));
}
let column: usize = parts[3].parse::<usize>().address_part_result("column")? - 1; // Convert to 0-based
let row: usize = parts[4].parse::<usize>().address_part_result("row")? - 1; // Convert to 0-based
if let Some(rosc::OscType::String(state_str)) = args.first() {
let state = state_str.parse::<TrackState>().unwrap_or(TrackState::Empty);
log::trace!("TrackStateChanged: column={column}, row={row}, state={state}");
return Ok(Some(Message::TrackStateChanged { column, row, state }));
}
} else if addr.starts_with("/looper/cell/") && addr.ends_with("/volume") {
// Parse: /looper/cell/{column}/{row}/volume
let parts: Vec<&str> = addr.split('/').collect();
if parts.len() != 6 {
return Err(Error::AddressParseError(addr));
}
let column: usize = parts[3].parse::<usize>().address_part_result("column")? - 1; // Convert to 0-based
let row: usize = parts[4].parse::<usize>().address_part_result("row")? - 1; // Convert to 0-based
if let Some(rosc::OscType::Float(volume)) = args.first() {
log::trace!("TrackVolumeChanged: column={column}, row={row}, volume={volume}");
return Ok(Some(Message::TrackVolumeChanged {
column,
row,
volume: *volume,
}));
}
} else if addr == "/looper/selected/column" {
if let Some(rosc::OscType::Int(column_1based)) = args.first() {
log::trace!("SelectedColumnChanged: column={column_1based}");
let column = (*column_1based as usize).saturating_sub(1); // Convert to 0-based
return Ok(Some(Message::SelectedColumnChanged { column }));
}
} else if addr == "/looper/selected/row" {
if let Some(rosc::OscType::Int(row_1based)) = args.first() {
log::trace!("SelectedRowChanged: row={row_1based}");
let row = (*row_1based as usize).saturating_sub(1); // Convert to 0-based
return Ok(Some(Message::SelectedRowChanged { row }));
}
} else if addr == "/looper/metronome/position" {
if let Some(rosc::OscType::Float(position)) = args.first() {
log::trace!("MetronomePosition: position={position}");
return Ok(Some(Message::MetronomePosition {
position: *position,
}));
}
} else if addr == "/looper/tempo" {
if let Some(rosc::OscType::Float(bpm)) = args.first() {
log::trace!("Tempo: bpm={bpm}");
return Ok(Some(Message::Tempo { bpm: *bpm }));
}
}
// Unknown or unsupported message
Ok(None)
}
pub fn to_osc_packet(self) -> rosc::OscPacket {
match self {
Message::TrackStateChanged { column, row, state } => {
let address = format!("/looper/cell/{}/{}/state", column + 1, row + 1); // 1-based indexing
rosc::OscPacket::Message(rosc::OscMessage {
addr: address,
args: vec![rosc::OscType::String(state.to_string())],
})
}
Message::TrackVolumeChanged {
column,
row,
volume,
} => {
let address = format!("/looper/cell/{}/{}/volume", column + 1, row + 1); // 1-based indexing
rosc::OscPacket::Message(rosc::OscMessage {
addr: address,
args: vec![rosc::OscType::Float(volume)],
})
}
Message::SelectedColumnChanged { column } => {
let address = "/looper/selected/column".to_string();
rosc::OscPacket::Message(rosc::OscMessage {
addr: address,
args: vec![rosc::OscType::Int((column + 1) as i32)], // 1-based indexing
})
}
Message::SelectedRowChanged { row } => {
let address = "/looper/selected/row".to_string();
rosc::OscPacket::Message(rosc::OscMessage {
addr: address,
args: vec![rosc::OscType::Int((row + 1) as i32)], // 1-based indexing
})
}
Message::MetronomePosition { position } => {
let address = "/looper/metronome/position".to_string();
rosc::OscPacket::Message(rosc::OscMessage {
addr: address,
args: vec![rosc::OscType::Float(position)],
})
}
Message::Tempo { bpm } => {
let address = "/looper/tempo".to_string();
rosc::OscPacket::Message(rosc::OscMessage {
addr: address,
args: vec![rosc::OscType::Float(bpm)],
})
}
}
}
}