mod osc_router;
mod private;
use private::{Message, Packet, ReplyMatcher};
use rosc::{
decoder::decode, encoder::encode, OscBundle, OscError, OscMessage, OscPacket, OscTime, OscType,
};
use std::{
error, fmt, io,
net::{ToSocketAddrs, UdpSocket},
sync::{
atomic::{AtomicI32, Ordering},
mpsc, Arc, Mutex,
},
thread,
time::{Duration, SystemTime},
};
#[derive(Clone)]
pub struct Server(Arc<ServerInner>);
impl fmt::Debug for Server {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let subscribers_len = &self.0.subscribers.lock().unwrap().len();
f.debug_struct("Server")
.field("socket", &self.0.socket)
.field("subscribers", &subscribers_len)
.finish()
}
}
struct ServerInner {
socket: UdpSocket,
subscribers: Mutex<Vec<mpsc::Sender<Reply>>>,
sync_id_counter: AtomicI32,
}
impl Server {
pub fn connect<A: ToSocketAddrs>(server_address: A) -> Result<Server> {
let socket = std::net::UdpSocket::bind("0.0.0.0:0")
.map_err(|err| Error(ErrorInner::UdpBind(err)))?;
socket
.connect(server_address)
.map_err(|err| Error(ErrorInner::UdpConnect(err)))?;
let server = Server(Arc::new(ServerInner {
socket,
subscribers: Mutex::new(Vec::new()),
sync_id_counter: AtomicI32::new(0),
}));
let reader = server.clone();
thread::spawn(move || reader.recv_loop());
Ok(server)
}
pub fn subscribe(&self) -> mpsc::Receiver<Reply> {
let (sender, receiver) = mpsc::channel();
self.0.subscribers.lock().unwrap().push(sender);
receiver
}
pub fn send_sync(&self, command: impl AsyncCommand) -> Result<Reply> {
let reply_matcher = command.reply_matcher();
let packet = command.into_packet();
let replies = self.subscribe();
self.send(packet)?;
loop {
let reply = replies.recv().unwrap();
if reply_matcher.matches(&reply) {
return Ok(reply);
}
}
}
pub fn send(&self, command: impl Command) -> Result<()> {
log::debug!("send: {:?}", command);
let packet = command.into_packet();
let bytes = encode(&packet.0).map_err(|err| Error(ErrorInner::OscEncode(err)))?;
self.0
.socket
.send(&bytes)
.map_err(|err| Error(ErrorInner::Send(err)))?;
Ok(())
}
fn recv_loop(self) {
const MTU: usize = 65536;
let mut buffer = [0_u8; MTU];
loop {
match self.recv(&mut buffer) {
Ok(packet) => match packet {
OscPacket::Message(message) => {
if let Some(reply) = Reply::parse(&message) {
let mut subscribers = self.0.subscribers.lock().unwrap();
let mut offset = 0;
for i in 0..subscribers.len() {
if subscribers[i - offset].send(reply.clone()).is_err() {
subscribers.swap_remove(i);
offset += 1;
}
}
}
}
OscPacket::Bundle(bundle) => {
log::error!("unexpected bundle in server response: {:?}", bundle)
}
},
Err(err) => log::error!("error receiving next packet from server: {}", err),
}
}
}
fn recv(&self, buffer: &mut [u8]) -> Result<OscPacket> {
let len = self
.0
.socket
.recv(buffer)
.map_err(|err| Error(ErrorInner::Recv(err)))?;
let packet = decode(&buffer[..len]).map_err(|err| Error(ErrorInner::OscDecode(err)))?;
log::debug!("recv: {:?}", packet);
Ok(packet)
}
pub fn reset(&self) -> Result<()> {
self.send(GroupFreeAll::new(vec![0]))?;
self.send(ClearSched::new())?;
self.sync()?;
Ok(())
}
pub fn sync(&self) -> Result<()> {
self.send_sync(Sync::new(self.next_sync_id()))?;
Ok(())
}
fn next_sync_id(&self) -> i32 {
self.0.sync_id_counter.fetch_add(1, Ordering::Relaxed)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct Error(ErrorInner);
#[derive(Debug)]
enum ErrorInner {
UdpBind(io::Error),
UdpConnect(io::Error),
Send(io::Error),
Recv(io::Error),
OscDecode(OscError),
OscEncode(OscError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
ErrorInner::UdpBind(e) => write!(f, "binding to UDP socket: {}", e),
ErrorInner::UdpConnect(e) => write!(f, "connecting UDP socket to server: {}", e),
ErrorInner::Send(e) => write!(f, "sending message to server: {}", e),
ErrorInner::Recv(e) => write!(f, "receiving message from server: {}", e),
ErrorInner::OscDecode(e) => write!(f, "decoding OSC packet: {:?}", e),
ErrorInner::OscEncode(_) => write!(f, "encoding OSC packet"),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self.0 {
ErrorInner::UdpBind(e) => Some(e),
ErrorInner::UdpConnect(e) => Some(e),
ErrorInner::Send(e) => Some(e),
ErrorInner::Recv(e) => Some(e),
ErrorInner::OscDecode(_) => None,
ErrorInner::OscEncode(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AddAction {
HeadOfGroup = 0,
TailOfGroup = 1,
BeforeNode = 2,
AfterNode = 3,
ReplaceNode = 4,
}
impl Default for AddAction {
fn default() -> AddAction {
AddAction::HeadOfGroup
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Control {
id: ControlID,
value: ControlValue,
}
impl Control {
pub fn new(id: impl Into<ControlID>, value: impl Into<ControlValue>) -> Control {
Control {
id: id.into(),
value: value.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ControlRange {
start_id: ControlID,
values: Vec<ControlValue>,
}
impl ControlRange {
pub fn new<V>(
start_id: impl Into<ControlID>,
values: impl IntoIterator<Item = V>,
) -> ControlRange
where
V: Into<ControlValue>,
{
ControlRange {
start_id: start_id.into(),
values: values.into_iter().map(V::into).collect(),
}
}
fn into_osc_args(self) -> Vec<OscType> {
if self.values.len() == 1 {
vec![
self.start_id.into_osc_type(),
self.values[0].into_osc_type(),
]
} else {
vec![
self.start_id.into_osc_type(),
OscType::Array(
self.values
.into_iter()
.map(|value| value.into_osc_type())
.collect(),
),
]
}
}
}
impl From<Control> for ControlRange {
fn from(control: Control) -> ControlRange {
ControlRange {
start_id: control.id,
values: vec![control.value],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ControlID {
Index(i32),
Name(String),
}
impl From<i32> for ControlID {
fn from(index: i32) -> Self {
Self::Index(index)
}
}
impl From<String> for ControlID {
fn from(name: String) -> Self {
ControlID::Name(name)
}
}
impl From<&str> for ControlID {
fn from(name: &str) -> Self {
ControlID::Name(name.to_owned())
}
}
impl ControlID {
fn into_osc_type(self) -> OscType {
match self {
ControlID::Index(index) => index.into(),
ControlID::Name(name) => name.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum ControlValue {
Int(i32),
Float(f32),
ControlBus(i32),
AudioBus(i32),
}
impl From<i32> for ControlValue {
fn from(n: i32) -> ControlValue {
ControlValue::Int(n)
}
}
impl From<f32> for ControlValue {
fn from(n: f32) -> ControlValue {
ControlValue::Float(n)
}
}
impl ControlValue {
fn into_osc_type(self) -> OscType {
match self {
ControlValue::Int(x) => x.into(),
ControlValue::Float(x) => x.into(),
ControlValue::ControlBus(x) => format!("c{}", x).into(),
ControlValue::AudioBus(x) => format!("a{}", x).into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Bundle(OscBundle);
impl Bundle {
pub fn new<I, C>(time: SystemTime, commands: I) -> Bundle
where
I: IntoIterator<Item = C>,
C: Command,
{
Bundle(OscBundle {
timetag: Self::osc_time(time),
content: commands
.into_iter()
.map(|command| command.into_packet().0)
.collect(),
})
}
fn osc_time(time: SystemTime) -> OscTime {
const UNIX_OFFSET: u64 = 2_208_988_800; const TWO_POW_32: f64 = 4294967296.0;
let unix_time = time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); let unix_offset = Duration::new(UNIX_OFFSET, 0);
let epoch_time = unix_offset + unix_time;
let ts_secs = epoch_time.as_secs() as u32;
let ts_nanos = epoch_time.subsec_nanos() as f64;
let ts_frac = ((ts_nanos * TWO_POW_32) / 1.0e9).round() as u32;
(ts_secs, ts_frac)
}
}
impl Command for Bundle {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Packet(OscPacket::Bundle(self.0))
}
}
pub trait Command: fmt::Debug {
#[doc(hidden)]
fn into_packet(self) -> Packet;
}
pub trait AsyncCommand: Command {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher;
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Quit {
_hidden: (),
}
impl Quit {
#[allow(clippy::new_without_default)]
pub fn new() -> Quit {
Quit { _hidden: () }
}
}
impl Command for Quit {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/quit").into_packet()
}
}
impl AsyncCommand for Quit {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
ReplyMatcher::new(|reply| matches!(reply, Reply::Done))
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Notify {
setting: NotifySetting,
client_id: Option<i32>,
}
impl Notify {
pub fn new(setting: NotifySetting) -> Notify {
Notify {
setting,
client_id: None,
}
}
pub fn client_id(mut self, client_id: i32) -> Notify {
self.client_id = Some(client_id);
self
}
}
impl Command for Notify {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/notify")
.arg(self.setting as i32)
.optional(self.client_id)
.into_packet()
}
}
impl AsyncCommand for Notify {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
ReplyMatcher::new(|reply| matches!(reply, Reply::NotifyDone { .. }))
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferAllocate {
buffer_number: i32,
number_of_frames: i32,
number_of_channels: i32,
on_completion: Option<Vec<u8>>,
}
impl BufferAllocate {
pub fn new(buffer_number: i32, number_of_frames: i32) -> BufferAllocate {
BufferAllocate {
buffer_number,
number_of_frames,
number_of_channels: 1,
on_completion: None,
}
}
pub fn number_of_channels(mut self, number_of_channels: i32) -> BufferAllocate {
self.number_of_channels = number_of_channels;
self
}
pub fn on_completion(mut self, message: Vec<u8>) -> BufferAllocate {
self.on_completion = Some(message);
self
}
}
impl Command for BufferAllocate {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_alloc")
.arg(self.buffer_number)
.arg(self.number_of_frames)
.arg(self.number_of_channels)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for BufferAllocate {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_number = self.buffer_number;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferAllocateDone { buffer_number }
if *buffer_number == command_buffer_number
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferAllocateRead {
buffer_number: i32,
file_path: String,
starting_frame: i32,
number_of_frames: i32,
on_completion: Option<Vec<u8>>,
}
impl BufferAllocateRead {
pub fn new(buffer_number: i32, file_path: impl Into<String>) -> BufferAllocateRead {
BufferAllocateRead {
buffer_number,
file_path: file_path.into(),
starting_frame: 0,
number_of_frames: 0,
on_completion: None,
}
}
pub fn starting_frame(mut self, starting_frame: i32) -> BufferAllocateRead {
self.starting_frame = starting_frame;
self
}
pub fn number_of_frames(mut self, number_of_frames: i32) -> BufferAllocateRead {
self.number_of_frames = number_of_frames;
self
}
pub fn on_completion(mut self, message: Vec<u8>) -> BufferAllocateRead {
self.on_completion = Some(message);
self
}
}
impl Command for BufferAllocateRead {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_allocRead")
.arg(self.buffer_number)
.arg(self.file_path)
.arg(self.starting_frame)
.arg(self.number_of_frames)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for BufferAllocateRead {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_number = self.buffer_number;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferAllocateReadDone { buffer_number }
if *buffer_number == command_buffer_number
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferRead {
buffer_number: i32,
file_path: String,
file_starting_frame: i32,
number_of_frames: i32,
buffer_starting_frame: i32,
leave_file_open: i32,
on_completion: Option<Vec<u8>>,
}
impl BufferRead {
pub fn new(buffer_number: i32, file_path: impl Into<String>) -> BufferRead {
BufferRead {
buffer_number,
file_path: file_path.into(),
file_starting_frame: 0,
number_of_frames: -1,
buffer_starting_frame: 0,
leave_file_open: 0,
on_completion: None,
}
}
pub fn file_starting_frame(mut self, frame_number: i32) -> BufferRead {
self.file_starting_frame = frame_number;
self
}
pub fn number_of_frames(mut self, number_of_frames: i32) -> BufferRead {
self.number_of_frames = number_of_frames;
self
}
pub fn buffer_starting_frame(mut self, frame_number: i32) -> BufferRead {
self.buffer_starting_frame = frame_number;
self
}
pub fn leave_file_open(mut self) -> BufferRead {
self.leave_file_open = 1;
self
}
pub fn on_completion(mut self, message: Vec<u8>) -> BufferRead {
self.on_completion = Some(message);
self
}
}
impl Command for BufferRead {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_read")
.arg(self.buffer_number)
.arg(self.file_path)
.arg(self.file_starting_frame)
.arg(self.number_of_frames)
.arg(self.buffer_starting_frame)
.arg(self.leave_file_open)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for BufferRead {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_number = self.buffer_number;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferReadDone { buffer_number }
if *buffer_number == command_buffer_number
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferClose {
buffer_number: i32,
on_completion: Option<Vec<u8>>,
}
impl BufferClose {
pub fn new(buffer_number: i32) -> BufferClose {
BufferClose {
buffer_number,
on_completion: None,
}
}
pub fn on_completion(mut self, message: Vec<u8>) -> BufferClose {
self.on_completion = Some(message);
self
}
}
impl Command for BufferClose {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_close")
.arg(self.buffer_number)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for BufferClose {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_number = self.buffer_number;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferCloseDone { buffer_number }
if *buffer_number == command_buffer_number
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferQuery {
buffer_numbers: Vec<i32>,
}
impl BufferQuery {
pub fn new(buffer_numbers: impl IntoIterator<Item = i32>) -> BufferQuery {
BufferQuery {
buffer_numbers: buffer_numbers.into_iter().collect(),
}
}
}
impl Command for BufferQuery {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_query")
.args(self.buffer_numbers)
.into_packet()
}
}
impl AsyncCommand for BufferQuery {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_numbers = self.buffer_numbers.clone();
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferInfo { buffers }
if buffers.iter().map(|buffer| &buffer.buffer_number).eq(&command_buffer_numbers)
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum Reply {
Done,
NotifyDone {
client_id: i32,
max_logins: Option<i32>,
},
Synced {
id: i32,
},
SynthDefRecvDone,
BufferAllocateDone {
buffer_number: i32,
},
BufferAllocateReadDone {
buffer_number: i32,
},
BufferReadDone {
buffer_number: i32,
},
BufferWriteDone {
buffer_number: i32,
},
BufferFreeDone {
buffer_number: i32,
},
BufferCloseDone {
buffer_number: i32,
},
BufferInfo {
buffers: Vec<BufferInfo>,
},
#[non_exhaustive]
Fail {
command: String,
error: String,
},
}
impl Reply {
fn parse(message: &OscMessage) -> Option<Reply> {
let mut router = osc_router::Router::default();
router.addr("/done").handle(|_| Some(Reply::Done));
router
.addr("/done")
.expect_str("/notify")
.capture("client_id")
.capture_optional("max_logins")
.handle(|args| {
Some(Reply::NotifyDone {
client_id: args.int("client_id")?,
max_logins: args.int("max_logins"),
})
});
router.addr("/synced").capture("id").handle(|args| {
Some(Reply::Synced {
id: args.int("id")?,
})
});
router
.addr("/done")
.expect_str("/d_recv")
.handle(|_| Some(Reply::SynthDefRecvDone));
router
.addr("/done")
.expect_str("/b_alloc")
.capture("buffer_number")
.handle(|args| {
Some(Reply::BufferAllocateDone {
buffer_number: args.int("buffer_number")?,
})
});
router
.addr("/done")
.expect_str("/b_allocRead")
.capture("buffer_number")
.handle(|args| {
Some(Reply::BufferAllocateReadDone {
buffer_number: args.int("buffer_number")?,
})
});
router
.addr("/done")
.expect_str("/b_read")
.capture("buffer_number")
.handle(|args| {
Some(Reply::BufferReadDone {
buffer_number: args.int("buffer_number")?,
})
});
router
.addr("/done")
.expect_str("/b_write")
.capture("buffer_number")
.handle(|args| {
Some(Reply::BufferWriteDone {
buffer_number: args.int("buffer_number")?,
})
});
router
.addr("/done")
.expect_str("/b_free")
.capture("buffer_number")
.handle(|args| {
Some(Reply::BufferFreeDone {
buffer_number: args.int("buffer_number")?,
})
});
router
.addr("/done")
.expect_str("/b_close")
.capture("buffer_number")
.handle(|args| {
Some(Reply::BufferCloseDone {
buffer_number: args.int("buffer_number")?,
})
});
router.addr("/b_info").capture_rest().handle(|args| {
let buffers = args.rest()?
.chunks(4)
.map(|chunk| {
match *chunk {
[
OscType::Int(buffer_number),
OscType::Int(number_of_frames),
OscType::Int(number_of_channels),
OscType::Float(sample_rate),
] => Some(BufferInfo {
buffer_number,
number_of_frames,
number_of_channels,
sample_rate,
}),
_ => None,
}
}).collect::<Option<Vec<_>>>()?;
Some(Reply::BufferInfo { buffers })
});
router
.addr("/fail")
.capture("command")
.capture("error")
.handle(|args| {
Some(Reply::Fail {
command: args.string("command")?,
error: args.string("error")?,
})
});
router.route(message.clone())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NotifySetting {
Start = 1,
Stop = 0,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct Sync {
id: i32,
}
impl Sync {
pub fn new(id: i32) -> Sync {
Sync { id }
}
}
impl Command for Sync {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/sync").arg(self.id).into_packet()
}
}
impl AsyncCommand for Sync {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_id = self.id;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::Synced { id }
if *id == command_id
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct ClearSched {
_hidden: (),
}
impl ClearSched {
#[allow(clippy::new_without_default)]
pub fn new() -> ClearSched {
ClearSched { _hidden: () }
}
}
impl Command for ClearSched {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/clearSched").into_packet()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct SynthDefRecv {
data: Vec<u8>,
on_completion: Option<Vec<u8>>,
}
impl SynthDefRecv {
pub fn new(data: &[u8]) -> SynthDefRecv {
SynthDefRecv {
data: data.to_vec(),
on_completion: None,
}
}
pub fn on_completion(mut self, message: Vec<u8>) -> SynthDefRecv {
self.on_completion = Some(message);
self
}
}
impl Command for SynthDefRecv {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/d_recv")
.arg(self.data)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for SynthDefRecv {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
ReplyMatcher::new(move |reply| matches!(reply, Reply::SynthDefRecvDone))
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct SynthDefFree {
names: Vec<String>,
}
impl SynthDefFree {
pub fn new<I, S>(names: I) -> SynthDefFree
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
SynthDefFree {
names: names.into_iter().map(|s| s.into()).collect(),
}
}
}
impl Command for SynthDefFree {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/d_free").args(self.names).into_packet()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct NodeFree {
node_ids: Vec<i32>,
}
impl NodeFree {
pub fn new(node_ids: impl IntoIterator<Item = i32>) -> NodeFree {
NodeFree {
node_ids: node_ids.into_iter().collect(),
}
}
}
impl Command for NodeFree {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/n_free").args(self.node_ids).into_packet()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct NodeSet {
node_id: i32,
controls: Vec<ControlRange>,
}
impl NodeSet {
pub fn new<C>(node_id: i32, controls: impl IntoIterator<Item = C>) -> NodeSet
where
C: Into<ControlRange>,
{
NodeSet {
node_id,
controls: controls.into_iter().map(C::into).collect(),
}
}
}
impl Command for NodeSet {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/n_set")
.arg(self.node_id)
.args(
self.controls
.into_iter()
.flat_map(ControlRange::into_osc_args),
)
.into_packet()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct SynthNew {
synthdef_name: String,
synth_id: i32,
add_action: AddAction,
add_target_id: i32,
controls: Vec<ControlRange>,
}
impl SynthNew {
pub fn new(synthdef_name: impl Into<String>, add_target_id: i32) -> SynthNew {
SynthNew {
synthdef_name: synthdef_name.into(),
synth_id: -1,
add_target_id,
controls: Vec::new(),
add_action: AddAction::default(),
}
}
pub fn controls<C>(mut self, controls: impl IntoIterator<Item = C>) -> SynthNew
where
C: Into<ControlRange>,
{
self.controls = controls.into_iter().map(C::into).collect();
self
}
pub fn add_action(mut self, add_action: AddAction) -> SynthNew {
self.add_action = add_action;
self
}
pub fn synth_id(mut self, synth_id: i32) -> SynthNew {
self.synth_id = synth_id;
self
}
}
impl Command for SynthNew {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/s_new")
.arg(self.synthdef_name)
.arg(self.synth_id)
.arg(self.add_action as i32)
.arg(self.add_target_id)
.args(
self.controls
.into_iter()
.flat_map(ControlRange::into_osc_args),
)
.into_packet()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct GroupFreeAll {
group_ids: Vec<i32>,
}
impl GroupFreeAll {
pub fn new(group_ids: impl IntoIterator<Item = i32>) -> GroupFreeAll {
GroupFreeAll {
group_ids: group_ids.into_iter().collect(),
}
}
}
impl Command for GroupFreeAll {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/g_freeAll")
.args(self.group_ids)
.into_packet()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferWrite {
buffer_number: i32,
file_path: String,
header_format: HeaderFormat,
sample_format: SampleFormat,
number_of_frames: i32,
starting_frame: i32,
leave_file_open: i32,
on_completion: Option<Vec<u8>>,
}
impl BufferWrite {
pub fn new(
buffer_number: i32,
file_path: impl Into<String>,
header_format: HeaderFormat,
sample_format: SampleFormat,
) -> BufferWrite {
BufferWrite {
buffer_number,
file_path: file_path.into(),
header_format,
sample_format,
number_of_frames: -1,
starting_frame: 0,
leave_file_open: 0,
on_completion: None,
}
}
pub fn number_of_frames(mut self, number_of_frames: i32) -> BufferWrite {
self.number_of_frames = number_of_frames;
self
}
pub fn starting_frame(mut self, starting_frame: i32) -> BufferWrite {
self.starting_frame = starting_frame;
self
}
pub fn leave_file_open(mut self) -> BufferWrite {
self.leave_file_open = 1;
self
}
pub fn on_completion(mut self, message: Vec<u8>) -> BufferWrite {
self.on_completion = Some(message);
self
}
}
impl Command for BufferWrite {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_write")
.arg(self.buffer_number)
.arg(self.file_path)
.arg(self.header_format.to_string())
.arg(self.sample_format.to_string())
.arg(self.number_of_frames)
.arg(self.starting_frame)
.arg(self.leave_file_open)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for BufferWrite {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_number = self.buffer_number;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferWriteDone { buffer_number }
if *buffer_number == command_buffer_number
)
})
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct BufferFree {
buffer_number: i32,
on_completion: Option<Vec<u8>>,
}
impl BufferFree {
pub fn new(buffer_number: i32) -> BufferFree {
BufferFree {
buffer_number,
on_completion: None,
}
}
pub fn on_completion(mut self, message: Vec<u8>) -> BufferFree {
self.on_completion = Some(message);
self
}
}
impl Command for BufferFree {
#[doc(hidden)]
fn into_packet(self) -> Packet {
Message::addr("/b_free")
.arg(self.buffer_number)
.optional(self.on_completion)
.into_packet()
}
}
impl AsyncCommand for BufferFree {
#[doc(hidden)]
fn reply_matcher(&self) -> ReplyMatcher {
let command_buffer_number = self.buffer_number;
ReplyMatcher::new(move |reply| {
matches!(reply,
Reply::BufferFreeDone { buffer_number }
if *buffer_number == command_buffer_number
)
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct BufferInfo {
pub buffer_number: i32,
pub number_of_frames: i32,
pub number_of_channels: i32,
pub sample_rate: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum HeaderFormat {
Aiff,
Next,
Wav,
Icram,
Raw,
}
impl fmt::Display for HeaderFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
HeaderFormat::Aiff => write!(f, "aiff"),
HeaderFormat::Next => write!(f, "next"),
HeaderFormat::Wav => write!(f, "wav"),
HeaderFormat::Icram => write!(f, "icram"),
HeaderFormat::Raw => write!(f, "raw"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SampleFormat {
Int8,
Int16,
Int24,
Int32,
Float,
Double,
Mulaw,
Alaw,
}
impl fmt::Display for SampleFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SampleFormat::Int8 => write!(f, "int8"),
SampleFormat::Int16 => write!(f, "int16"),
SampleFormat::Int24 => write!(f, "int24"),
SampleFormat::Int32 => write!(f, "int32"),
SampleFormat::Float => write!(f, "float"),
SampleFormat::Double => write!(f, "double"),
SampleFormat::Mulaw => write!(f, "mulaw"),
SampleFormat::Alaw => write!(f, "alaw"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_replies() {
assert_eq!(reply("/done", no_args()), Some(Reply::Done));
assert_eq!(
reply("/done", vec![arg("/notify"), arg(1)]),
Some(Reply::NotifyDone {
client_id: 1,
max_logins: None,
})
);
assert_eq!(
reply("/done", vec![arg("/notify"), arg(2), arg(3)]),
Some(Reply::NotifyDone {
client_id: 2,
max_logins: Some(3),
})
);
assert_eq!(reply("/synced", vec![1]), Some(Reply::Synced { id: 1 }));
assert_eq!(
reply("/done", vec![arg("/d_recv")]),
Some(Reply::SynthDefRecvDone)
);
assert_eq!(
reply("/done", vec![arg("/b_alloc"), arg(1)]),
Some(Reply::BufferAllocateDone { buffer_number: 1 })
);
assert_eq!(
reply("/done", vec![arg("/b_allocRead"), arg(1)]),
Some(Reply::BufferAllocateReadDone { buffer_number: 1 })
);
assert_eq!(
reply("/done", vec![arg("/b_read"), arg(1)]),
Some(Reply::BufferReadDone { buffer_number: 1 })
);
assert_eq!(
reply("/done", vec![arg("/b_write"), arg(1)]),
Some(Reply::BufferWriteDone { buffer_number: 1 })
);
assert_eq!(
reply("/done", vec![arg("/b_free"), arg(1)]),
Some(Reply::BufferFreeDone { buffer_number: 1 })
);
assert_eq!(
reply("/done", vec![arg("/b_close"), arg(1)]),
Some(Reply::BufferCloseDone { buffer_number: 1 })
);
assert_eq!(
reply("/b_info", vec![arg(1), arg(2), arg(3), OscType::Float(4.0)]),
Some(Reply::BufferInfo {
buffers: vec![BufferInfo {
buffer_number: 1,
number_of_frames: 2,
number_of_channels: 3,
sample_rate: 4.0,
}]
})
);
assert_eq!(
reply("/fail", vec![arg("/cmdName"), arg("oops")]),
Some(Reply::Fail {
command: "/cmdName".to_owned(),
error: "oops".to_owned(),
})
);
}
fn no_args() -> Vec<OscType> {
Vec::new()
}
fn reply<I, T>(addr: &'static str, args: I) -> Option<Reply>
where
I: IntoIterator<Item = T>,
T: Into<OscType>,
{
Reply::parse(&OscMessage {
addr: addr.to_owned(),
args: args.into_iter().map(T::into).collect(),
})
}
fn arg(x: impl Into<OscType>) -> OscType {
x.into()
}
}