Skip to main content

open_sound_control/
timetag.rs

1use crate::helpers::OscParseError;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4// From OSC v1.0 Spec:
5// 
6// Time tags are represented by a 64 bit fixed point number. 
7// The first 32 bits specify the number of seconds since midnight 
8// on January 1, 1900, and the last 32 bits specify fractional 
9// parts of a second to a precision of about 200 picoseconds. 
10// This is the representation used by Internet NTP timestamps.
11// The time tag value consisting of 63 zero bits followed by a one
12// in the least signifigant bit is a special case meaning "immediately."
13
14/// Represents an OSC Time Tag
15#[derive(Debug, PartialEq, Clone)]
16pub struct OscTimeTag {
17    pub seconds: u32,      // number of seconds since midnight on January 1, 1900
18    pub fractional: u32,     // fractional parts of a second to a precision of about 200 picoseconds
19}
20
21impl OscTimeTag {
22
23    /// Constructs an OscTimeTag representing the current time
24    pub fn now() -> Self {
25        let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time calculation error");
26
27        let unix_seconds = now.as_secs() as u64;
28        let unix_nanos = now.subsec_nanos() as u64;
29        let ntp_seconds = unix_seconds + 2208988800;
30        let fractional = ((unix_nanos as f64 / 1_000_000_000.0) * (u32::MAX as f64 + 1.0)) as u32;
31
32        OscTimeTag {
33            seconds: ntp_seconds as u32,
34            fractional,
35        }
36    }
37
38    /// Converts the OscTimeTag to a sequence of bytes
39    pub fn to_bytes(&self) -> [u8; 8] {
40        let mut bytes = [0u8; 8];
41        bytes[..4].copy_from_slice(&self.seconds.to_be_bytes());
42        bytes[4..].copy_from_slice(&self.fractional.to_be_bytes());
43        bytes
44    }
45
46    /// Constructs an OscTimeTag from a i64 value
47    pub fn from_i64(value: i64) -> Self {
48        let uvalue = value as u64; // interpret bits as unsigned
49        let seconds = (uvalue >> 32) as u32;
50        let fractional = uvalue as u32; // lower 32 bits
51        Self { seconds, fractional }
52    }
53
54    /// Constructs an OscTimeTag from a sequence of bytes
55    pub fn from_bytes(bytes: &[u8]) -> Result<Self, OscParseError> {
56        if bytes.len() < 8 {
57            return Err(OscParseError::NotEnoughData);
58        }
59        let seconds = u32::from_be_bytes(bytes[..4].try_into().unwrap());
60        let fractional = u32::from_be_bytes(bytes[4..8].try_into().unwrap());
61        Ok(Self { seconds, fractional })
62    }
63}
64
65//==================================================================
66// Tests
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_time_tag() {
73      let tag = OscTimeTag { seconds: 0x11223344, fractional: 0x55667788 };
74      assert_eq!(tag.to_bytes(),[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);
75
76      let result = OscTimeTag::from_bytes(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]);
77      assert!(result.is_ok());
78      let tag = result.unwrap();
79      assert_eq!(tag.seconds, 0x11223344);
80      assert_eq!(tag.fractional, 0x55667788);
81
82      let immediately_tag = OscTimeTag { seconds: 0, fractional: 1 };
83      assert_eq!(immediately_tag.seconds, 0);
84      assert_eq!(immediately_tag.fractional, 1);
85    }
86}