Skip to main content

dirtydata_runtime/
osc.rs

1use std::net::UdpSocket;
2use rosc::{OscPacket, OscMessage as RoscMessage};
3use crossbeam_channel::{Sender, Receiver};
4use crate::nodes::OscMessage;
5use crate::{EngineCommand, ParameterUpdate, StableId};
6
7pub struct OscHandler {
8    command_tx: Sender<EngineCommand>,
9}
10
11impl OscHandler {
12    pub fn new(command_tx: Sender<EngineCommand>) -> Self {
13        Self { command_tx }
14    }
15
16    pub fn spawn_input_thread(&self, addr: &str) {
17        let command_tx = self.command_tx.clone();
18        let addr = addr.to_string();
19        std::thread::spawn(move || {
20            let socket = match UdpSocket::bind(&addr) {
21                Ok(s) => s,
22                Err(e) => {
23                    tracing::error!("Failed to bind OSC input socket to {}: {}", addr, e);
24                    return;
25                }
26            };
27            let mut buf = [0u8; 4096];
28            loop {
29                match socket.recv_from(&mut buf) {
30                    Ok((size, _)) => {
31                        if let Ok((_, packet)) = rosc::decoder::decode_udp(&buf[..size]) {
32                            if let OscPacket::Message(msg) = packet {
33                                Self::handle_message(&command_tx, msg);
34                            }
35                        }
36                    }
37                    Err(e) => {
38                        tracing::error!("OSC input recv error: {}", e);
39                        break;
40                    }
41                }
42            }
43        });
44    }
45
46    fn handle_message(command_tx: &Sender<EngineCommand>, msg: RoscMessage) {
47        let parts: Vec<&str> = msg.addr.split('/').filter(|s: &&str| !s.is_empty()).collect();
48        if parts.len() == 3 && parts[0] == "node" {
49            if let (Ok(node_id), Some(val)) = (parts[1].parse::<StableId>(), msg.args.first()) {
50                let float_val = match val {
51                    &rosc::OscType::Float(f) => f,
52                    &rosc::OscType::Double(d) => d as f32,
53                    &rosc::OscType::Int(i) => i as f32,
54                    _ => 0.0,
55                };
56                let _ = command_tx.send(EngineCommand::UpdateParameter(ParameterUpdate {
57                    node_id,
58                    param: parts[2].to_string(),
59                    value: float_val,
60                    provenance: vec!["osc".to_string()],
61                }));
62            }
63        }
64    }
65
66    pub fn spawn_output_thread(rx: Receiver<OscMessage>, target_addr: String) {
67        std::thread::spawn(move || {
68            let socket = match UdpSocket::bind("0.0.0.0:0") {
69                Ok(s) => s,
70                Err(e) => {
71                    tracing::error!("Failed to bind OSC output socket: {}", e);
72                    return;
73                }
74            };
75            while let Ok(msg) = rx.recv() {
76                let packet = OscPacket::Message(RoscMessage { addr: msg.addr, args: msg.args });
77                if let Ok(bytes) = rosc::encoder::encode(&packet) {
78                    let b: Vec<u8> = bytes;
79                    let _ = socket.send_to(&b, &target_addr);
80                }
81            }
82        });
83    }
84}