From e75f30de4c173e23ced224a0768ac595bc53e3e7 Mon Sep 17 00:00:00 2001
From: OptoCloud <26223094+OptoCloud@users.noreply.github.com>
Date: Wed, 10 Jul 2024 17:59:52 +0200
Subject: [PATCH 1/2] Increase accuracy and speed of TimeTag conversions
Microseconds are now preserved and conversion is 167 times faster
---
CoreOSC.Tests/TimetagTest.cs | 13 +++++++-----
CoreOSC/TimeTag.cs | 4 +++-
CoreOSC/Utils.cs | 39 ++++++++++++++++++++++++------------
3 files changed, 37 insertions(+), 19 deletions(-)
diff --git a/CoreOSC.Tests/TimetagTest.cs b/CoreOSC.Tests/TimetagTest.cs
index 9b7300b..423e395 100644
--- a/CoreOSC.Tests/TimetagTest.cs
+++ b/CoreOSC.Tests/TimetagTest.cs
@@ -8,12 +8,14 @@ public class TimetagTest
[Fact]
public void TestTimetag()
{
- var time = (ulong) 60 * 60 * 24 * 365 * 108;
- time <<= 32;
- time += (ulong)(Math.Pow(2, 32) / 2);
- var date = Utils.TimeTagToDateTime(time);
+ ulong tag = 0;
- Assert.Equal(DateTime.Parse("2007-12-06 00:00:00.500"), date);
+ tag |= (ulong)3424309443 << 32; // https://www.timeanddate.com/date/durationresult.html?d1=1&m1=1&y1=1900&d2=6&m2=7&y2=2008&h1=0&i1=0&s1=0&h2=5&i2=4&s2=3
+ tag |= (ulong)(0xFFFFFFFF / 10); // 100ms
+
+ var date = Utils.TimeTagToDateTime(tag);
+
+ Assert.Equal(DateTime.Parse("2008-7-06 05:04:03.100"), date);
}
[Fact]
@@ -29,5 +31,6 @@ public void TestDateTimeToTimetag()
Assert.Equal(dt.Minute, dtBack.Minute);
Assert.Equal(dt.Second, dtBack.Second);
Assert.Equal(dt.Millisecond, dtBack.Millisecond);
+ Assert.Equal(dt.Microsecond, dtBack.Microsecond);
}
}
\ No newline at end of file
diff --git a/CoreOSC/TimeTag.cs b/CoreOSC/TimeTag.cs
index c60b6aa..d476c1d 100644
--- a/CoreOSC/TimeTag.cs
+++ b/CoreOSC/TimeTag.cs
@@ -4,6 +4,8 @@ namespace LucHeart.CoreOSC;
public struct TimeTag
{
+ public static TimeTag Immediate => new(1);
+
public ulong Tag;
public DateTime Timestamp
@@ -19,7 +21,7 @@ public DateTime Timestamp
public double Fraction
{
get => Utils.TimeTagToFraction(Tag);
- set => Tag = (Tag & 0xFFFFFFFF00000000) + (uint)(value * 0xFFFFFFFF);
+ set => Tag = Utils.FractionToTimeTag(value);
}
public TimeTag(ulong value)
diff --git a/CoreOSC/Utils.cs b/CoreOSC/Utils.cs
index e646f65..d3c972a 100644
--- a/CoreOSC/Utils.cs
+++ b/CoreOSC/Utils.cs
@@ -4,17 +4,22 @@ namespace LucHeart.CoreOSC;
public static class Utils
{
+ private static readonly long EpochTicks = new DateTime(1900, 1, 1).Ticks;
+ private const uint OscTicksPerSecond = 0xFFFFFFFF;
+ private const double OscTicksPerDotNetTick = OscTicksPerSecond / (double)TimeSpan.TicksPerSecond; // 429.4967295
+
public static DateTime TimeTagToDateTime(ulong val)
{
if (val == 1)
return DateTime.Now;
- var seconds = (uint)(val >> 32);
- var time = DateTime.Parse("1900-01-01 00:00:00");
- time = time.AddSeconds(seconds);
- var fraction = TimeTagToFraction(val);
- time = time.AddSeconds(fraction);
- return time;
+ uint seconds = (uint)(val >> 32);
+ uint fraction = (uint)(val & 0xFFFFFFFF);
+
+ long secondsTicks = seconds * TimeSpan.TicksPerSecond;
+ long fractionTicks = (long)Math.Round(fraction / OscTicksPerDotNetTick); // We will loose accuracy in this conversion since there is about 430 OSC ticks per dotnet tick
+
+ return new DateTime(EpochTicks + secondsTicks + fractionTicks);
}
public static double TimeTagToFraction(ulong val)
@@ -22,18 +27,26 @@ public static double TimeTagToFraction(ulong val)
if (val == 1)
return 0.0;
- var seconds = (uint)(val & 0x00000000FFFFFFFF);
- var fraction = (double)seconds / 0xFFFFFFFF;
- return fraction;
+ return (double)val / OscTicksPerSecond;
}
public static ulong DateTimeToTimeTag(DateTime value)
{
- ulong seconds = (uint)(value - DateTime.Parse("1900-01-01 00:00:00.000")).TotalSeconds;
- var fraction = (uint)Math.Ceiling(0xFFFFFFFF * ((double)value.Millisecond / 1000));
+ long ticks = value.Ticks - EpochTicks;
+ if (ticks < 0) return 0;
+
+ uint seconds = (uint)(ticks / TimeSpan.TicksPerSecond);
+ uint fractions = (uint)Math.Round((ticks - (seconds * TimeSpan.TicksPerSecond)) * OscTicksPerDotNetTick);
- var output = (seconds << 32) + fraction;
- return output;
+ ulong secondTicks = (ulong)seconds << 32;
+ ulong fractionTicks = (ulong)fractions & 0xFFFFFFFF;
+
+ return secondTicks | fractionTicks;
+ }
+
+ public static ulong FractionToTimeTag(double value)
+ {
+ return (ulong)(value * OscTicksPerSecond);
}
public static int AlignedStringLength(string val)
From 410a09f0064bf2ccbb52ae9796214ac8f8f9878e Mon Sep 17 00:00:00 2001
From: OptoCloud <26223094+OptoCloud@users.noreply.github.com>
Date: Wed, 10 Jul 2024 18:17:40 +0200
Subject: [PATCH 2/2] Fix misunderstanding between fractions <-> seconds
---
CoreOSC/TimeTag.cs | 16 +++++++++++++++-
CoreOSC/Utils.cs | 26 +++++++++++++++++---------
2 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/CoreOSC/TimeTag.cs b/CoreOSC/TimeTag.cs
index d476c1d..b4d8593 100644
--- a/CoreOSC/TimeTag.cs
+++ b/CoreOSC/TimeTag.cs
@@ -8,12 +8,26 @@ public struct TimeTag
public ulong Tag;
+ ///
+ /// Gets or sets the timestamp from a DateTime. DateTime has an accuracy down to 100 nanoseconds (100'000
+ /// picoseconds)
+ ///
public DateTime Timestamp
{
get => Utils.TimeTagToDateTime(Tag);
set => Tag = Utils.DateTimeToTimeTag(value);
}
+ ///
+ /// Gets or sets the total seconds in the timestamp. the double precision number is multiplied by 2^32
+ /// giving an accuracy down to about 230 picoseconds ( 1/(2^32) of a second)
+ ///
+ public double Seconds
+ {
+ get => Utils.TimeTagToSeconds(Tag);
+ set => Tag = Utils.SecondsToTimeTag(value);
+ }
+
///
/// Gets or sets the fraction of a second in the timestamp. the double precision number is multiplied by 2^32
/// giving an accuracy down to about 230 picoseconds ( 1/(2^32) of a second)
@@ -21,7 +35,7 @@ public DateTime Timestamp
public double Fraction
{
get => Utils.TimeTagToFraction(Tag);
- set => Tag = Utils.FractionToTimeTag(value);
+ set => Tag = Utils.SecondsToTimeTag(value);
}
public TimeTag(ulong value)
diff --git a/CoreOSC/Utils.cs b/CoreOSC/Utils.cs
index d3c972a..347a995 100644
--- a/CoreOSC/Utils.cs
+++ b/CoreOSC/Utils.cs
@@ -22,14 +22,6 @@ public static DateTime TimeTagToDateTime(ulong val)
return new DateTime(EpochTicks + secondsTicks + fractionTicks);
}
- public static double TimeTagToFraction(ulong val)
- {
- if (val == 1)
- return 0.0;
-
- return (double)val / OscTicksPerSecond;
- }
-
public static ulong DateTimeToTimeTag(DateTime value)
{
long ticks = value.Ticks - EpochTicks;
@@ -44,11 +36,27 @@ public static ulong DateTimeToTimeTag(DateTime value)
return secondTicks | fractionTicks;
}
- public static ulong FractionToTimeTag(double value)
+ public static double TimeTagToSeconds(ulong val)
+ {
+ if (val == 1)
+ return 0.0;
+
+ return (double)val / OscTicksPerSecond;
+ }
+
+ public static ulong SecondsToTimeTag(double value)
{
return (ulong)(value * OscTicksPerSecond);
}
+ public static double TimeTagToFraction(ulong val)
+ {
+ if (val == 1)
+ return 0.0;
+
+ return (double)(val & 0xFFFFFFFF) / OscTicksPerSecond;
+ }
+
public static int AlignedStringLength(string val)
{
var len = val.Length + (4 - val.Length % 4);