Skip to content

Commit

Permalink
Convert osc lib to use template types to reduce amount of code comple…
Browse files Browse the repository at this point in the history
…xity
  • Loading branch information
benaclejames committed Mar 16, 2023
1 parent 4965159 commit 9298941
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 89 deletions.
2 changes: 1 addition & 1 deletion VRCFaceTracking/MainStandalone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static class MainStandalone
public static OscMain OscMain;
public static UnifiedConfig unifiedConfig = new UnifiedConfig();

private static List<OscMessage> ConstructMessages(IEnumerable<OSCParams.BaseParam> parameters) =>
private static List<byte[]> ConstructMessages(IEnumerable<OSCParams.BaseParam> parameters) =>
parameters.Where(p => p.NeedsSend).Select(param =>
{
param.NeedsSend = false;
Expand Down
33 changes: 24 additions & 9 deletions VRCFaceTracking/OSC/OSCBundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,43 @@ public class OscBundle
{
public readonly byte[] Data;

public OscBundle(IEnumerable<OscMessage> messages)
public OscBundle(OscMessage<object>[] messages)
{
int size = messages.Sum(param => param.Data.Length + 4);
Data = new byte[16+size]; // Include #bundle header and null terminator
int messageCount = messages.Length;
byte[][] messageData = new byte[][messageCount];
// Fill the second array with the message data from each message
int i = 0;
int combinedSize = 0;
foreach (var message in messages)
{
byte[] oscData = message.Serialize();
messageData[i] = oscData;
combinedSize += oscData.Length;
i++;
}

Data = new byte[16+combinedSize]; // Include #bundle header and null terminator
Array.Copy(new byte[] {35, 98, 117, 110, 100, 108, 101, 0}, Data, 8);

// Get the NTP time with picoseconds
Int64 time = (Int64) (DateTime.UtcNow - new DateTime(1900, 1, 1)).TotalMilliseconds * 1000;
var timeBytes = BitConverter.GetBytes(time);
Array.Reverse(timeBytes);
if (BitConverter.IsLittleEndian)
Array.Reverse(timeBytes);
Array.Copy(timeBytes, 0, Data, 8, 8);

// Now add bundle data
int ix = 16;
foreach (var message in messages)
foreach (var message in messageData)
{
var length = BitConverter.GetBytes(message.Data.Length);
Array.Reverse(length);
var length = BitConverter.GetBytes(message.Length);
if (BitConverter.IsLittleEndian)
Array.Reverse(length);
Array.Copy(length, 0, Data, ix, 4);
ix += 4;

Array.Copy(message.Data, 0, Data, ix, message.Data.Length);
ix += message.Data.Length;
Array.Copy(message, 0, Data, ix, message.Length);
ix += message.Length;
}
}
}
Expand Down
4 changes: 1 addition & 3 deletions VRCFaceTracking/OSC/OSCMain.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace VRCFaceTracking.OSC
Expand Down Expand Up @@ -50,7 +48,7 @@ private void Recv()
// Ignore as this is most likely a timeout exception
return;
}
var newMsg = new OscMessage(buffer);
var newMsg = new OscMessage<object>(buffer);
switch (newMsg.Address)
{
case "/avatar/change":
Expand Down
140 changes: 64 additions & 76 deletions VRCFaceTracking/OSC/OSCMessage.cs
Original file line number Diff line number Diff line change
@@ -1,67 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace VRCFaceTracking.OSC
{
public class OscMessage
public class OscMessage<T>
{
public readonly byte[] Data;
public readonly string Address;
public readonly object Value;
private readonly byte[] _addressBytes;

private readonly byte[] _valueIdentBytes = {44, 0, 0, 0};

public char TypeIdentifier
{
get => (char)_valueIdentBytes[1];
set => _valueIdentBytes[1] = (byte)value;
}

public T Value;
private byte[] _data;

public byte[] SerializationCache;

private OscMessage(string name, char typeIdentifier)
{
// Set constant address
Address = name;

var nameBytes = Encoding.ASCII.GetBytes(name);
nameBytes = nameBytes.EnsureCompliance();

var valueIdentBytes = Encoding.ASCII.GetBytes("," + typeIdentifier);
valueIdentBytes = valueIdentBytes.EnsureCompliance();
_addressBytes = Encoding.ASCII.GetBytes(name).EnsureCompliance();

Data = new byte[nameBytes.Length + valueIdentBytes.Length];
Array.Copy(nameBytes, Data, nameBytes.Length);
Array.Copy(valueIdentBytes, 0, Data, nameBytes.Length, valueIdentBytes.Length);
TypeIdentifier = typeIdentifier;
}

public OscMessage(string name, Type type) : this(name, OscUtils.TypeConversions[type].oscType) {}

public OscMessage(string name, int value) : this(name, 'i')
public void SetValue(T newValue)
{
var valueArr = BitConverter.GetBytes(value);
Array.Reverse(valueArr);
Value = newValue;

if (typeof(T) == typeof(int))
SetInt((int)(object)newValue);

if (typeof(T) == typeof(float) || typeof(T) == typeof(double))
SetFloat((double)(object)newValue);

if (typeof(T) == typeof(bool))
SetBool((bool)(object)newValue);
}

var newFullArr = new byte[Data.Length+valueArr.Length];
Array.Copy(Data, newFullArr, Data.Length);
Array.Copy(valueArr, 0, newFullArr, Data.Length, valueArr.Length);
Data = newFullArr;
private void SetInt(int newValue)
{
_data = BitConverter.GetBytes(newValue);

if (BitConverter.IsLittleEndian)
Array.Reverse(_data);
}

public OscMessage(string name, double value) : this(name, 'f')
private void SetFloat(double newValue)
{
var valueArr = BitConverter.GetBytes((float)value);
Array.Reverse(valueArr);

var newFullArr = new byte[Data.Length+valueArr.Length];
Array.Copy(Data, newFullArr, Data.Length);
Array.Copy(valueArr, 0, newFullArr, Data.Length, valueArr.Length);
Data = newFullArr;
_data = BitConverter.GetBytes(newValue);

if (BitConverter.IsLittleEndian)
Array.Reverse(_data);
}

public OscMessage(string name, bool value) : this(name, value ? 'T' : 'F') {}
private void SetBool(bool newValue) => TypeIdentifier = newValue ? 'T' : 'F';

public OscMessage(string name, char type, byte[] valueBytes) : this(name, type)
public byte[] Serialize()
{
if (valueBytes == null) return;
var newFullArr = new byte[Data.Length+valueBytes.Length];
Array.Copy(Data, newFullArr, Data.Length);
Array.Copy(valueBytes, 0, newFullArr, Data.Length, valueBytes.Length);
Data = newFullArr;
SerializationCache = new byte[_addressBytes.Length + _valueIdentBytes.Length + _data.Length];
Array.Copy(_addressBytes, SerializationCache, _addressBytes.Length);
Array.Copy(_valueIdentBytes, 0, SerializationCache, _addressBytes.Length, _valueIdentBytes.Length);
Array.Copy(_data, 0, SerializationCache, _addressBytes.Length + _valueIdentBytes.Length, _data.Length);
return SerializationCache;
}

public OscMessage(byte[] bytes)
{
int iter = 0;
var iter = 0;

var addressBytes = new List<byte>();
for (; iter < bytes.Length; iter++)
Expand All @@ -77,32 +92,27 @@ public OscMessage(byte[] bytes)
// Increase iter until we find the type identifier
for (; iter < bytes.Length; iter++)
{
if (bytes[iter] == ',')
{
iter++;
break;
}
if (bytes[iter] != ',') continue;

iter++;
break;
}


byte type = bytes[iter];
iter += 2; // Next multiple of 4

switch (type)
switch (bytes[iter])
{
#region Standard OSC-Type/s

case 105: // OSC Type tag: 'i' ; int32
var intBytes = new byte[4];
Array.Copy(bytes, iter, intBytes, 0, 4);
Array.Reverse(intBytes);
Value = BitConverter.ToInt32(intBytes, 0);
Value = (T)(object)BitConverter.ToInt32(intBytes, 0);
break;
case 102: // OSC Type tag: 'f' ; float32
var floatBytes = new byte[4];
Array.Copy(bytes, iter, floatBytes, 0, 4);
Array.Reverse(floatBytes);
Value = BitConverter.ToSingle(floatBytes, 0);
Value = (T)(object)BitConverter.ToSingle(floatBytes, 0);
break;
case 115: // OSC Type tag: 's' ; OSC-string
var stringBytes = new List<byte>();
Expand All @@ -113,50 +123,28 @@ public OscMessage(byte[] bytes)

stringBytes.Add(bytes[iter]);
}
Value = Encoding.ASCII.GetString(stringBytes.ToArray());
break;
case 98: // OSC Type tag: 'b' ; OSC-blob
goto default;

#endregion

#region Non Standard OSC-Type/s
Value = (T)(object)Encoding.ASCII.GetString(stringBytes.ToArray());
break;

case 104: // OSC Type tag: 'h' ; 64 Bit Big-Endian
goto default;
case 116: // OSC Type tag: 't' ; OSC-timetag
goto default;
case 100: // OSC Type tag: 'd' ; 64 Bit Big-Endian
goto default;
case 83: // OSC Type tag: 'S' ; Type represented in OSC-string
goto default;
case 99: // OSC Type tag: 'c' ; 32 bit ASCII
goto default;
case 114: // OSC Type tag: 'r' ; 32 bit RGBA color
goto default;
case 109: // OSC Type tag: 'm' ; 4 bit MIDI. Each byte as: port id, status, data1, data2
goto default;
case 70: // OSC Type tag: 'T' ; Represents true, No extra data
Value = false;
Value = (T)(object)false;
break;
case 84: // OSC Type tag: 'F' ; Represents false, No extra data
Value = true;
Value = (T)(object)true;
break;
case 78: // OSC Type tag: 'N' ; Represents NIL (zero), No extra data
Value = 0;
Value = (T)(object)0;
break;
case 73: // OSC Type tag: 'I' ; Represents Infinitum (endlessly infinite), No extra data. Capping to 1.
Value = 1.0f;
Value = (T)(object)int.MaxValue;
break;
case 91: // OSC Type tag: '[' ; Represents start of an array.
goto default;
case 93: // OSC Type tag: ']' ; Represents end of an array.
goto default;

#endregion

default:
Logger.Error("OSC Type unimplemented: " + type + " for name " + Address);
break;
}
}
Expand Down

0 comments on commit 9298941

Please sign in to comment.