diff --git a/VRCFaceTracking/MainStandalone.cs b/VRCFaceTracking/MainStandalone.cs index ff7881bd..1742b130 100644 --- a/VRCFaceTracking/MainStandalone.cs +++ b/VRCFaceTracking/MainStandalone.cs @@ -31,7 +31,7 @@ public static class MainStandalone public static OscMain OscMain; public static UnifiedConfig unifiedConfig = new UnifiedConfig(); - private static List ConstructMessages(IEnumerable parameters) => + private static List ConstructMessages(IEnumerable parameters) => parameters.Where(p => p.NeedsSend).Select(param => { param.NeedsSend = false; diff --git a/VRCFaceTracking/OSC/OSCBundle.cs b/VRCFaceTracking/OSC/OSCBundle.cs index 4ad777da..4aedead5 100644 --- a/VRCFaceTracking/OSC/OSCBundle.cs +++ b/VRCFaceTracking/OSC/OSCBundle.cs @@ -8,28 +8,43 @@ public class OscBundle { public readonly byte[] Data; - public OscBundle(IEnumerable messages) + public OscBundle(OscMessage[] 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; } } } diff --git a/VRCFaceTracking/OSC/OSCMain.cs b/VRCFaceTracking/OSC/OSCMain.cs index 2ac93706..56d27de3 100644 --- a/VRCFaceTracking/OSC/OSCMain.cs +++ b/VRCFaceTracking/OSC/OSCMain.cs @@ -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 @@ -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(buffer); switch (newMsg.Address) { case "/avatar/change": diff --git a/VRCFaceTracking/OSC/OSCMessage.cs b/VRCFaceTracking/OSC/OSCMessage.cs index 1b05c796..98179fc3 100644 --- a/VRCFaceTracking/OSC/OSCMessage.cs +++ b/VRCFaceTracking/OSC/OSCMessage.cs @@ -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 { - 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(); for (; iter < bytes.Length; iter++) @@ -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(); @@ -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; } }