use serde::{
de::{self, Visitor},
Deserialize, Deserializer, Serialize, Serializer,
};
use std::{collections::HashMap, fmt};
#[derive(Debug, Clone)]
pub struct OscRootNode {
pub(crate) root: OscNode,
}
impl OscRootNode {
pub fn new() -> Self {
Self {
root: OscNode {
full_path: "/".to_string(),
..Default::default() },
}
}
pub fn with_avatar(self) -> Self {
self.add_node(OscNode {
full_path: "/avatar".to_string(),
description: Some("Root for avatar-specific parameters.".to_string()),
..Default::default()
})
}
pub fn with_tracking(self) -> Self {
self.add_node(OscNode {
full_path: "/tracking".to_string(),
description: Some(
"Root for tracking-related OSC messages (e.g., head, hands).".to_string(),
),
..Default::default()
})
}
pub fn with_dolly(self) -> Self {
self.add_node(OscNode {
full_path: "/dolly".to_string(),
description: Some("Parameters related to camera dolly or movement.".to_string()),
..Default::default()
})
}
pub fn with_usercamera(self) -> Self {
self.add_node(OscNode {
full_path: "/usercamera".to_string(),
description: Some("Parameters related to the user's camera.".to_string()),
..Default::default()
})
}
pub fn get_node(&self, path: &str) -> Option<&OscNode> {
if path == "/" {
return Some(&self.root);
}
let mut current = &self.root;
for part in path.split('/').filter(|p| !p.is_empty()) {
current = current.contents.get(part)?; }
Some(current)
}
pub fn get_node_mut(&mut self, path: &str) -> Option<&mut OscNode> {
if path == "/" {
return Some(&mut self.root);
}
let mut current = &mut self.root;
for part in path.split('/').filter(|p| !p.is_empty()) {
current = current.contents.get_mut(part)?;
}
Some(current)
}
pub fn add_node(mut self, new_node_props: OscNode) -> Self {
let path_parts: Vec<&str> = new_node_props
.full_path
.split('/')
.filter(|p| !p.is_empty())
.collect();
let mut current = &mut self.root;
let mut constructed_path = String::from("/");
for (i, part) in path_parts.iter().enumerate() {
if i > 0 || (i == 0 && !part.is_empty()) {
if constructed_path.len() > 1 {
constructed_path.push('/');
}
constructed_path.push_str(part);
} else if i == 0 && part.is_empty() && constructed_path.is_empty() {
constructed_path = "/".to_string();
}
current = current
.contents
.entry(part.to_string())
.or_insert_with(|| OscNode {
full_path: constructed_path.clone(),
..Default::default()
});
}
current.description = new_node_props.description.or(current.description.take());
current.r#type = new_node_props.r#type.or(current.r#type.take());
current.access = if new_node_props.access != AccessMode::default() {
new_node_props.access
} else {
current.access
};
current.value = new_node_props.value.or(current.value.take());
current.range = new_node_props.range.or(current.range.take());
for (key, child_node) in new_node_props.contents {
current.contents.insert(key, child_node);
}
current.full_path = new_node_props.full_path;
self
}
pub fn remove_node(&mut self, path: &str) -> Option<OscNode> {
let parts: Vec<&str> = path.split('/').filter(|p| !p.is_empty()).collect();
if parts.is_empty() {
return None;
}
let mut current = &mut self.root;
for i in 0..parts.len() - 1 {
current = current.contents.get_mut(parts[i])?; }
current.contents.remove(&parts.last().unwrap().to_string())
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub struct OscNode {
pub full_path: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub contents: HashMap<String, OscNode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub r#type: Option<OscTypeTag>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)] pub access: AccessMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Vec<OscValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range: Option<Vec<RangeInfo>>,
}
impl OscNode {
pub fn is_type(&self, expected_type: &OscType) -> bool {
self.r#type
.as_ref()
.map_or(false, |tag| tag.is_single_type(expected_type))
}
pub fn is_boolean_type(&self) -> bool {
self.r#type.as_ref().map_or(false, |tag| tag.is_boolean())
}
pub fn get_single_osc_type(&self) -> Option<&OscType> {
self.r#type.as_ref().and_then(|tag| tag.get_single_type())
}
}
#[derive(Debug, Clone)]
pub struct OscTypeTag(Vec<OscType>);
impl OscTypeTag {
pub fn new(types: Vec<OscType>) -> Self {
OscTypeTag(types)
}
pub fn with_type(mut self, t: OscType) -> Self {
self.0.push(t);
self
}
pub fn from_tag(tag_string: &str) -> Option<OscTypeTag> {
let mut types = Vec::new();
let mut chars = tag_string.chars().peekable();
while let Some(c) = chars.next() {
if c == '[' {
let mut array_tag_str = String::from("[");
while let Some(ac) = chars.next() {
array_tag_str.push(ac);
if ac == ']' {
break;
}
}
if let Some(array_type) = OscType::from_tag(&array_tag_str) {
types.push(array_type);
} else {
return None; }
} else {
if let Some(t) = OscType::from_tag(&c.to_string()) {
types.push(t);
} else {
return None; }
}
}
if types.is_empty() && !tag_string.is_empty() {
return None;
}
Some(OscTypeTag(types))
}
pub fn is_single_type(&self, expected_type: &OscType) -> bool {
self.0.len() == 1 && &self.0[0] == expected_type
}
pub fn is_boolean(&self) -> bool {
self.0.len() == 1 && self.0[0].is_boolean()
}
pub fn get_single_type(&self) -> Option<&OscType> {
if self.0.len() == 1 {
Some(&self.0[0])
} else {
None
}
}
pub fn contains(&self, osc_type: &OscType) -> bool {
self.0.contains(osc_type)
}
}
impl Serialize for OscTypeTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tag_string = String::new();
for t in &self.0 {
tag_string.push_str(&t.tag());
}
serializer.serialize_str(&tag_string)
}
}
impl<'de> Deserialize<'de> for OscTypeTag {
fn deserialize<D>(deserializer: D) -> Result<OscTypeTag, D::Error>
where
D: Deserializer<'de>,
{
struct OscTypeTagVisitor;
impl<'de> Visitor<'de> for OscTypeTagVisitor {
type Value = OscTypeTag;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid OSC type tag string (e.g., 'ifs')")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
OscTypeTag::from_tag(value).ok_or_else(|| {
de::Error::custom(format!("invalid OSC type tag string: {}", value))
})
}
}
deserializer.deserialize_str(OscTypeTagVisitor)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum OscType {
Int32, Float32, OscString, OscBlob, Int64, Timetag, Double, Symbol, Char, RgbaColor, Midi, True, False, Nil, Infinitum, Array(Vec<OscType>), }
impl OscType {
pub fn tag(&self) -> String {
match self {
OscType::Int32 => "i".to_string(),
OscType::Float32 => "f".to_string(),
OscType::OscString => "s".to_string(),
OscType::OscBlob => "b".to_string(),
OscType::Int64 => "h".to_string(),
OscType::Timetag => "t".to_string(),
OscType::Double => "d".to_string(),
OscType::Symbol => "S".to_string(),
OscType::Char => "c".to_string(),
OscType::RgbaColor => "r".to_string(),
OscType::Midi => "m".to_string(),
OscType::True => "T".to_string(),
OscType::False => "F".to_string(),
OscType::Nil => "N".to_string(),
OscType::Infinitum => "I".to_string(),
OscType::Array(array_types) => {
let mut tag = String::from("[");
for t in array_types {
tag.push_str(&t.tag());
}
tag.push(']');
tag
}
}
}
pub fn is_boolean(&self) -> bool {
matches!(self, OscType::True | OscType::False)
}
pub fn is_numeric(&self) -> bool {
matches!(
self,
OscType::Int32 | OscType::Float32 | OscType::Int64 | OscType::Double
)
}
pub fn from_tag(tag_char_or_array_str: &str) -> Option<OscType> {
if tag_char_or_array_str.starts_with('[')
&& tag_char_or_array_str.ends_with(']')
&& tag_char_or_array_str.len() >= 2
{
let inner_tags_str = &tag_char_or_array_str[1..tag_char_or_array_str.len() - 1];
let mut types = Vec::new();
let mut chars = inner_tags_str.chars().peekable();
let mut current_segment = String::new();
while let Some(c) = chars.next() {
current_segment.push(c);
if c == '[' {
let mut bracket_level = 1;
while let Some(next_char_in_array) = chars.peek() {
if *next_char_in_array == '[' {
bracket_level += 1;
} else if *next_char_in_array == ']' {
bracket_level -= 1;
}
current_segment.push(chars.next().unwrap()); if bracket_level == 0 {
break;
}
}
if bracket_level != 0 {
return None;
}
if let Some(array_type_segment) = OscType::from_tag(¤t_segment) {
types.push(array_type_segment);
current_segment.clear();
} else {
return None; }
} else {
if let Some(basic_type) = OscType::from_tag(¤t_segment) {
types.push(basic_type);
current_segment.clear();
} else {
return None;
}
}
}
if !current_segment.is_empty() {
return None;
}
return Some(OscType::Array(types));
}
match tag_char_or_array_str {
"i" => Some(OscType::Int32),
"f" => Some(OscType::Float32),
"s" => Some(OscType::OscString),
"b" => Some(OscType::OscBlob),
"h" => Some(OscType::Int64),
"t" => Some(OscType::Timetag),
"d" => Some(OscType::Double),
"S" => Some(OscType::Symbol),
"c" => Some(OscType::Char),
"r" => Some(OscType::RgbaColor),
"m" => Some(OscType::Midi),
"T" => Some(OscType::True),
"F" => Some(OscType::False),
"N" => Some(OscType::Nil),
"I" => Some(OscType::Infinitum),
_ => None, }
}
}
impl Serialize for OscType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.tag())
}
}
impl<'de> Deserialize<'de> for OscType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct OscTypeVisitor;
impl<'de> Visitor<'de> for OscTypeVisitor {
type Value = OscType;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(
"a valid OSC type tag character (e.g., 'f') or array string (e.g., '[ii]')",
)
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
OscType::from_tag(value)
.ok_or_else(|| de::Error::custom(format!("invalid OSC type tag: {}", value)))
}
}
deserializer.deserialize_str(OscTypeVisitor)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RgbaColorValue {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(untagged)] pub enum OscValue {
Int(i32),
Float(f64), String(String),
Bool(bool),
Color(RgbaColorValue), Array(Vec<OscValue>), Empty {}, Nil, }
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub struct RangeInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub min: Option<OscValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max: Option<OscValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vals: Option<Vec<OscValue>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum AccessMode {
#[default] None, ReadOnly, WriteOnly, ReadWrite, }
impl Serialize for AccessMode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = match self {
AccessMode::None => 0,
AccessMode::ReadOnly => 1,
AccessMode::WriteOnly => 2,
AccessMode::ReadWrite => 3,
};
serializer.serialize_u8(value)
}
}
impl<'de> Deserialize<'de> for AccessMode {
fn deserialize<D>(deserializer: D) -> Result<AccessMode, D::Error>
where
D: Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
0 => Ok(AccessMode::None),
1 => Ok(AccessMode::ReadOnly),
2 => Ok(AccessMode::WriteOnly),
3 => Ok(AccessMode::ReadWrite),
_ => Err(de::Error::custom(format!(
"invalid access mode integer: {}, expected 0-3",
value
))),
}
}
}