192 lines
7.2 KiB
Rust
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)],
|
|
})
|
|
}
|
|
}
|
|
}
|
|
} |