initial code scetch
This commit is contained in:
108
src/osc.rs
Normal file
108
src/osc.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
/// OSC server and sender
|
||||
///
|
||||
/// Listen port : 9000
|
||||
/// Send port : 9001
|
||||
///
|
||||
/// Incoming address map (/deck/<n>/...):
|
||||
/// play, pause, stop
|
||||
/// seek f (0.0–1.0)
|
||||
/// fader f (0.0–1.0) → wpctl set-volume on stream node
|
||||
/// pitch f (semitones) → pw-cli Props on filter node
|
||||
/// tempo f (multiplier)→ pw-cli Props on filter node
|
||||
/// cue/set, cue/goto
|
||||
///
|
||||
/// Outgoing:
|
||||
/// /deck/<n>/position f
|
||||
/// /deck/<n>/vu f f
|
||||
/// /deck/<n>/loaded s
|
||||
use std::{net::UdpSocket, thread};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam_channel::Sender;
|
||||
use rosc::{encoder, OscMessage, OscPacket, OscType};
|
||||
|
||||
use crate::audio::AudioCmd;
|
||||
use crate::deck::DeckId;
|
||||
|
||||
pub const LISTEN_PORT: u16 = 9000;
|
||||
pub const SEND_PORT: u16 = 9001;
|
||||
|
||||
/// Messages that require effect chain interaction (pitch/tempo/fader)
|
||||
/// are sent back through AppCmd so the app can call the appropriate
|
||||
/// EffectChain / wpctl method.
|
||||
#[derive(Debug)]
|
||||
pub enum OscAppCmd {
|
||||
Audio(AudioCmd),
|
||||
SetPitch { deck: DeckId, semitones: f32 },
|
||||
SetTempo { deck: DeckId, multiplier: f32 },
|
||||
SetFader { deck: DeckId, value: f32 },
|
||||
}
|
||||
|
||||
pub fn start_osc_server(cmd_tx: Sender<OscAppCmd>) -> Result<()> {
|
||||
let socket = UdpSocket::bind(format!("0.0.0.0:{}", LISTEN_PORT))?;
|
||||
tracing::info!("OSC listening on :{}", LISTEN_PORT);
|
||||
|
||||
thread::Builder::new()
|
||||
.name("osc-server".into())
|
||||
.spawn(move || {
|
||||
let mut buf = [0u8; 4096];
|
||||
loop {
|
||||
match socket.recv_from(&mut buf) {
|
||||
Ok((size, _)) => {
|
||||
if let Ok((_, pkt)) = rosc::decoder::decode_udp(&buf[..size]) {
|
||||
handle_packet(pkt, &cmd_tx);
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::error!("OSC recv: {e}"),
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_packet(pkt: OscPacket, tx: &Sender<OscAppCmd>) {
|
||||
match pkt {
|
||||
OscPacket::Message(msg) => handle_msg(msg, tx),
|
||||
OscPacket::Bundle(b) => b.content.into_iter().for_each(|p| handle_packet(p, tx)),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_msg(msg: OscMessage, tx: &Sender<OscAppCmd>) {
|
||||
let parts: Vec<&str> = msg.addr.trim_start_matches('/').split('/').collect();
|
||||
if parts.len() < 3 || parts[0] != "deck" { return; }
|
||||
let n: usize = parts[1].parse().unwrap_or(99);
|
||||
let deck = match DeckId::from_index(n) { Some(d) => d, None => return };
|
||||
|
||||
let cmd = match (parts[2], parts.get(3).copied()) {
|
||||
("play", None) => Some(OscAppCmd::Audio(AudioCmd::Play(deck))),
|
||||
("pause", None) => Some(OscAppCmd::Audio(AudioCmd::Pause(deck))),
|
||||
("stop", None) => Some(OscAppCmd::Audio(AudioCmd::Stop(deck))),
|
||||
("seek", None) => f32_arg(&msg.args, 0).map(|v| OscAppCmd::Audio(AudioCmd::Seek { deck, position: v })),
|
||||
("fader", None) => f32_arg(&msg.args, 0).map(|v| OscAppCmd::SetFader { deck, value: v }),
|
||||
("pitch", None) => f32_arg(&msg.args, 0).map(|v| OscAppCmd::SetPitch { deck, semitones: v }),
|
||||
("tempo", None) => f32_arg(&msg.args, 0).map(|v| OscAppCmd::SetTempo { deck, multiplier: v }),
|
||||
("cue", Some("set")) => Some(OscAppCmd::Audio(AudioCmd::SetCue(deck))),
|
||||
("cue", Some("goto")) => Some(OscAppCmd::Audio(AudioCmd::GotoCue(deck))),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(c) = cmd { let _ = tx.send(c); }
|
||||
}
|
||||
|
||||
fn f32_arg(args: &[OscType], i: usize) -> Option<f32> {
|
||||
match args.get(i)? {
|
||||
OscType::Float(f) => Some(*f),
|
||||
OscType::Double(d) => Some(*d as f32),
|
||||
OscType::Int(i) => Some(*i as f32),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_osc(addr: &str, args: Vec<OscType>) {
|
||||
let Ok(sock) = UdpSocket::bind("0.0.0.0:0") else { return };
|
||||
let msg = OscPacket::Message(OscMessage { addr: addr.to_string(), args });
|
||||
if let Ok(buf) = encoder::encode(&msg) {
|
||||
let _ = sock.send_to(&buf, format!("127.0.0.1:{}", SEND_PORT));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user