1#![cfg_attr(not(test), no_std)]
46
47extern crate alloc;
48use alloc::{boxed::Box, string::String, vec::Vec};
49use core::fmt;
50
51#[cfg(feature = "serde")]
52use serde::{Deserialize, Serialize};
53
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub struct IrTimestamp {
58 pub seconds: i64,
59 pub nanos: u32,
60}
61
62#[cfg(feature = "osc10")]
66#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct IrTimetag {
69 pub value: u64,
70}
71
72#[cfg(feature = "osc10")]
73impl IrTimetag {
74 pub fn immediate() -> Self {
76 Self { value: 1 }
77 }
78
79 pub fn from_ntp(ntp_time: u64) -> Self {
81 Self { value: ntp_time }
82 }
83
84 pub fn is_immediate(&self) -> bool {
86 self.value == 1
87 }
88}
89
90#[cfg(feature = "osc10")]
94#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
95#[derive(Clone, Debug, PartialEq)]
96pub enum IrBundleElement {
97 Message(IrValue),
99 Bundle(IrBundle),
101}
102
103#[cfg(feature = "osc10")]
106#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107#[derive(Clone, Debug, PartialEq)]
108pub struct IrBundle {
109 pub timetag: IrTimetag,
111 pub elements: Vec<IrBundleElement>,
113}
114
115#[cfg(feature = "osc10")]
116impl IrBundle {
117 pub fn immediate() -> Self {
119 Self {
120 timetag: IrTimetag::immediate(),
121 elements: Vec::new(),
122 }
123 }
124
125 pub fn new(timetag: IrTimetag) -> Self {
127 Self {
128 timetag,
129 elements: Vec::new(),
130 }
131 }
132
133 pub fn add_message(&mut self, message: IrValue) {
135 self.elements.push(IrBundleElement::Message(message));
136 }
137
138 pub fn add_bundle(&mut self, bundle: IrBundle) {
140 self.elements.push(IrBundleElement::Bundle(bundle));
141 }
142
143 pub fn add_element(&mut self, element: IrBundleElement) {
145 self.elements.push(element);
146 }
147
148 pub fn is_empty(&self) -> bool {
150 self.elements.is_empty()
151 }
152
153 pub fn len(&self) -> usize {
155 self.elements.len()
156 }
157
158 pub fn is_immediate(&self) -> bool {
160 self.timetag.is_immediate()
161 }
162}
163
164#[cfg(feature = "osc10")]
165impl IrBundleElement {
166 pub fn is_message(&self) -> bool {
168 matches!(self, IrBundleElement::Message(_))
169 }
170
171 pub fn is_bundle(&self) -> bool {
173 matches!(self, IrBundleElement::Bundle(_))
174 }
175
176 pub fn as_message(&self) -> Option<&IrValue> {
178 match self {
179 IrBundleElement::Message(msg) => Some(msg),
180 _ => None,
181 }
182 }
183
184 pub fn as_bundle(&self) -> Option<&IrBundle> {
186 match self {
187 IrBundleElement::Bundle(bundle) => Some(bundle),
188 _ => None,
189 }
190 }
191}
192
193#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
195#[derive(Clone, Debug, PartialEq, Default)]
196pub enum IrValue {
197 #[default]
198 Null,
199 Bool(bool),
200 Integer(i64),
201 Float(f64),
202 String(Box<str>),
203 #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
204 Binary(Vec<u8>),
205 Array(Vec<IrValue>),
206 Map(Vec<(String, IrValue)>),
208 Timestamp(IrTimestamp),
209 Ext {
211 type_id: i8,
212 #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
213 data: Vec<u8>,
214 },
215 #[cfg(feature = "osc10")]
218 Bundle(IrBundle),
219 #[cfg(feature = "osc11")]
222 Color {
223 r: u8,
224 g: u8,
225 b: u8,
226 a: u8,
227 },
228 #[cfg(feature = "osc11")]
231 Midi {
232 port: u8,
233 status: u8,
234 data1: u8,
235 data2: u8,
236 },
237}
238
239impl fmt::Display for IrValue {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 write!(f, "{:?}", self)
242 }
243}
244
245impl IrValue {
246 pub fn null() -> Self {
247 IrValue::Null
248 }
249
250 pub fn is_null(&self) -> bool {
251 matches!(self, IrValue::Null)
252 }
253
254 pub fn as_bool(&self) -> Option<bool> {
255 match self {
256 IrValue::Bool(v) => Some(*v),
257 _ => None,
258 }
259 }
260
261 pub fn as_integer(&self) -> Option<i64> {
262 match self {
263 IrValue::Integer(v) => Some(*v),
264 _ => None,
265 }
266 }
267
268 pub fn as_float(&self) -> Option<f64> {
269 match self {
270 IrValue::Float(v) => Some(*v),
271 _ => None,
272 }
273 }
274
275 pub fn as_str(&self) -> Option<&str> {
276 match self {
277 IrValue::String(v) => Some(v.as_ref()),
278 _ => None,
279 }
280 }
281
282 pub fn as_binary(&self) -> Option<&[u8]> {
283 match self {
284 IrValue::Binary(v) => Some(v.as_slice()),
285 _ => None,
286 }
287 }
288
289 pub fn as_array(&self) -> Option<&[IrValue]> {
290 match self {
291 IrValue::Array(v) => Some(v.as_slice()),
292 _ => None,
293 }
294 }
295
296 pub fn as_map(&self) -> Option<&[(String, IrValue)]> {
297 match self {
298 IrValue::Map(v) => Some(v.as_slice()),
299 _ => None,
300 }
301 }
302
303 pub fn as_timestamp(&self) -> Option<&IrTimestamp> {
304 match self {
305 IrValue::Timestamp(v) => Some(v),
306 _ => None,
307 }
308 }
309
310 pub fn as_ext(&self) -> Option<(i8, &[u8])> {
311 match self {
312 IrValue::Ext { type_id, data } => Some((*type_id, data.as_slice())),
313 _ => None,
314 }
315 }
316
317 #[cfg(feature = "osc10")]
318 pub fn as_bundle(&self) -> Option<&IrBundle> {
319 match self {
320 IrValue::Bundle(bundle) => Some(bundle),
321 _ => None,
322 }
323 }
324
325 #[cfg(feature = "osc11")]
326 pub fn as_color(&self) -> Option<(u8, u8, u8, u8)> {
327 match self {
328 IrValue::Color { r, g, b, a } => Some((*r, *g, *b, *a)),
329 _ => None,
330 }
331 }
332
333 #[cfg(feature = "osc11")]
334 pub fn as_midi(&self) -> Option<(u8, u8, u8, u8)> {
335 match self {
336 IrValue::Midi { port, status, data1, data2 } => Some((*port, *status, *data1, *data2)),
337 _ => None,
338 }
339 }
340}
341
342impl From<()> for IrValue {
343 fn from(_: ()) -> Self {
344 IrValue::Null
345 }
346}
347
348impl From<bool> for IrValue {
349 fn from(v: bool) -> Self {
350 IrValue::Bool(v)
351 }
352}
353
354impl From<i8> for IrValue {
355 fn from(v: i8) -> Self {
356 IrValue::Integer(v as i64)
357 }
358}
359
360impl From<i16> for IrValue {
361 fn from(v: i16) -> Self {
362 IrValue::Integer(v as i64)
363 }
364}
365
366impl From<i32> for IrValue {
367 fn from(v: i32) -> Self {
368 IrValue::Integer(v as i64)
369 }
370}
371
372impl From<i64> for IrValue {
373 fn from(v: i64) -> Self {
374 IrValue::Integer(v)
375 }
376}
377
378impl From<isize> for IrValue {
379 fn from(v: isize) -> Self {
380 IrValue::Integer(v as i64)
381 }
382}
383
384impl From<u8> for IrValue {
385 fn from(v: u8) -> Self {
386 IrValue::Integer(v as i64)
387 }
388}
389
390impl From<u16> for IrValue {
391 fn from(v: u16) -> Self {
392 IrValue::Integer(v as i64)
393 }
394}
395
396impl From<u32> for IrValue {
397 fn from(v: u32) -> Self {
398 IrValue::Integer(v as i64)
399 }
400}
401
402impl From<f32> for IrValue {
403 fn from(v: f32) -> Self {
404 IrValue::Float(v as f64)
405 }
406}
407
408impl From<f64> for IrValue {
409 fn from(v: f64) -> Self {
410 IrValue::Float(v)
411 }
412}
413
414impl From<String> for IrValue {
415 fn from(v: String) -> Self {
416 IrValue::String(v.into_boxed_str())
417 }
418}
419
420impl From<Box<str>> for IrValue {
421 fn from(v: Box<str>) -> Self {
422 IrValue::String(v)
423 }
424}
425
426impl From<&str> for IrValue {
427 fn from(v: &str) -> Self {
428 IrValue::String(v.into())
429 }
430}
431
432impl From<Vec<u8>> for IrValue {
433 fn from(v: Vec<u8>) -> Self {
434 IrValue::Binary(v)
435 }
436}
437
438impl From<&[u8]> for IrValue {
439 fn from(v: &[u8]) -> Self {
440 IrValue::Binary(v.to_vec())
441 }
442}
443
444impl From<Vec<IrValue>> for IrValue {
445 fn from(v: Vec<IrValue>) -> Self {
446 IrValue::Array(v)
447 }
448}
449
450impl From<Vec<(String, IrValue)>> for IrValue {
451 fn from(v: Vec<(String, IrValue)>) -> Self {
452 IrValue::Map(v)
453 }
454}
455
456impl From<IrTimestamp> for IrValue {
457 fn from(v: IrTimestamp) -> Self {
458 IrValue::Timestamp(v)
459 }
460}
461
462#[cfg(feature = "osc10")]
463impl From<IrBundle> for IrValue {
464 fn from(v: IrBundle) -> Self {
465 IrValue::Bundle(v)
466 }
467}
468
469#[cfg(feature = "osc10")]
470impl From<IrTimetag> for IrBundle {
471 fn from(timetag: IrTimetag) -> Self {
472 IrBundle {
473 timetag,
474 elements: Vec::new(),
475 }
476 }
477}
478
479#[cfg(feature = "osc10")]
480impl From<IrValue> for IrBundleElement {
481 fn from(value: IrValue) -> Self {
482 IrBundleElement::Message(value)
483 }
484}
485
486#[cfg(feature = "osc10")]
487impl From<IrBundle> for IrBundleElement {
488 fn from(bundle: IrBundle) -> Self {
489 IrBundleElement::Bundle(bundle)
490 }
491}
492
493impl IrValue {
494 #[cfg(feature = "osc11")]
496 pub fn color(r: u8, g: u8, b: u8, a: u8) -> Self {
497 IrValue::Color { r, g, b, a }
498 }
499
500 #[cfg(feature = "osc11")]
502 pub fn midi(port: u8, status: u8, data1: u8, data2: u8) -> Self {
503 IrValue::Midi { port, status, data1, data2 }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use alloc::vec;
511
512 #[test]
513 fn conversions_work() {
514 assert_eq!(IrValue::from(true).as_bool(), Some(true));
515 assert_eq!(IrValue::from(42_i32).as_integer(), Some(42));
516 assert_eq!(IrValue::from(3.5_f32).as_float(), Some(3.5));
517 assert_eq!(IrValue::from("hi").as_str(), Some("hi"));
518 assert_eq!(IrValue::from(vec![1_u8, 2]).as_binary(), Some(&[1, 2][..]));
519 let arr = IrValue::from(vec![IrValue::from(1_i32), IrValue::from(2_i32)]);
520 assert_eq!(arr.as_array().unwrap().len(), 2);
521 let ts = IrTimestamp {
522 seconds: 1,
523 nanos: 2,
524 };
525 assert_eq!(IrValue::from(ts).as_timestamp(), Some(&ts));
526 }
527
528 #[test]
529 fn ext_and_default_helpers() {
530 let ext = IrValue::Ext {
531 type_id: 9,
532 data: vec![0xAA, 0xBB],
533 };
534 assert_eq!(ext.as_ext(), Some((9, &[0xAA, 0xBB][..])));
535
536 let default = IrValue::default();
537 assert!(default.is_null());
538 assert!(default.as_array().is_none());
539 }
540
541 #[test]
542 #[cfg(feature = "osc10")]
543 fn bundle_creation_and_nesting() {
544 let mut bundle = IrBundle::immediate();
546 assert!(bundle.is_immediate());
547 assert!(bundle.is_empty());
548 assert_eq!(bundle.len(), 0);
549
550 bundle.add_message(IrValue::from("hello"));
552 assert!(!bundle.is_empty());
553 assert_eq!(bundle.len(), 1);
554
555 let mut nested_bundle = IrBundle::new(IrTimetag::from_ntp(1000));
557 assert!(!nested_bundle.is_immediate());
558 nested_bundle.add_message(IrValue::from(42));
559 nested_bundle.add_message(IrValue::from(true));
560
561 bundle.add_bundle(nested_bundle);
563 assert_eq!(bundle.len(), 2);
564
565 assert!(bundle.elements[0].is_message());
567 assert!(!bundle.elements[0].is_bundle());
568 assert_eq!(bundle.elements[0].as_message().unwrap().as_str(), Some("hello"));
569
570 assert!(!bundle.elements[1].is_message());
571 assert!(bundle.elements[1].is_bundle());
572 let nested = bundle.elements[1].as_bundle().unwrap();
573 assert_eq!(nested.len(), 2);
574 assert_eq!(nested.timetag.value, 1000);
575 }
576
577 #[test]
578 #[cfg(feature = "osc10")]
579 fn bundle_conversions() {
580 let bundle = IrBundle::immediate();
582 let value = IrValue::from(bundle.clone());
583 assert_eq!(value.as_bundle(), Some(&bundle));
584
585 let message = IrValue::from("test");
587 let element = IrBundleElement::from(message.clone());
588 assert!(element.is_message());
589 assert_eq!(element.as_message(), Some(&message));
590
591 let element = IrBundleElement::from(bundle.clone());
593 assert!(element.is_bundle());
594 assert_eq!(element.as_bundle(), Some(&bundle));
595 }
596
597 #[test]
598 #[cfg(feature = "osc10")]
599 fn timetag_functionality() {
600 let immediate = IrTimetag::immediate();
601 assert!(immediate.is_immediate());
602 assert_eq!(immediate.value, 1);
603
604 let ntp_time = IrTimetag::from_ntp(12345678);
605 assert!(!ntp_time.is_immediate());
606 assert_eq!(ntp_time.value, 12345678);
607 }
608
609 #[test]
610 #[cfg(feature = "osc10")]
611 fn complex_nested_bundle_structure() {
612 let mut root_bundle = IrBundle::immediate();
614
615 root_bundle.add_message(IrValue::from("root message 1"));
617 root_bundle.add_message(IrValue::from(100));
618
619 let mut nested1 = IrBundle::new(IrTimetag::from_ntp(2000));
621 nested1.add_message(IrValue::from("nested1 message"));
622
623 let mut nested2 = IrBundle::new(IrTimetag::from_ntp(3000));
625 nested2.add_message(IrValue::from("nested2 message"));
626
627 let mut deeply_nested = IrBundle::new(IrTimetag::from_ntp(4000));
628 deeply_nested.add_message(IrValue::from("deeply nested message"));
629 deeply_nested.add_message(IrValue::from(core::f64::consts::PI));
630
631 nested2.add_bundle(deeply_nested);
632
633 root_bundle.add_bundle(nested1);
635 root_bundle.add_bundle(nested2);
636
637 assert_eq!(root_bundle.len(), 4); assert!(root_bundle.elements[0].is_message());
640 assert!(root_bundle.elements[1].is_message());
641 assert!(root_bundle.elements[2].is_bundle());
642 assert!(root_bundle.elements[3].is_bundle());
643
644 let nested2_ref = root_bundle.elements[3].as_bundle().unwrap();
646 assert_eq!(nested2_ref.len(), 2); assert!(nested2_ref.elements[1].is_bundle());
648
649 let deeply_nested_ref = nested2_ref.elements[1].as_bundle().unwrap();
651 assert_eq!(deeply_nested_ref.len(), 2);
652 assert_eq!(deeply_nested_ref.timetag.value, 4000);
653 }
654
655 #[test]
656 #[cfg(feature = "osc11")]
657 fn osc_1_1_types() {
658 let color = IrValue::color(255, 128, 64, 255);
660 assert_eq!(color.as_color(), Some((255, 128, 64, 255)));
661
662 let midi = IrValue::midi(0, 144, 60, 127); assert_eq!(midi.as_midi(), Some((0, 144, 60, 127)));
665
666 assert!(color.as_midi().is_none());
668 assert!(midi.as_color().is_none());
669 assert!(IrValue::from(42).as_color().is_none());
670 assert!(IrValue::from("test").as_midi().is_none());
671 }
672}