Skip to content

Commit

Permalink
Merge pull request #2 from OptoCloud/master
Browse files Browse the repository at this point in the history
Increase accuracy and speed of TimeTag conversions
  • Loading branch information
LucHeart authored Jul 10, 2024
2 parents bc589bb + 410a09f commit c15e2f3
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 21 deletions.
13 changes: 8 additions & 5 deletions CoreOSC.Tests/TimetagTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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);
}
}
18 changes: 17 additions & 1 deletion CoreOSC/TimeTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,38 @@ namespace LucHeart.CoreOSC;

public struct TimeTag
{
public static TimeTag Immediate => new(1);

public ulong Tag;

/// <summary>
/// Gets or sets the timestamp from a DateTime. DateTime has an accuracy down to 100 nanoseconds (100'000
/// picoseconds)
/// </summary>
public DateTime Timestamp
{
get => Utils.TimeTagToDateTime(Tag);
set => Tag = Utils.DateTimeToTimeTag(value);
}

/// <summary>
/// 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)
/// </summary>
public double Seconds
{
get => Utils.TimeTagToSeconds(Tag);
set => Tag = Utils.SecondsToTimeTag(value);
}

/// <summary>
/// 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)
/// </summary>
public double Fraction
{
get => Utils.TimeTagToFraction(Tag);
set => Tag = (Tag & 0xFFFFFFFF00000000) + (uint)(value * 0xFFFFFFFF);
set => Tag = Utils.SecondsToTimeTag(value);
}

public TimeTag(ulong value)
Expand Down
51 changes: 36 additions & 15 deletions CoreOSC/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,57 @@ 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)
public static ulong DateTimeToTimeTag(DateTime value)
{
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);

ulong secondTicks = (ulong)seconds << 32;
ulong fractionTicks = (ulong)fractions & 0xFFFFFFFF;

return secondTicks | fractionTicks;
}

public static double TimeTagToSeconds(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)
public static ulong SecondsToTimeTag(double 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));
return (ulong)(value * OscTicksPerSecond);
}

public static double TimeTagToFraction(ulong val)
{
if (val == 1)
return 0.0;

var output = (seconds << 32) + fraction;
return output;
return (double)(val & 0xFFFFFFFF) / OscTicksPerSecond;
}

public static int AlignedStringLength(string val)
Expand Down

0 comments on commit c15e2f3

Please sign in to comment.