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);