dirtydata-runtime 0.1.0

High-performance JIT-enabled audio engine for DirtyData forensic graphs
Documentation
use std::net::UdpSocket;
use rosc::{OscPacket, OscMessage as RoscMessage};
use crossbeam_channel::{Sender, Receiver};
use crate::nodes::OscMessage;
use crate::{EngineCommand, ParameterUpdate, StableId};

pub struct OscHandler {
    command_tx: Sender<EngineCommand>,
}

impl OscHandler {
    pub fn new(command_tx: Sender<EngineCommand>) -> Self {
        Self { command_tx }
    }

    pub fn spawn_input_thread(&self, addr: &str) {
        let command_tx = self.command_tx.clone();
        let addr = addr.to_string();
        std::thread::spawn(move || {
            let socket = match UdpSocket::bind(&addr) {
                Ok(s) => s,
                Err(e) => {
                    tracing::error!("Failed to bind OSC input socket to {}: {}", addr, e);
                    return;
                }
            };
            let mut buf = [0u8; 4096];
            loop {
                match socket.recv_from(&mut buf) {
                    Ok((size, _)) => {
                        if let Ok((_, packet)) = rosc::decoder::decode_udp(&buf[..size]) {
                            if let OscPacket::Message(msg) = packet {
                                Self::handle_message(&command_tx, msg);
                            }
                        }
                    }
                    Err(e) => {
                        tracing::error!("OSC input recv error: {}", e);
                        break;
                    }
                }
            }
        });
    }

    fn handle_message(command_tx: &Sender<EngineCommand>, msg: RoscMessage) {
        let parts: Vec<&str> = msg.addr.split('/').filter(|s: &&str| !s.is_empty()).collect();
        if parts.len() == 3 && parts[0] == "node" {
            if let (Ok(node_id), Some(val)) = (parts[1].parse::<StableId>(), msg.args.first()) {
                let float_val = match val {
                    &rosc::OscType::Float(f) => f,
                    &rosc::OscType::Double(d) => d as f32,
                    &rosc::OscType::Int(i) => i as f32,
                    _ => 0.0,
                };
                let _ = command_tx.send(EngineCommand::UpdateParameter(ParameterUpdate {
                    node_id,
                    param: parts[2].to_string(),
                    value: float_val,
                    provenance: vec!["osc".to_string()],
                }));
            }
        }
    }

    pub fn spawn_output_thread(rx: Receiver<OscMessage>, target_addr: String) {
        std::thread::spawn(move || {
            let socket = match UdpSocket::bind("0.0.0.0:0") {
                Ok(s) => s,
                Err(e) => {
                    tracing::error!("Failed to bind OSC output socket: {}", e);
                    return;
                }
            };
            while let Ok(msg) = rx.recv() {
                let packet = OscPacket::Message(RoscMessage { addr: msg.addr, args: msg.args });
                if let Ok(bytes) = rosc::encoder::encode(&packet) {
                    let b: Vec<u8> = bytes;
                    let _ = socket.send_to(&b, &target_addr);
                }
            }
        });
    }
}