bbx_net/osc/
server.rs

1//! OSC UDP server for receiving control messages.
2
3use std::{
4    net::{SocketAddr, UdpSocket},
5    sync::Arc,
6    thread::{self, JoinHandle},
7};
8
9use crate::{
10    address::NodeId,
11    buffer::NetBufferProducer,
12    clock::ClockSync,
13    error::{NetError, Result},
14    osc::parser::parse_osc_message,
15};
16
17/// Configuration for the OSC server.
18pub struct OscServerConfig {
19    /// Address to bind to (e.g., "0.0.0.0:9000").
20    pub bind_addr: SocketAddr,
21    /// This node's ID for filtering targeted messages.
22    pub node_id: NodeId,
23    /// Buffer size for UDP packets.
24    pub recv_buffer_size: usize,
25}
26
27impl Default for OscServerConfig {
28    fn default() -> Self {
29        Self {
30            bind_addr: "0.0.0.0:9000".parse().unwrap(),
31            node_id: NodeId::default(),
32            recv_buffer_size: 1024,
33        }
34    }
35}
36
37/// OSC server for TouchOSC/Max/PD integration.
38///
39/// Listens on a UDP port for OSC messages and forwards them to a
40/// `NetBufferProducer` for consumption by the audio thread.
41pub struct OscServer {
42    config: OscServerConfig,
43    producer: NetBufferProducer,
44    clock_sync: Arc<ClockSync>,
45}
46
47impl OscServer {
48    /// Create a new OSC server.
49    ///
50    /// # Arguments
51    ///
52    /// * `config` - Server configuration
53    /// * `producer` - Buffer producer for sending messages to audio thread
54    pub fn new(config: OscServerConfig, producer: NetBufferProducer) -> Self {
55        Self {
56            config,
57            producer,
58            clock_sync: Arc::new(ClockSync::new()),
59        }
60    }
61
62    /// Create with default configuration.
63    pub fn with_defaults(producer: NetBufferProducer) -> Self {
64        Self::new(OscServerConfig::default(), producer)
65    }
66
67    /// Get the clock synchronization instance.
68    pub fn clock(&self) -> Arc<ClockSync> {
69        Arc::clone(&self.clock_sync)
70    }
71
72    /// Start the OSC server in the current thread (blocking).
73    ///
74    /// This method blocks forever, listening for OSC messages.
75    /// Call from a dedicated thread.
76    pub fn run(mut self) -> Result<()> {
77        let socket = UdpSocket::bind(self.config.bind_addr)?;
78        let mut buf = vec![0u8; self.config.recv_buffer_size];
79
80        loop {
81            match socket.recv_from(&mut buf) {
82                Ok((len, src_addr)) => {
83                    self.handle_packet(&buf[..len], src_addr);
84                }
85                Err(e) => {
86                    if e.kind() != std::io::ErrorKind::WouldBlock {
87                        return Err(NetError::IoError);
88                    }
89                }
90            }
91        }
92    }
93
94    /// Start the OSC server in a background thread.
95    ///
96    /// Returns a `JoinHandle` that can be used to wait for the server
97    /// to finish (which normally only happens on error).
98    pub fn spawn(self) -> JoinHandle<Result<()>> {
99        thread::spawn(move || self.run())
100    }
101
102    fn handle_packet(&mut self, data: &[u8], _src_addr: SocketAddr) {
103        let source_node_id = self.config.node_id;
104
105        if let Ok(messages) = parse_osc_message(data, source_node_id) {
106            for mut msg in messages {
107                msg.timestamp = self.clock_sync.now();
108
109                let _ = self.producer.try_send(msg);
110            }
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::buffer::net_buffer;
119
120    #[test]
121    fn test_osc_server_config_default() {
122        let config = OscServerConfig::default();
123        assert_eq!(config.bind_addr.port(), 9000);
124        assert_eq!(config.recv_buffer_size, 1024);
125    }
126
127    #[test]
128    fn test_osc_server_creation() {
129        let (producer, _consumer) = net_buffer(64);
130        let server = OscServer::with_defaults(producer);
131        assert!(Arc::strong_count(&server.clock_sync) >= 1);
132    }
133}