├── .gitignore ├── Cargo.toml ├── LICENSE ├── README └── src ├── argument.rs ├── lib.rs ├── message.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # cargo stuff 2 | Cargo.lock 3 | .cargo 4 | 5 | # build stuff 6 | target 7 | 8 | # tempfile stuff 9 | .*swp 10 | *.swo 11 | *~ 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinyosc" 3 | version = "0.0.3" 4 | authors = ["William Light "] 5 | description = "OpenSoundControl implementation" 6 | license = "MIT" 7 | readme = "README" 8 | keywords = ["audio", "osc", "opensoundcontrol"] 9 | repository = "https://github.com/wrl/tinyosc" 10 | 11 | [dependencies] 12 | byteorder = "0.5.1" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 William Light 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | _ _ 2 | | |_|_|___ _ _ ___ ___ ___ 3 | | _| | | | | . |_ -| _| 4 | |_| |_|_|_|_ |___|___|___| 5 | |___| 6 | 7 | tinyosc is a small, liberally licensed opensoundcontrol implementation for 8 | rust. it is, at the moment, quite bare-bones, but the gist and spirit are 9 | both there. 10 | 11 | currently, only serialization and deserialization of messages is 12 | supported. pattern matching is planned. 13 | 14 | tinyosc supports the following type-tags: 15 | 16 | 'i' => i32, 17 | 'f' => f32, 18 | 's' => &str, 19 | 'T' => true, 20 | 'F' => false, 21 | 'N' => Argument::None 22 | 23 | you can construct argument lists manually, with vec![] like so: 24 | 25 | Message { 26 | path: "/where/ever/its/happenin", 27 | arguments: vec![Argument::i(42), Argument::f(32.32), Argument::F] 28 | } 29 | 30 | or you could use the handy osc_args![] macro, which automatically wraps 31 | arguments in their corresponding Argument variant: 32 | 33 | Message { 34 | path: "/where/ever/its/happenin", 35 | arguments: osc_args![42, 32.32, false] 36 | } 37 | 38 | this is identical in effect to the above example with vec![]. the choice 39 | is yours! 40 | 41 | simply pair Message serialization/deserialization with std::net::UdpSocket 42 | and you are well on yr way! 43 | 44 | questions, comments, and/or ascii art to wrl@illest.net 45 | peace 46 | -------------------------------------------------------------------------------- /src/argument.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 William Light 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | use std::str; 22 | use std::io::Write; 23 | use std::io; 24 | 25 | use byteorder::{ByteOrder, WriteBytesExt, BigEndian}; 26 | 27 | #[allow(non_camel_case_types)] 28 | #[derive(Debug)] 29 | pub enum Argument<'a> { 30 | i(i32), 31 | f(f32), 32 | s(&'a str), 33 | T, 34 | F, 35 | None 36 | } 37 | 38 | fn strchr(haystack: &[u8], needle: u8) -> Option { 39 | for (idx, &hay) in haystack.iter().enumerate() { 40 | if hay == needle { 41 | return Some(idx) 42 | } 43 | } 44 | 45 | return None 46 | } 47 | 48 | impl<'a> Argument<'a> { 49 | pub fn deserialize(typetag: char, slice: &mut &'a [u8]) -> Result, ()> { 50 | match typetag { 51 | 'T' => Ok(Argument::T), 52 | 'F' => Ok(Argument::F), 53 | 'N' => Ok(Argument::None), 54 | 55 | 'i' => { 56 | let n = BigEndian::read_i32(*slice); 57 | *slice = &slice[4 ..]; 58 | Ok(Argument::i(n)) 59 | }, 60 | 61 | 'f' => { 62 | let n = BigEndian::read_f32(*slice); 63 | *slice = &slice[4 ..]; 64 | Ok(Argument::f(n)) 65 | }, 66 | 67 | 's' => { 68 | // find the terminating null 69 | let next_null = match strchr(slice, 0) { 70 | Some(next) => next + 1, 71 | None => return Err(()) 72 | }; 73 | 74 | let s = match str::from_utf8(&slice[.. (next_null - 1)]) { 75 | Ok(s) => s, 76 | Err(_) => return Err(()) 77 | }; 78 | 79 | // swallow the additional padding 80 | let pad = match next_null % 4 { 81 | 0 => 0, 82 | pad => 4 - pad, 83 | }; 84 | 85 | *slice = &slice[(next_null + pad) ..]; 86 | 87 | Ok(Argument::s(s)) 88 | } 89 | 90 | _ => Err(()) 91 | } 92 | } 93 | 94 | pub fn typetag(&self) -> char { 95 | match *self { 96 | Argument::T => 'T', 97 | Argument::F => 'F', 98 | Argument::None => 'N', 99 | Argument::i(_) => 'i', 100 | Argument::f(_) => 'f', 101 | Argument::s(_) => 's' 102 | } 103 | } 104 | 105 | pub fn serialize(&self, into: &mut Write) -> io::Result<()> { 106 | match *self { 107 | Argument::T | Argument::F | Argument::None => Ok(()), 108 | 109 | Argument::i(arg) => into.write_i32::(arg), 110 | Argument::f(arg) => into.write_f32::(arg), 111 | 112 | Argument::s(arg) => { 113 | try!(into.write_all(arg.as_ref())); 114 | 115 | let pad = 1 + match (arg.len() + 1) % 4 { 116 | 0 => 0, 117 | pad => 4 - pad 118 | }; 119 | 120 | try!(into.write_all(&[0; 5][.. pad])); 121 | 122 | Ok(()) 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl<'a> From for Argument<'a> { 129 | fn from(b: bool) -> Argument<'a> { 130 | if b { 131 | Argument::T 132 | } else { 133 | Argument::F 134 | } 135 | } 136 | } 137 | 138 | impl<'a> From for Argument<'a> { 139 | fn from(i: i32) -> Argument<'a> { 140 | Argument::i(i) 141 | } 142 | } 143 | 144 | impl<'a> From for Argument<'a> { 145 | fn from(f: f32) -> Argument<'a> { 146 | Argument::f(f) 147 | } 148 | } 149 | 150 | impl<'a> From<&'a str> for Argument<'a> { 151 | fn from(s: &'a str) -> Argument<'a> { 152 | Argument::s(s) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 William Light 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | extern crate byteorder; 22 | 23 | mod message; 24 | mod argument; 25 | 26 | pub use message::Message; 27 | pub use argument::Argument; 28 | 29 | #[macro_export] 30 | macro_rules! osc_args { 31 | [] => {{ 32 | vec![] 33 | }}; 34 | 35 | [$($x:expr),*] => {{ 36 | let mut v = vec![]; 37 | $(v.push($crate::Argument::from($x));)* 38 | v 39 | }}; 40 | } 41 | 42 | mod test; 43 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 William Light 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | use std::io::Write; 22 | use std::io; 23 | 24 | use Argument; 25 | 26 | pub struct Message<'a> { 27 | pub path: &'a str, 28 | pub arguments: Vec> 29 | } 30 | 31 | impl<'a> Message<'a> { 32 | pub fn deserialize(buf: &'a [u8]) -> Result, ()> { 33 | let mut msg = Message { 34 | path: "", 35 | arguments: vec![] 36 | }; 37 | 38 | let mut slice = buf; 39 | 40 | match Argument::deserialize('s', &mut slice) { 41 | Ok(Argument::s(st)) => msg.path = st, 42 | _ => return Err(()) 43 | } 44 | 45 | let typetags = match Argument::deserialize('s', &mut slice) { 46 | Ok(Argument::s(st)) => st, 47 | _ => return Err(()) 48 | }; 49 | 50 | if typetags.as_bytes()[0] != (',' as u8) { 51 | return Err(()) 52 | } 53 | 54 | for typetag in typetags[1 ..].chars() { 55 | let arg = Argument::deserialize(typetag, &mut slice); 56 | 57 | match arg { 58 | Ok(arg) => msg.arguments.push(arg), 59 | Err(_) => return Err(()) 60 | } 61 | } 62 | 63 | Ok(msg) 64 | } 65 | 66 | pub fn serialize_into(&self, into: &mut Write) -> io::Result<()> { 67 | try!(Argument::s(self.path).serialize(into)); 68 | 69 | let mut typetags = String::from(","); 70 | 71 | for arg in &self.arguments { 72 | typetags.push(arg.typetag()); 73 | } 74 | 75 | try!(Argument::s(&*typetags).serialize(into)); 76 | 77 | for arg in &self.arguments { 78 | try!(arg.serialize(into)); 79 | } 80 | 81 | Ok(()) 82 | } 83 | 84 | pub fn serialize(&self) -> io::Result> { 85 | let mut ret: Vec = vec![]; 86 | try!(self.serialize_into(&mut ret)); 87 | Ok(ret) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 William Light 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #[allow(unused_imports)] 22 | use { 23 | Argument, 24 | Message 25 | }; 26 | 27 | macro_rules! assert_msg_equal { 28 | ($x:expr, $expected:expr) => { 29 | assert_eq!($x.unwrap(), $expected.as_bytes()); 30 | } 31 | } 32 | 33 | #[test] 34 | fn serialize_no_args() { 35 | let msg = Message { 36 | path: "/test_msg", 37 | arguments: osc_args![] 38 | }; 39 | 40 | assert_msg_equal!(msg.serialize(), "/test_msg\0\0\0,\0\0\0"); 41 | } 42 | 43 | #[test] 44 | fn serialize_bool() { 45 | let msg = Message { 46 | path: "/test_msg", 47 | arguments: osc_args![true, false] 48 | }; 49 | 50 | assert_msg_equal!(msg.serialize(), "/test_msg\0\0\0,TF\0"); 51 | } 52 | 53 | #[test] 54 | fn serialize_none() { 55 | let msg = Message { 56 | path: "/test_msg", 57 | arguments: osc_args![Argument::None] 58 | }; 59 | 60 | assert_msg_equal!(msg.serialize(), "/test_msg\0\0\0,N\0\0"); 61 | } 62 | 63 | #[test] 64 | fn serialize_i32() { 65 | let msg = Message { 66 | path: "/test_msg", 67 | arguments: osc_args![42] 68 | }; 69 | 70 | assert_msg_equal!(msg.serialize(), "/test_msg\0\0\0,i\0\0\0\0\0\x2A"); 71 | } 72 | 73 | #[test] 74 | fn serialize_f32() { 75 | let msg = Message { 76 | path: "/test_msg", 77 | arguments: osc_args![0.0 as f32] 78 | }; 79 | 80 | assert_msg_equal!(msg.serialize(), "/test_msg\0\0\0,f\0\0\0\0\0\0"); 81 | } 82 | 83 | #[test] 84 | fn serialize_str() { 85 | let msg = Message { 86 | path: "/test_msg", 87 | arguments: osc_args!["testing"] 88 | }; 89 | 90 | assert_msg_equal!(msg.serialize(), "/test_msg\0\0\0,s\0\0testing\0"); 91 | } 92 | 93 | #[test] 94 | fn serialize_kitchen_sink() { 95 | let msg = Message { 96 | path: "/test_msg", 97 | arguments: osc_args![Argument::None, 42, (0.0 as f32), true, "testing", false] 98 | }; 99 | 100 | assert_msg_equal!(msg.serialize(), 101 | "/test_msg\0\0\0,NifTsF\0\0\0\0\x2A\0\0\0\0testing\0"); 102 | } 103 | 104 | #[test] 105 | fn deserialize_no_args() { 106 | let buf = "/test_msg\0\0\0,\0\0\0".as_bytes(); 107 | let msg = Message::deserialize(buf).unwrap(); 108 | 109 | assert_eq!(msg.path, "/test_msg"); 110 | assert!(msg.arguments.len() == 0); 111 | } 112 | 113 | #[test] 114 | fn deserialize_bool() { 115 | let buf = "/test_msg\0\0\0,TF\0".as_bytes(); 116 | let msg = Message::deserialize(buf).unwrap(); 117 | 118 | assert_eq!(msg.path, "/test_msg"); 119 | assert!(msg.arguments.len() == 2); 120 | 121 | assert!(match msg.arguments[0] { 122 | Argument::T => true, 123 | _ => false 124 | }); 125 | 126 | assert!(match msg.arguments[1] { 127 | Argument::F => true, 128 | _ => false 129 | }); 130 | } 131 | 132 | #[test] 133 | fn deserialize_none() { 134 | let buf = "/test_msg\0\0\0,N\0\0".as_bytes(); 135 | let msg = Message::deserialize(buf).unwrap(); 136 | 137 | assert_eq!(msg.path, "/test_msg"); 138 | assert!(msg.arguments.len() == 1); 139 | 140 | assert!(match msg.arguments[0] { 141 | Argument::None => true, 142 | _ => false 143 | }); 144 | } 145 | 146 | #[test] 147 | fn deserialize_i32() { 148 | let buf = "/test_msg\0\0\0,i\0\0\0\0\0\x2A".as_bytes(); 149 | let msg = Message::deserialize(buf).unwrap(); 150 | 151 | assert_eq!(msg.path, "/test_msg"); 152 | assert!(msg.arguments.len() == 1); 153 | 154 | assert!(match msg.arguments[0] { 155 | Argument::i(v) => (v == 42), 156 | _ => false 157 | }); 158 | } 159 | 160 | #[test] 161 | fn deserialize_f32() { 162 | let buf = "/test_msg\0\0\0,f\0\0\0\0\0\0".as_bytes(); 163 | let msg = Message::deserialize(buf).unwrap(); 164 | 165 | assert_eq!(msg.path, "/test_msg"); 166 | assert!(msg.arguments.len() == 1); 167 | 168 | assert!(match msg.arguments[0] { 169 | Argument::f(v) => (v == 0.0), 170 | _ => false 171 | }); 172 | } 173 | 174 | #[test] 175 | fn deserialize_string() { 176 | let buf = "/test_msg\0\0\0,s\0\0testing\0".as_bytes(); 177 | let msg = Message::deserialize(buf).unwrap(); 178 | 179 | assert_eq!(msg.path, "/test_msg"); 180 | assert!(msg.arguments.len() == 1); 181 | 182 | assert!(match msg.arguments[0] { 183 | Argument::s(v) => (v == "testing"), 184 | _ => false 185 | }); 186 | } 187 | 188 | #[test] 189 | fn deserialize_kitchen_sink() { 190 | let buf = "/test_msg\0\0\0,NifTsF\0\0\0\0\x2A\0\0\0\0testing\0".as_bytes(); 191 | let msg = Message::deserialize(buf).unwrap(); 192 | 193 | assert_eq!(msg.path, "/test_msg"); 194 | assert!(msg.arguments.len() == 6); 195 | 196 | assert!(match msg.arguments[0] { 197 | Argument::None => true, 198 | _ => false 199 | }); 200 | assert!(match msg.arguments[1] { 201 | Argument::i(v) => (v == 42), 202 | _ => false 203 | }); 204 | 205 | assert!(match msg.arguments[2] { 206 | Argument::f(v) => (v == 0.0), 207 | _ => false 208 | }); 209 | 210 | assert!(match msg.arguments[3] { 211 | Argument::T => true, 212 | _ => false 213 | }); 214 | 215 | assert!(match msg.arguments[4] { 216 | Argument::s(v) => (v == "testing"), 217 | _ => false 218 | }); 219 | 220 | assert!(match msg.arguments[5] { 221 | Argument::F => true, 222 | _ => false 223 | }); 224 | } 225 | --------------------------------------------------------------------------------