summaryrefslogtreecommitdiff
path: root/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2024-12-27 00:56:58 -0500
committerFreya Murphy <freya@freyacat.org>2024-12-27 00:58:02 -0500
commit799e6680d40119dc9c2a9e0b320054a40324bebe (patch)
treedbcd308d59eb6e4f937a5547dd77d9f91d4fec20 /VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs
parentmove to self host (diff)
downloadunityprojects-799e6680d40119dc9c2a9e0b320054a40324bebe.tar.gz
unityprojects-799e6680d40119dc9c2a9e0b320054a40324bebe.tar.bz2
unityprojects-799e6680d40119dc9c2a9e0b320054a40324bebe.zip
VRCSDK3Avatars found!
Diffstat (limited to 'VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs')
-rw-r--r--VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs682
1 files changed, 682 insertions, 0 deletions
diff --git a/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs
new file mode 100644
index 00000000..6b6d8d54
--- /dev/null
+++ b/VRCSDK3Avatars/Assets/Resources/Lyuma/Av3Emulator/Scripts/A3ESimpleOSC.cs
@@ -0,0 +1,682 @@
+/* A3ESimpleOSC for C#, version 0.1
+Copyright (c) 2022 Lyuma <xn.lyuma@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. */
+
+#if UNITY_5_3_OR_NEWER
+#define UNITY
+#endif
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+public class A3ESimpleOSC
+{
+ public enum Impulse {IMPULSE}
+ public struct TimeTag {
+ public int secs;
+ public int nsecs;
+ public override string ToString() {
+ return "" + secs + ":" + nsecs;
+ }
+ #if UNITY
+ public static implicit operator UnityEngine.Vector2Int(TimeTag tt) {
+ return new UnityEngine.Vector2Int { x = tt.secs, y = tt.nsecs };
+ }
+ public static implicit operator TimeTag(UnityEngine.Vector2Int v2) {
+ return new TimeTag { secs = v2.x, nsecs = v2.y };
+ }
+ #endif
+ }
+ public struct OSCColor {
+ public byte r;
+ public byte g;
+ public byte b;
+ public byte a;
+ public override string ToString() {
+ return "OSCColor<" + r + "," + g + "," + b + "," + a + ">";
+ }
+ #if UNITY
+ public static implicit operator UnityEngine.Color(OSCColor c) {
+ return (UnityEngine.Color)new UnityEngine.Color32 { r = c.r, g = c.g, b = c.b, a = c.a };
+ }
+ public static implicit operator OSCColor(UnityEngine.Color c) {
+ UnityEngine.Color32 c32 = (UnityEngine.Color32)c;
+ return new OSCColor { r = c32.r, g = c32.g, b = c32.b, a = c32.a };
+ }
+ public static implicit operator OSCColor(UnityEngine.Color32 c) {
+ return new OSCColor { r = c.r, g = c.g, b = c.b, a = c.a };
+ }
+ public static implicit operator UnityEngine.Color32(OSCColor c) {
+ return new UnityEngine.Color32 { r = c.r, g = c.g, b = c.b, a = c.a };
+ }
+ #endif
+ }
+ public struct OSCMessage {
+ public IPEndPoint sender;
+ public uint bundleId; // 0 if not in a bundle; positive integer if part of a bundle.
+ public string path;
+ public TimeTag time;
+ public string typeTag;
+ public object[] arguments;
+ public override string ToString() {
+ System.Text.StringBuilder ret = new System.Text.StringBuilder();
+ ret.Append("<OSCMessage ");
+ DebugInto(ret);
+ ret.Append(">");
+ return ret.ToString();
+ }
+ public void DebugInto(System.Text.StringBuilder ret, bool dispIPTime=true) {
+ ret.Append(path);
+ if (dispIPTime && sender != null) {
+ ret.Append("; from ");
+ ret.Append(sender.ToString());
+ }
+ if (dispIPTime && (time.secs != 0 || time.nsecs != 0)) {
+ ret.Append(" @");
+ ret.Append(time);
+ }
+ if (dispIPTime) {
+ ret.Append("; type ");
+ ret.Append(typeTag);
+ }
+ ret.Append(":\n");
+ DebugObjectArrayInto(ret, " ", arguments);
+ }
+ }
+
+ public static bool DebugLoggingEnabled = true; // Set to false to handle bad data without logspam.
+ static void CryWolf(string logMsg) {
+ if (DebugLoggingEnabled) {
+ #if UNITY
+ UnityEngine.Debug.LogWarning(logMsg);
+ #else
+ System.Console.WriteLine(logMsg);
+ #endif
+ }
+ }
+
+ public static void DebugObjectArrayInto(System.Text.StringBuilder sb, string indent, object[] args) {
+ int idx = 0;
+ foreach (var arg in args) {
+ sb.Append(indent);
+ sb.Append("[Arg");
+ sb.Append(idx);
+ sb.Append("] = ");
+ switch (arg) {
+ case null:
+ sb.Append("null");
+ break;
+ case object[] subArgs:
+ sb.Append("[\n");
+ DebugObjectArrayInto(sb, indent + " ", subArgs);
+ sb.Append(indent + "]");
+ break;
+ case byte[] subBytes:
+ sb.Append("new byte[] {");
+ bool first = true;
+ foreach (byte b in subBytes) {
+ if (!first) {
+ sb.Append(",");
+ }
+ first = false;
+ sb.Append((int)b);
+ }
+ sb.Append("}");
+ break;
+ case float f:
+ sb.Append(f.ToString("F4"));
+ break;
+ case int i:
+ sb.Append(i.ToString());
+ break;
+ case bool b:
+ sb.Append(b.ToString());
+ break;
+ default:
+ sb.Append("(");
+ sb.Append(arg.GetType().Name);
+ sb.Append(")");
+ sb.Append(arg);
+ break;
+ }
+ sb.Append("\n");
+ idx++;
+ }
+ }
+
+
+ /// Parsing / decoding functions:
+ static object ParseType(char typeTag, byte[] data, ref int offset) {
+ switch(typeTag) {
+ case 'T':
+ return true;
+ case 'F':
+ return false;
+ case 'N':
+ return null;
+ case 'I':
+ return Impulse.IMPULSE;
+ case 'i':
+ int iret = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ return iret;
+ case 'f':
+ byte[] tmp = new byte[4];
+ Array.Copy(data, offset, tmp, 0, 4);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(tmp);
+ }
+ float fret = BitConverter.ToSingle(tmp, 0);
+ offset += 4;
+ return fret;
+ case 't':
+ int secs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ int nanosecs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 4));
+ offset += 8;
+ return new TimeTag { secs = secs, nsecs = nanosecs };
+ case 's':
+ int strend = offset;
+ while (data[strend] != 0) {
+ strend++;
+ }
+ tmp = new byte[strend - offset];
+ Array.Copy(data, offset, tmp, 0, strend - offset);
+ offset = strend;
+ offset = (offset + 4) & ~3;
+ return System.Text.Encoding.UTF8.GetString(tmp);
+ case 'b':
+ int len = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ tmp = new byte[len];
+ Array.Copy(data, offset, tmp, 0, len);
+ offset += len;
+ offset = (offset + 3) & ~3;
+ return tmp;
+ // Non-standard types:
+ case 'r':
+ byte r = data[offset++];
+ byte g = data[offset++];
+ byte b = data[offset++];
+ byte a = data[offset++];
+ return new OSCColor { r = r, g = g, b = b, a = a };
+ case 'h':
+ long lret = IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, offset));
+ offset += 8;
+ return lret;
+ case 'd':
+ byte[] dtmp = new byte[8];
+ Array.Copy(data, offset, dtmp, 0, 8);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(dtmp);
+ }
+ double dret = BitConverter.ToDouble(dtmp, 0);
+ offset += 8;
+ return dret;
+ case 'c':
+ uint cret = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ return cret;
+ default:
+ CryWolf("Unknown type tag " + typeTag + " offset " + offset);
+ break;
+ }
+ return null;
+ }
+
+ static void SerializeTypeInto(byte[] data, ref int offset, object value, char typeTag) {
+ // Debug.Log("Serialize " + value.GetType() + " " + (value) + " as " + typeTag);
+ byte[]tmp;
+ switch (typeTag) {
+ case 'T':
+ case 'F':
+ case 'N':
+ case 'I':
+ break;
+ case 'i':
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)value)), 0, data, offset, 4);
+ offset += 4;
+ break;
+ case 'f':
+ tmp = BitConverter.GetBytes((float)value);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(tmp);
+ }
+ Array.Copy(tmp, 0, data, offset, 4);
+ offset += 4;
+ break;
+ case 't':
+ TimeTag v2;
+ switch (value) {
+ #if UNITY
+ case UnityEngine.Vector2Int i2:
+ v2 = (TimeTag)i2;
+ break;
+ #endif
+ default:
+ v2 = (TimeTag)value;
+ break;
+ }
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)v2.secs)), 0, data, offset, 4);
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)v2.nsecs)), 0, data, offset + 4, 4);
+ offset += 8;
+ break;
+ case 's':
+ tmp = System.Text.Encoding.UTF8.GetBytes((string)value);
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ break;
+ case 'b':
+ tmp = (byte[])value;
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tmp.Length)), 0, data, offset, 4);
+ offset += 4;
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 3) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ break;
+ // Non-standard types:
+ case 'r':
+ OSCColor col;
+ switch (value) {
+ #if UNITY
+ case UnityEngine.Color32 unic32:
+ col = (OSCColor)unic32;
+ break;
+ case UnityEngine.Color unic:
+ col = (OSCColor)unic;
+ break;
+ #endif
+ default:
+ col = (OSCColor)value;
+ break;
+ }
+ data[offset++] = col.r;
+ data[offset++] = col.g;
+ data[offset++] = col.b;
+ data[offset++] = col.a;
+ break;
+ case 'h':
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((long)value)), 0, data, offset, 8);
+ offset += 8;
+ break;
+ case 'd':
+ tmp = BitConverter.GetBytes((double)value);
+ if (BitConverter.IsLittleEndian) {
+ Array.Reverse(tmp);
+ }
+ Array.Copy(tmp, 0, data, offset, 8);
+ offset += 8;
+ break;
+ case 'c':
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(uint)value)), 0, data, offset, 4);
+ offset += 4;
+ break;
+ default:
+ CryWolf("Unexpected type tag to serialize " + typeTag + " offset " + offset);
+ break;
+ }
+ }
+
+ public static void DecodeOSCInto(ConcurrentQueue<OSCMessage> outQueue, byte[] data, int offset, int length, IPEndPoint senderIp=null, uint bundleId=0, TimeTag bundleTimetag=new TimeTag(), uint bundleIdNested=0) {
+ if (offset == 0 && length > 20 && data[offset] == '#' && data[offset + 1] == 'b' && data[offset + 2] == 'u' && data[offset + 3] == 'n' &&
+ data[offset + 4] == 'd' && data[offset + 5] == 'l' && data[offset + 6] == 'e' && data[offset + 7] == 0) {
+ int secs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 8));
+ int nanosecs = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset + 12));
+ bundleTimetag = new TimeTag { secs = secs, nsecs = nanosecs };
+ offset += 16;
+ while (offset < length) {
+ int msglen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, offset));
+ offset += 4;
+ DecodeOSCInto(outQueue, data, offset, msglen, senderIp, bundleId, bundleTimetag, bundleId);
+ offset += msglen;
+ }
+ return;
+ }
+ OSCMessage msg = new OSCMessage();
+ msg.time = bundleTimetag;
+ msg.sender = senderIp;
+ msg.bundleId = bundleIdNested;
+ int strlen = 0;
+ while (data[offset + strlen] != 0) {
+ strlen++;
+ }
+ msg.path = System.Text.Encoding.UTF8.GetString(data, offset, strlen);
+ offset += strlen;
+ offset = (offset + 4) & ~3;
+ while (data[offset] != ',') {
+ offset++;
+ }
+ int typetags = offset;
+ while (data[offset] != 0) {
+ offset++;
+ }
+ msg.typeTag = System.Text.Encoding.ASCII.GetString(data, typetags, offset - typetags);
+ offset = (offset + 4) & ~3;
+ //msg.arguments = new object[msg.typeTag.Length];
+ List<object> topLevelArguments = new List<object>();
+ List<List<object>> nested = new List<List<object>>();
+ nested.Add(topLevelArguments);
+ for (int i = 1; i < msg.typeTag.Length; i++) {
+ // Debug.Log("doing type tag " + msg.typeTag[i] + " offset: " + offset);
+ object obj;
+ switch (msg.typeTag[i]) {
+ case '[':
+ nested.Add(new List<object>());
+ break;
+ case ']':
+ if (nested.Count > 1) {
+ obj = nested[nested.Count - 1].ToArray();
+ nested.RemoveAt(nested.Count - 1);
+ nested[nested.Count - 1].Add(obj);
+ }
+ break;
+ default:
+ obj = ParseType(msg.typeTag[i], data, ref offset);
+ nested[nested.Count - 1].Add(obj);
+ break;
+ }
+ }
+ if (nested.Count != 1) {
+ CryWolf("Invalid nested count (mismatched start and end array in OSC message): " + msg.typeTag);
+ }
+ msg.arguments = topLevelArguments.ToArray();
+ outQueue.Enqueue(msg);
+ }
+
+ static void GenerateOSCTypeTagInto(System.Text.StringBuilder typeTag, object[] packet) {
+ if (typeTag.Length == 0) {
+ typeTag.Append(',');
+ }
+ foreach (object po in packet) {
+ switch(po) {
+ case object[] subArray:
+ typeTag.Append('[');
+ GenerateOSCTypeTagInto(typeTag, subArray);
+ typeTag.Append(']');
+ break;
+ case float f:
+ typeTag.Append('f');
+ break;
+ case int i:
+ typeTag.Append('i');
+ break;
+ #if UNITY
+ case UnityEngine.Color32 ui32:
+ case UnityEngine.Color ui:
+ #endif
+ case OSCColor i:
+ typeTag.Append('r');
+ break;
+ case true:
+ typeTag.Append('T');
+ break;
+ case false:
+ typeTag.Append('F');
+ break;
+ case null:
+ typeTag.Append('N');
+ break;
+ case Impulse.IMPULSE:
+ typeTag.Append('I');
+ break;
+ case string s:
+ typeTag.Append('s');
+ break;
+ case byte[] b:
+ typeTag.Append('b');
+ break;
+ #if UNITY
+ case UnityEngine.Vector2Int i2:
+ #endif
+ case TimeTag tt:
+ typeTag.Append('t');
+ break;
+ case double d:
+ typeTag.Append('d');
+ break;
+ case long l:
+ typeTag.Append('h');
+ break;
+ case uint c: // represent OSC char as uint. confusing???
+ typeTag.Append('c');
+ break;
+ default:
+ CryWolf("Invalid type " + po.GetType() + " at " + typeTag);
+ break;
+ }
+ }
+ }
+
+ public static void EncodeOSCInto(byte[] data, ref int offset, OSCMessage msg, string type_tag_override="") {
+ if (msg.typeTag == null || msg.typeTag.Length == 0) {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ GenerateOSCTypeTagInto(sb, msg.arguments);
+ msg.typeTag = sb.ToString();
+ }
+ byte[] tmp = System.Text.Encoding.UTF8.GetBytes((string)msg.path);
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ tmp = System.Text.Encoding.UTF8.GetBytes((string)msg.typeTag);
+ Array.Copy(tmp, 0, data, offset, tmp.Length);
+ data[tmp.Length + offset] = 0;
+ offset += tmp.Length;
+ for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) {
+ data[offset] = 0;
+ }
+ List<object[]> nested = new List<object[]>();
+ nested.Add(msg.arguments);
+ List<int> nestedIdx = new List<int>();
+ nestedIdx.Add(0);
+ foreach (char ch in msg.typeTag) {
+ switch (ch) {
+ case ',':
+ continue;
+ case '[':
+ object[] newArr = (object[])nested[nested.Count-1][nestedIdx[nestedIdx.Count-1]];
+ nested.Add(newArr);
+ nestedIdx[nestedIdx.Count-1] += 1;
+ nestedIdx.Add(0);
+ break;
+ case ']':
+ nested.RemoveAt(nested.Count-1);
+ nestedIdx.RemoveAt(nestedIdx.Count-1);
+ break;
+ default:
+ SerializeTypeInto(data, ref offset, nested[nested.Count-1][nestedIdx[nestedIdx.Count-1]], ch);
+ nestedIdx[nestedIdx.Count-1] += 1;
+ break;
+ }
+ }
+ }
+
+ public static void EncodeOSCBundleInto(byte[] data, ref int offset, List<OSCMessage> packets, TimeTag tt) {
+ Array.Copy(new byte[]{(byte)'#',(byte)'b',(byte)'u',(byte)'n',(byte)'d',(byte)'l',(byte)'e',0}, 0, data, offset, 8);
+ offset += 8;
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tt.secs)), 0, data, offset, 4);
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)tt.nsecs)), 0, data, offset + 4, 4);
+ offset += 8;
+ foreach (var msg in packets) {
+ int startOffset = offset;
+ offset += 4;
+ EncodeOSCInto(data, ref offset, msg);
+ int endOffset = offset;
+ Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)(endOffset - startOffset - 4))), 0, data, startOffset, 4);
+ }
+ }
+
+ public class UDPThread {
+ UdpClient udpServer;
+ public bool shutdown;
+ public int udp_port;
+ public uint bundleCounter; // just so callers know if it came from a bundle.
+ public ConcurrentQueue<OSCMessage> receivedMessageQueue = new ConcurrentQueue<OSCMessage>();
+
+ public IPEndPoint Open(int udp_port) {
+ udpServer = new UdpClient(udp_port);
+ return (IPEndPoint)udpServer.Client.LocalEndPoint;
+ }
+ public IPEndPoint Open(IPEndPoint local_udp_endpoint) {
+ udpServer = new UdpClient(local_udp_endpoint);
+ return (IPEndPoint)udpServer.Client.LocalEndPoint;
+ }
+ public void Connect(IPEndPoint endPoint) {
+ udpServer.Connect(endPoint);
+ }
+
+ public void mythread(){
+ while (!shutdown) {
+ var incomingIP = new IPEndPoint(IPAddress.Any, 0);
+ try {
+ var data = udpServer.Receive(ref incomingIP);
+ try {
+ if (bundleCounter == 0) {
+ bundleCounter += 1;
+ }
+ DecodeOSCInto(receivedMessageQueue, data, 0, data.Length, incomingIP, bundleCounter);
+ bundleCounter += 1;
+ } catch (Exception e) {
+ CryWolf(e.ToString());
+ }
+ } catch (SocketException) {
+ if (!shutdown) {
+ continue; //throw;
+ }
+ }
+ }
+ }
+ public void Close() {
+ shutdown = true;
+ if (udpServer != null) {
+ udpServer.Close();
+ }
+ }
+ public void SendBytes(byte[] buffer, int length, IPEndPoint endPoint) {
+ if (endPoint == null) {
+ udpServer.Send(buffer, length);
+ } else {
+ udpServer.Send(buffer, length, endPoint);
+ }
+ }
+ }
+
+ Thread runningThread;
+ UDPThread udpThreadState;
+ byte[]scratchSpace = new byte[8192];
+ IPEndPoint unconnectedEndpoint = null;
+
+ // Two methods for establishing send relationship:
+ // I. A connected UDP socket cannot receive messages from other hosts.
+ public void Connect(IPEndPoint endPoint) {
+ udpThreadState.Connect(endPoint);
+ }
+ // II. This can be called freely for every datagram and only affects data sent.
+ public void SetUnconnectedEndpoint(IPEndPoint endPoint) {
+ unconnectedEndpoint = endPoint;
+ }
+
+ // Two methods for creating a socket (with or without bound local ip/port)
+ // I. Open a socket only. Avoids creating a thread.
+ public IPEndPoint OpenSendOnlyClient(int udp_port=0) {
+ return OpenSendOnlyClient(new IPEndPoint(IPAddress.Any, udp_port));
+ }
+ public IPEndPoint OpenSendOnlyClient(IPEndPoint local_udp_endpoint) {
+ runningThread = null;
+ udpThreadState = new UDPThread();
+ return udpThreadState.Open(local_udp_endpoint);
+ }
+
+ // II. Open a socket, and creates a thread for receiving.
+ public IPEndPoint OpenClient(int udp_port=0) {
+ return OpenClient(new IPEndPoint(IPAddress.Any, udp_port));
+ }
+ public IPEndPoint OpenClient(IPEndPoint local_udp_endpoint) {
+ if (udpThreadState != null) {
+ StopClient();
+ udpThreadState = null;
+ }
+ udpThreadState = new UDPThread();
+ IPEndPoint localEndPoint = udpThreadState.Open(local_udp_endpoint);
+ runningThread = new Thread(new ThreadStart(udpThreadState.mythread));
+ runningThread.Start();
+ return localEndPoint;
+ }
+
+ // Call this to close the socket, and join the thread if any.
+ public void StopClient() {
+ if (udpThreadState != null && !udpThreadState.shutdown) {
+ udpThreadState.Close();
+ if (runningThread != null) {
+ runningThread.Join(5000);
+ }
+ }
+ }
+
+ // Read data waiting in the buffer.
+ public void GetIncomingOSC(List<OSCMessage> incomingMessages) {
+ OSCMessage msg;
+ if (udpThreadState != null && runningThread != null) {
+ while (udpThreadState.receivedMessageQueue.TryDequeue(out msg)) {
+ incomingMessages.Add(msg);
+ }
+ }
+ }
+
+ // Send a single OSCMessage, not bundled.
+ public void SendOSCPacket(OSCMessage msg, byte[] buffer=null) {
+ if (buffer == null) {
+ buffer = scratchSpace;
+ }
+ int encodedLength = 0;
+ EncodeOSCInto(buffer, ref encodedLength, msg);
+ udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint);
+ }
+
+ // Send a single OSCMessage as a bundle.
+ public void SendOSCBundle(List<OSCMessage> messages, TimeTag ts, byte[] buffer=null) {
+ if (messages.Count == 0) {
+ CryWolf("Attempt to send bundle with no messages!");
+ }
+ if (buffer == null) {
+ buffer = scratchSpace;
+ }
+ int encodedLength = 0;
+ EncodeOSCBundleInto(buffer, ref encodedLength, messages, ts);
+ udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint);
+ }
+ public void SendRaw(byte[] buffer, int encodedLength) {
+ udpThreadState.SendBytes(buffer, encodedLength, unconnectedEndpoint);
+ }
+
+ // udpServer.Send(new byte[] { 1 }, 1); // if data is received reply letting the client know that we got his data
+} \ No newline at end of file