diff --git a/src/autocrap.rs b/src/autocrap.rs new file mode 100644 index 0000000..f999b84 --- /dev/null +++ b/src/autocrap.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod interpreter; diff --git a/src/autocrap/config.rs b/src/autocrap/config.rs new file mode 100644 index 0000000..42e59e3 --- /dev/null +++ b/src/autocrap/config.rs @@ -0,0 +1,250 @@ +use std::{iter, net::{SocketAddrV4}}; + +use rosc::{OscMessage, OscType}; +use serde::{Serialize, Deserialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Numbering { + Single { + ctrl_in_sequence: Option>, + ctrl_in: Option, + ctrl_out: Option, + midi: Option, + }, + Range { + ctrl_in: Option<(u8, u8)>, + ctrl_out: Option<(u8, u8)>, + midi: Option<(u8, u8)>, + } +} + +impl Numbering { + pub fn num_alternatives(&self) -> Option { + match self { + Numbering::Single { .. } => Some(1), + Numbering::Range {ctrl_in, ctrl_out, midi} => { + let ranges = iter::once(ctrl_in) + .chain(iter::once(ctrl_out)) + .chain(iter::once(midi)) + .filter_map(|r| *r); + + let mut num_opt: Option = None; + for (lo, hi) in ranges { + let range_num = hi - lo + 1; + let Some(num) = num_opt else { + num_opt = Some(range_num); + continue; + }; + + if range_num != num { + return None; + } + } + + num_opt + } + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum CtrlNum { + Single(u8), + Range(u8, u8), + Sequence(Vec) +} + +impl CtrlNum { + pub fn match_num(&self, num: u8) -> Option { + match *self { + CtrlNum::Single(n) if num == n => + Some(0), + CtrlNum::Range(lo, hi) if lo <= num && num <= hi => + Some(num - lo), + // TODO: Sequence + _ => + None + } + } + + pub fn range_size(&self) -> u8 { + match *self { + CtrlNum::Single(_) => 1, + CtrlNum::Range(lo, hi) => hi - lo + 1, + _ => unimplemented!() + } + } + + pub fn index_to_num(&self, i: u8) -> Option { + match *self { + CtrlNum::Single(num) if i == 0 => + Some(num), + CtrlNum::Range(lo, hi) if i <= hi-lo => + Some(lo + i), + CtrlNum::Sequence(_) => + unimplemented!(), + _ => None + } + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum CtrlKind { + OnOff, + EightBit, + Relative, +} + +impl CtrlKind { + pub fn ctrl_to_osc(&self, val: u8) -> Vec { + match self { + CtrlKind::OnOff => + vec![OscType::Float(if val == 0x7f { 1.0 } else { 0.0 })], + CtrlKind::Relative => + vec![OscType::Float(if val < 0x40 { val as f32 } else { val as f32 - 128.0 })], + _ => unimplemented!() + } + } + + pub fn osc_to_ctrl(&self, args: &[OscType]) -> Option { + if args.len() < 1 { + return None; + } + + let OscType::Float(val) = args[0] else { + return None; + }; + + match self { + CtrlKind::OnOff => + Some(float_to_7bit(val)), + CtrlKind::Relative => + Some(float_to_7bit(val)), + _ => unimplemented!() + } + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum MidiKind { + Cc, + CoarseFine, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Mapping { + pub name: String, + pub numbering: Numbering, + pub ctrl_kind: CtrlKind, + pub midi_kind: MidiKind, +} + +impl Mapping { + pub fn expand_iter(&self) -> impl Iterator { + let mut mappings = vec![]; + match self.numbering { + Numbering::Single { .. } => mappings.push(self.clone()), + Numbering::Range {ctrl_in, ctrl_out, midi} => { + println!("{:?}", self.numbering); + let num = self.numbering.num_alternatives().unwrap(); + for i in 0..num { + mappings.push(Mapping { + name: self.name.replace("{i}", &i.to_string()), + numbering: Numbering::Single { + ctrl_in_sequence: None, + ctrl_in: ctrl_in.map(|(lo, hi)| lo + i), + ctrl_out: ctrl_out.map(|(lo, hi)| lo + i), + midi: midi.map(|(lo, hi)| lo + i), + }, + ctrl_kind: self.ctrl_kind, + midi_kind: self.midi_kind, + }); + } + } + }; + mappings.into_iter() + } + + pub fn osc_addr(&self, i: u8) -> String { + format!("/{}", self.name.replace("{i}", &i.to_string())) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Config { + pub vendor_id: u16, + pub product_id: u16, + pub in_endpoint: u8, + pub out_endpoint: u8, + pub host_addr: SocketAddrV4, + pub osc_out_addr: SocketAddrV4, + pub osc_in_addr: SocketAddrV4, + pub mappings: Vec +} + +impl Config { + // pub fn match_ctrl(&self, num: u8, val: u8) -> Option { + // for mapping in self.mappings.iter() { + // let Some(ctrl_in_num) = mapping.ctrl_in_num else { + // continue; + // }; + + // let Some(i) = ctrl_in_num.match_num(num) else { + // continue; + // }; + + // return Some(CtrlMatchData { + // osc_addr: mapping.osc_addr(i), + // osc_args: mapping.ctrl_kind.ctrl_to_osc(val) + // }) + // } + + // None + // } + + // pub fn match_osc(&self, msg: &OscMessage) -> Option { + // for mapping in self.mappings.iter() { + // let Some(ctrl_out_num) = mapping.ctrl_out_num else { + // continue; + // }; + + // for i in 0..ctrl_out_num.range_size() { + // let addr = mapping.osc_addr(i); + + // if addr != msg.addr { + // continue; + // } + + // let Some(num) = ctrl_out_num.index_to_num(i) else { + // continue; + // }; + + // let Some(val) = mapping.ctrl_kind.osc_to_ctrl(&msg.args) else { + // continue; + // }; + + // return Some(OscMatchData { + // ctrl_data: vec![num, val] + // }); + // } + // } + + // None + // } +} + +#[derive(Clone, Debug)] +pub struct CtrlMatchData { + pub osc_addr: String, + pub osc_args: Vec, +} + +#[derive(Clone, Debug)] +pub struct OscMatchData { + pub ctrl_data: Vec, +} + + +fn float_to_7bit(val: f32) -> u8 { + (val.max(0.0).min(1.0) * 127.0).round() as u8 +} diff --git a/src/autocrap/interpreter.rs b/src/autocrap/interpreter.rs new file mode 100644 index 0000000..ee92ff4 --- /dev/null +++ b/src/autocrap/interpreter.rs @@ -0,0 +1,133 @@ +use std::{ + sync::{Arc, RwLock} +}; + +use rosc::{OscMessage, OscType}; + +use super::config::{Config, CtrlKind, Numbering}; + +pub struct Interpreter { + ctrls: Vec>, +} + +impl Interpreter { + pub fn new(config: &Config) -> Interpreter { + let mut ctrls: Vec> = vec![]; + for mapping in config.mappings.iter() { + for m in mapping.expand_iter() { + let Numbering::Single { ctrl_in, ctrl_out, midi, ref ctrl_in_sequence } = m.numbering + else { + unreachable!(); + }; + + match m.ctrl_kind { + CtrlKind::OnOff => { + ctrls.push(Box::new(OnOffLogic { + ctrl_in_num: ctrl_in, + ctrl_out_num: ctrl_out, + osc_addr: format!("/{}", m.name), + state: Arc::new(RwLock::new(false)) + })); + }, + CtrlKind::EightBit => { + if let Some(ctrl_in_sequence) = ctrl_in_sequence { + ctrls.push(Box::new(EightBitLogic { + ctrl_in_first: ctrl_in_sequence[0], + ctrl_in_num: ctrl_in_sequence[1], + osc_addr: format!("/{}", m.name), + state: Arc::new(RwLock::new([0x00,0x00])) + })); + } + }, + _ => { + println!("{:?}", m); + } + } + } + } + + Interpreter { + ctrls + } + } + + pub fn handle_ctrl(&self, num: u8, val: u8) -> Option { + for ctrl in &self.ctrls { + let Some(response) = ctrl.handle_ctrl(num, val) else { + continue; + }; + + return Some(response); + } + + None + } +} + +pub trait CtrlLogic { + fn handle_ctrl(&self, num: u8, val: u8) -> Option; +} + +pub struct OnOffLogic { + ctrl_in_num: Option, + ctrl_out_num: Option, + osc_addr: String, + state: Arc> +} + +impl CtrlLogic for OnOffLogic { + fn handle_ctrl(&self, num: u8, val: u8) -> Option { + let Some(ctrl_in_num) = self.ctrl_in_num else { + return None; + }; + + if num != ctrl_in_num { + return None; + } + + let mut state = self.state.write().unwrap(); + *state = if val != 0 { true } else { false }; + let ctrl_out = self.ctrl_out_num.map(|num| vec![num, if *state { 0x7f} else { 0x00 }]); + Some(CtrlResponse { + osc: Some((self.osc_addr.clone(), vec![OscType::Float(if *state { 1.0 } else { 0.0 })])), + ctrl: ctrl_out + }) + } +} + +pub struct EightBitLogic { + ctrl_in_first: u8, + ctrl_in_num: u8, + osc_addr: String, + state: Arc> +} + +impl CtrlLogic for EightBitLogic { + fn handle_ctrl(&self, num: u8, val: u8) -> Option { + if num == self.ctrl_in_first { + let mut state = self.state.write().unwrap(); + state[0] = val; + return Some(CtrlResponse { + osc: None, + ctrl: None + }); + } + + if num == self.ctrl_in_num { + let mut state = self.state.write().unwrap(); + state[1] = val; + let val8 = state[0] << 1 | (if state[1] != 0x00 { 1 } else { 0 }); + return Some(CtrlResponse { + osc: Some((self.osc_addr.clone(), vec![OscType::Float(val8 as f32 / 255.0)])), + ctrl: None + }) + } + + None + } +} + +pub struct CtrlResponse { + pub osc: Option<(String, Vec)>, + pub ctrl: Option> +} diff --git a/src/main.rs b/src/main.rs index 5062a46..c3ee4dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,13 @@ use rusb::{ use serde::{Serialize, Deserialize}; use serde_json; +mod autocrap; + +use autocrap::{ + config::{CtrlKind, CtrlNum, Config, Mapping, MidiKind}, + interpreter::{Interpreter} +}; + type Result = std::result::Result>; const DEFAULT_TIMEOUT: Duration = Duration::from_millis(1000); @@ -33,180 +40,6 @@ struct Endpoint { direction: Direction, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -enum CtrlNum { - Single(u8), - Range(u8, u8), - Pair(u8, u8) -} - -impl CtrlNum { - fn match_num(&self, num: u8) -> Option { - match *self { - CtrlNum::Single(n) if num == n => - Some(0), - CtrlNum::Range(lo, hi) if lo <= num && num <= hi => - Some(num - lo), - // TODO: Pair - _ => - None - } - } - - fn range_size(&self) -> u8 { - match *self { - CtrlNum::Single(_) => 1, - CtrlNum::Range(lo, hi) => hi - lo + 1, - _ => unimplemented!() - } - } - - fn index_to_num(&self, i: u8) -> Option { - match *self { - CtrlNum::Single(num) if i == 0 => - Some(num), - CtrlNum::Range(lo, hi) if i <= hi-lo => - Some(lo + i), - CtrlNum::Pair(_, _) => - unimplemented!(), - _ => None - } - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -enum CtrlKind { - Button, - EightBit, - Relative, -} - -impl CtrlKind { - fn ctrl_to_osc(&self, val: u8) -> Vec { - match self { - CtrlKind::Button => - vec![OscType::Float(if val == 0x7f { 1.0 } else { 0.0 })], - CtrlKind::Relative => - vec![OscType::Float(if val < 0x40 { val as f32 } else { val as f32 - 128.0 })], - _ => unimplemented!() - } - } - - fn osc_to_ctrl(&self, args: &[OscType]) -> Option { - if args.len() < 1 { - return None; - } - - let OscType::Float(val) = args[0] else { - return None; - }; - - match self { - CtrlKind::Button => - Some(float_to_7bit(val)), - CtrlKind::Relative => - Some(float_to_7bit(val)), - _ => unimplemented!() - } - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -enum MidiKind { - Cc, - CoarseFine, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -struct Mapping { - name: String, - ctrl_in_num: Option, - ctrl_out_num: Option, - ctrl_kind: CtrlKind, - midi_kind: MidiKind, - midi_num: CtrlNum -} - -impl Mapping { - fn osc_addr(&self, i: u8) -> String { - format!("/{}", self.name.replace("{i}", &i.to_string())) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -struct Config { - vendor_id: u16, - product_id: u16, - in_endpoint: u8, - out_endpoint: u8, - host_addr: SocketAddrV4, - osc_out_addr: SocketAddrV4, - osc_in_addr: SocketAddrV4, - mappings: Vec -} - -impl Config { - fn match_ctrl(&self, num: u8, val: u8) -> Option { - for mapping in self.mappings.iter() { - let Some(ctrl_in_num) = mapping.ctrl_in_num else { - continue; - }; - - let Some(i) = ctrl_in_num.match_num(num) else { - continue; - }; - - return Some(CtrlMatchData { - osc_addr: mapping.osc_addr(i), - osc_args: mapping.ctrl_kind.ctrl_to_osc(val) - }) - } - - None - } - - fn match_osc(&self, msg: &OscMessage) -> Option { - for mapping in self.mappings.iter() { - let Some(ctrl_out_num) = mapping.ctrl_out_num else { - continue; - }; - - for i in 0..ctrl_out_num.range_size() { - let addr = mapping.osc_addr(i); - - if addr != msg.addr { - continue; - } - - let Some(num) = ctrl_out_num.index_to_num(i) else { - continue; - }; - - let Some(val) = mapping.ctrl_kind.osc_to_ctrl(&msg.args) else { - continue; - }; - - return Some(OscMatchData { - ctrl_data: vec![num, val] - }); - } - } - - None - } -} - -#[derive(Clone, Debug)] -struct CtrlMatchData { - osc_addr: String, - osc_args: Vec, -} - -#[derive(Clone, Debug)] -struct OscMatchData { - ctrl_data: Vec, -} - fn main() { run().unwrap(); } @@ -262,6 +95,8 @@ fn run() -> Result<()> { configure_endpoint(&mut handle, &ctrl_in_endpoint).unwrap(); configure_endpoint(&mut handle, &ctrl_out_endpoint).unwrap(); + let interpreter = Interpreter::new(&config); + write_init(&mut handle, ctrl_out_endpoint.address).unwrap(); thread::scope(|s| { @@ -269,8 +104,7 @@ fn run() -> Result<()> { run_writer(&config, &handle, &ctrl_out_endpoint).unwrap(); }); - - run_reader(&config, &handle, &ctrl_in_endpoint).unwrap(); + run_reader(&config, &interpreter, &handle, &ctrl_in_endpoint).unwrap(); writer_thread.join().unwrap(); @@ -363,11 +197,7 @@ fn configure_endpoint( Ok(()) } -fn float_to_7bit(val: f32) -> u8 { - (val.max(0.0).min(1.0) * 127.0).round() as u8 -} - -fn run_reader(config: &Config, handle: &DeviceHandle, endpoint: &Endpoint) -> Result<()> { +fn run_reader(config: &Config, interpreter: &Interpreter, handle: &DeviceHandle, endpoint: &Endpoint) -> Result<()> { let sock = UdpSocket::bind(config.host_addr)?; let mut all_bytes = [0u8; 8]; @@ -393,12 +223,22 @@ fn run_reader(config: &Config, handle: &DeviceHandle, endpoint let num = bytes[0]; let val = bytes[1]; - let addr: String; - let args: Vec; + if let Some(response) = interpreter.handle_ctrl(num, val) { + if let Some((addr, args)) = response.osc { + let msg = OscPacket::Message(OscMessage { + addr: addr, // TODO: dont allocate every time + args: args, + }); + println!("osc: {:?}", msg); + let msg_buf = encoder::encode(&msg)?; + + sock.send_to(&msg_buf, config.osc_out_addr)?; + } - if let Some(data) = config.match_ctrl(num, val) { - addr = data.osc_addr; - args = data.osc_args; + if let Some(out_bytes) = response.ctrl { + // TODO: gather, then send to writer after while loop + println!("ctrl: {:02x?}", out_bytes); + } } else { println!("unhandled data: {:02x?}", bytes); continue; @@ -415,15 +255,6 @@ fn run_reader(config: &Config, handle: &DeviceHandle, endpoint // addr = "/xfader".to_string(); // args = vec![OscType::Float(val8 as f32 / 255.0)]; - - let msg = OscPacket::Message(OscMessage { - addr: addr.to_string(), // TODO: dont allocate every time - args: args, - }); - // println!("osc: {:?}", msg); - let msg_buf = encoder::encode(&msg)?; - - sock.send_to(&msg_buf, config.osc_out_addr)?; } } } @@ -441,13 +272,13 @@ fn run_writer(config: &Config, handle: &DeviceHandle, endpoint let (_, packet) = rosc::decoder::decode_udp(&buf[..size])?; match packet { OscPacket::Message(msg) => { - let Some(osc_match_data) = config.match_osc(&msg) else { - println!("unhandled osc message: with size {} from {}: {} {:?}", size, addr, msg.addr, msg.args); - continue; - }; + // let Some(osc_match_data) = config.match_osc(&msg) else { + // println!("unhandled osc message: with size {} from {}: {} {:?}", size, addr, msg.addr, msg.args); + // continue; + // }; - println!("write: {:02x?}", osc_match_data.ctrl_data); - handle.write_interrupt(endpoint.address, &osc_match_data.ctrl_data, DEFAULT_TIMEOUT)?; + // println!("write: {:02x?}", osc_match_data.ctrl_data); + // handle.write_interrupt(endpoint.address, &osc_match_data.ctrl_data, DEFAULT_TIMEOUT)?; } OscPacket::Bundle(bundle) => { println!("OSC Bundle: {:?}", bundle); diff --git a/src/nocturn-config.json b/src/nocturn-config.json index a4ead61..1c66e38 100644 --- a/src/nocturn-config.json +++ b/src/nocturn-config.json @@ -9,55 +9,78 @@ "mappings": [ { "name": "knob{i}", - "ctrl_in_num": {"Range": [64, 71]}, - "ctrl_out_num": {"Range": [64, 71]}, + "numbering": {"Range": { + "ctrl_in": [64, 71], + "ctrl_out": [64, 71], + "midi": [0, 7] + }}, "ctrl_kind": "Relative", "midi_kind": "Cc", - "midi_num": {"Range": [0, 7]} + "mode": "Accumulate" }, { "name": "knobTouch{i}", - "ctrl_in_num": {"Range": [96, 104]}, - "ctrl_kind": "Button", + "numbering": {"Range": { + "ctrl_in": [96, 104], + "mini": [0, 7] + }}, + "ctrl_kind": "OnOff", "midi_kind": "Cc", - "midi_num": {"Range": [0, 7]} + "mode": "Momentary" }, { "name": "button{i}", + "numbering": {"Range": { + "ctrl_in": [112, 127], + "ctrl_out": [112, 127], + "midi": [0, 15] + }}, "ctrl_in_num": {"Range": [112, 127]}, "ctrl_out_num": {"Range": [112, 127]}, - "ctrl_kind": "Button", + "ctrl_kind": "OnOff", "midi_kind": "Cc", - "midi_num": {"Range": [0, 7]} + "mode": "Toggle" }, { "name": "xfader", - "ctrl_in_num": {"Pair": [72, 73]}, + "numbering": {"Single": { + "ctrl_in_sequence": [72, 73], + "midi": 20 + }}, "ctrl_kind": "EightBit", "midi_kind": "CoarseFine", - "midi_num": {"Single": 20} + "mode": "Raw" }, { "name": "xfaderTouch", - "ctrl_in_num": {"Single": 83}, - "ctrl_kind": "Button", + "numbering": {"Single": { + "ctrl_in": 83, + "midi": 20 + }}, + "ctrl_kind": "OnOff", "midi_kind": "Cc", - "midi_num": {"Single": 20} + "mode": "Momentary" }, { "name": "speedDial", - "ctrl_in_num": {"Single": 74}, - "ctrl_out_num": {"Single": 80}, + "numbering": {"Single": { + "ctrl_in": 74, + "ctrl_out": 80, + "midi": 10 + }}, "ctrl_kind": "Relative", "midi_kind": "Cc", - "midi_num": {"Single": 10} + "mode": "Accumulate" }, { "name": "speedDialButton", - "ctrl_in_num": {"Single": 81}, - "ctrl_kind": "Button", + "numbering": {"Single": { + "ctrl_in": 81, + "midi": 10 + }}, + "ctrl_kind": "OnOff", "midi_kind": "Cc", - "midi_num": {"Single": 10} + "mode": "Momentary" } ] }