bbx_net/osc/
parser.rs

1//! OSC message parsing utilities.
2
3use rosc::{OscMessage, OscPacket, OscType};
4
5use crate::{
6    address::{AddressPath, NodeId},
7    error::Result,
8    message::NetMessage,
9};
10
11/// Parsed OSC data ready for conversion to NetMessage.
12pub struct ParsedOscMessage {
13    pub address: AddressPath,
14    pub value: f32,
15}
16
17/// Parse an OSC packet into network messages.
18///
19/// Handles both single messages and bundles. Returns a Vec of parsed messages.
20pub fn parse_osc_packet(packet: &OscPacket) -> Vec<ParsedOscMessage> {
21    let mut messages = Vec::new();
22    collect_messages(packet, &mut messages);
23    messages
24}
25
26fn collect_messages(packet: &OscPacket, out: &mut Vec<ParsedOscMessage>) {
27    match packet {
28        OscPacket::Message(msg) => {
29            if let Some(parsed) = parse_single_message(msg) {
30                out.push(parsed);
31            }
32        }
33        OscPacket::Bundle(bundle) => {
34            for content in &bundle.content {
35                collect_messages(content, out);
36            }
37        }
38    }
39}
40
41fn parse_single_message(msg: &OscMessage) -> Option<ParsedOscMessage> {
42    let address = AddressPath::parse(&msg.addr).ok()?;
43
44    let value = msg.args.first().and_then(|arg| match arg {
45        OscType::Float(f) => Some(*f),
46        OscType::Int(i) => Some(*i as f32),
47        OscType::Double(d) => Some(*d as f32),
48        OscType::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
49        _ => None,
50    })?;
51
52    Some(ParsedOscMessage { address, value })
53}
54
55/// Parse a raw OSC message and convert to NetMessage.
56///
57/// # Arguments
58///
59/// * `data` - Raw OSC packet bytes
60/// * `source_node_id` - Node ID to attribute the message to
61///
62/// # Returns
63///
64/// A vector of NetMessages parsed from the packet.
65pub fn parse_osc_message(data: &[u8], source_node_id: NodeId) -> Result<Vec<NetMessage>> {
66    let packet = rosc::decoder::decode_udp(data)
67        .map_err(|_| crate::error::NetError::ParseError)?
68        .1;
69
70    let parsed = parse_osc_packet(&packet);
71
72    Ok(parsed
73        .into_iter()
74        .map(|p| NetMessage::param_change(&p.address.param_name, p.value, source_node_id))
75        .collect())
76}
77
78#[cfg(test)]
79mod tests {
80    use rosc::encoder;
81
82    use super::*;
83
84    #[test]
85    fn test_parse_float_message() {
86        let msg = OscMessage {
87            addr: "/blocks/param/gain".to_string(),
88            args: vec![OscType::Float(0.75)],
89        };
90        let packet = OscPacket::Message(msg);
91        let bytes = encoder::encode(&packet).unwrap();
92
93        let node_id = NodeId::default();
94        let messages = parse_osc_message(&bytes, node_id).unwrap();
95
96        assert_eq!(messages.len(), 1);
97        assert!((messages[0].payload.value().unwrap() - 0.75).abs() < f32::EPSILON);
98    }
99
100    #[test]
101    fn test_parse_int_message() {
102        let msg = OscMessage {
103            addr: "/blocks/param/level".to_string(),
104            args: vec![OscType::Int(100)],
105        };
106        let packet = OscPacket::Message(msg);
107        let bytes = encoder::encode(&packet).unwrap();
108
109        let node_id = NodeId::default();
110        let messages = parse_osc_message(&bytes, node_id).unwrap();
111
112        assert_eq!(messages.len(), 1);
113        assert!((messages[0].payload.value().unwrap() - 100.0).abs() < f32::EPSILON);
114    }
115
116    #[test]
117    fn test_parse_bool_message() {
118        let msg = OscMessage {
119            addr: "/blocks/param/enabled".to_string(),
120            args: vec![OscType::Bool(true)],
121        };
122        let packet = OscPacket::Message(msg);
123        let bytes = encoder::encode(&packet).unwrap();
124
125        let node_id = NodeId::default();
126        let messages = parse_osc_message(&bytes, node_id).unwrap();
127
128        assert_eq!(messages.len(), 1);
129        assert!((messages[0].payload.value().unwrap() - 1.0).abs() < f32::EPSILON);
130    }
131
132    #[test]
133    fn test_parse_invalid_address() {
134        let msg = OscMessage {
135            addr: "/invalid/path".to_string(),
136            args: vec![OscType::Float(0.5)],
137        };
138        let packet = OscPacket::Message(msg);
139        let bytes = encoder::encode(&packet).unwrap();
140
141        let node_id = NodeId::default();
142        let messages = parse_osc_message(&bytes, node_id).unwrap();
143
144        assert!(messages.is_empty());
145    }
146}