Skip to content

Instantly share code, notes, and snippets.

@jminor
Last active December 5, 2019 08:35
Show Gist options
  • Save jminor/c0b86101e800ff06b2ffc2b81b9527d5 to your computer and use it in GitHub Desktop.
Save jminor/c0b86101e800ff06b2ffc2b81b9527d5 to your computer and use it in GitHub Desktop.
OpenSoundControl for Unity
/*
OSC.cs
Open Sound Control for Unity
LICENSE:
A large portion of this code is based on Makingthings's Make Controller OSC C# implementation:
https://web.archive.org/web/20121021044334/http://www.makingthings.com:80/ref/firmware/html/group___o_s_c.html
Their code is licensed under the Apache License 2.0: http://www.apache.org/licenses/LICENSE-2.0
Another portion of this code is by Thomas O Fredericks.
https://github.com/thomasfredericks/UnityOSC
MIT License
Copyright (c) 2017 Thomas O Fredericks
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.
Further changes by Joshua Minor, released under the same MIT License.
Copyright (c) 2018 Joshua Minor
*/
using System;
using System.IO;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using UnityEngine;
/// \mainpage
/// \section Overview
/// This library is designed
/// to make it as simple as possible for developers to integrate the
/// Open Sound Control into their Unity projects, offering the
/// transparency that makes open source software so rewarding to work with.
/// You can communicate via OSC from your applications
/// over Ethernet, Wi-Fi, or both.
///
/// More details about OSC can be found at http://www.opensoundcontrol.org.
///
/// \section Communication
/// The OSC (Open Sound Control) protocol
/// is an open, transport-independent standard supported by an increasing
/// number of environments and devices.
///
/// \subsection OSCmessages OSC Messages
/// OSC messages are represented by the class OscMessage, and consist of two elements:
/// - An address string (usually like: "/something/whatever").
/// - An optional list of value(s) which can be ints, floats, or strings.
///
/// OSC messages always begin with a slash, and use a slash to delimit each element in the address,
/// so some example OSC address string would look like:
/// \code /subsystem/device/property \endcode
/// \code /world/player/controller/left/position \endcode
/// \code /trapdoor/activate \endcode
///
/// The second part of an OscMessage is a list of values to be sent to the specified address.
/// The OSC types for these values are integers,
/// floats, and strings. The values in this list are simply separated by spaces, and the
/// list can be arbitrarily long.
/// For example, to set the position of servo 1 on your robot, you might send a message which
/// in string form might look like:
/// \code /servo/1/position 512 \endcode
///
/// This addressing scheme allows interactions with a wide variety of systems
/// and properties, and most importantly, accommodates the possibility of future or
/// dynamic systems easily.
///
/// \section sendingdata Sending Data
/// Messages are sent as packets over UDP. Open Sound Control usually allows for TCP as well,
/// but this library only support UDP at the moment. UDP is used because
/// it is fast, low-latency, supports multicast, and simple to connect more than two computers.
///
/// Once you've created
/// a UdpPacket, you can simply call its Send() method, with the OscMessage you'd like to send.
/// There are helper methods to create an OscMessage from a string, or you can pass in the OscMessage itself.
///
/// For example, you might set up your SendStuff() routine to look something like:
/// \code public void SendStuff(string text)
/// {
/// var osc = GetComponent<OSC>();
/// OscMessage message = OSC.StringToOscMessage(text);
/// osc.Send(message);
/// } \endcode
/// If your data is already in the form of an OscMessage, you can call target.Send() directly.
///
/// \section readingdata Reading Data
/// This library provides handlers that will
/// call back a given function when an OscMessage with a given address string is received.
/// Your implementation could look something like:
/// \code// Set the handler in the constructor for a particular address
/// MyConstructor()
/// {
/// // A thread is started when the Osc object is created to read
/// // incoming messages.
/// var osc = GetComponent<OSC>();
/// osc.SetAddressHandler("/analogin/0/value", Ain0Message);
/// }
///
/// // The method you specified as the handler will be called back when a
/// // message with a matching address string comes back from the network.
/// public void AIn0Message(OscMessage oscMessage)
/// {
/// // write the message to a console, for example
/// Debug.Log("AIn0 > " + oscMessage.ToString() );
/// } \endcode
///
/// You could alternatively set a handler for ALL incoming messages by calling
/// the SetAllMessageHandler() method in your setup, instead of SetAddressHandler().
///
///
/// <summary>
/// UdpPacket provides packetIO over UDP
/// </summary>
class UDPPacketIO
{
private UdpClient Sender;
private UdpClient Receiver;
private bool socketsOpen;
private string remoteHostName;
private int remotePort;
private int localPort;
public UDPPacketIO(string hostIP, int remotePort, int localPort)
{
RemoteHostName = hostIP;
RemotePort = remotePort;
LocalPort = localPort;
socketsOpen = false;
}
~UDPPacketIO()
{
if (IsOpen())
{
// Debug.Log("Closing udpclient listener on port " + localPort);
Close();
}
}
/// <summary>
/// Open a UDP socket and create a UDP sender.
/// </summary>
/// <returns>True on success, false on failure.</returns>
public bool Open()
{
try
{
Sender = new UdpClient();
Debug.Log("Opening OSC listener on port " + localPort);
IPEndPoint listenerIp = new IPEndPoint(IPAddress.Any, localPort);
Receiver = new UdpClient(listenerIp);
// Receiver.ExclusiveAddressUse = false;
// Receiver.Connect(listenerIp);
socketsOpen = true;
return true;
}
catch (Exception e)
{
Debug.LogWarning("Unable open OSC listener on UDP port " + localPort);
Debug.LogWarning(e);
}
return false;
}
/// <summary>
/// Close the socket currently listening, and destroy the UDP sender device.
/// </summary>
public void Close()
{
if (Sender != null)
{
Sender.Close();
}
if (Receiver != null)
{
Receiver.Close();
}
Receiver = null;
socketsOpen = false;
}
public void OnDisable()
{
Close();
}
/// <summary>
/// Query the open state of the UDP socket.
/// </summary>
/// <returns>True if open, false if closed.</returns>
public bool IsOpen()
{
return socketsOpen;
}
/// <summary>
/// Send a packet of bytes out via UDP.
/// </summary>
/// <param name="packet">The packet of bytes to be sent.</param>
/// <param name="length">The length of the packet of bytes to be sent.</param>
public void SendPacket(byte[] packet, int length)
{
if (!IsOpen())
{
Open();
}
if (!IsOpen())
{
return;
}
Sender.Send(packet, length, remoteHostName, remotePort);
//Debug.Log("OSC message sent to "+remoteHostName+" port "+remotePort+" len="+length);
}
/// <summary>
/// Receive a packet of bytes over UDP.
/// </summary>
/// <param name="buffer">The buffer to be read into.</param>
/// <returns>The number of bytes read, or 0 on failure.</returns>
public int ReceivePacket(byte[] buffer)
{
if (!IsOpen())
{
Open();
}
if (!IsOpen())
{
return 0;
}
IPEndPoint iep = new IPEndPoint(IPAddress.Any, localPort);
byte[] incoming = Receiver.Receive(ref iep);
int count = Math.Min(buffer.Length, incoming.Length);
System.Array.Copy(incoming, buffer, count);
return count;
}
/// <summary>
/// The address of the destination that you're sending to.
/// </summary>
public string RemoteHostName
{
get
{
return remoteHostName;
}
set
{
remoteHostName = value;
}
}
/// <summary>
/// The remote port that you're sending to.
/// </summary>
public int RemotePort
{
get
{
return remotePort;
}
set
{
remotePort = value;
}
}
/// <summary>
/// The local port you're listening on.
/// </summary>
public int LocalPort
{
get
{
return localPort;
}
set
{
localPort = value;
}
}
}
/// <summary>
/// The OscMessage class is a data structure that represents
/// an OSC address and an arbitrary number of values to be sent to that address.
/// </summary>
public class OscMessage
{
/// <summary>
/// The OSC address of the message as a string.
/// </summary>
public string address;
/// <summary>
/// The list of values to be delivered to the Address.
/// </summary>
public ArrayList values;
public OscMessage()
{
values = new ArrayList();
}
public OscMessage(string addr, params object[] vals)
{
address = addr;
values = new ArrayList(vals);
}
public override string ToString()
{
StringBuilder s = new StringBuilder();
s.Append(address);
foreach (object o in values)
{
s.Append(" ");
s.Append(o.ToString());
}
return s.ToString();
}
public int GetInt(int index)
{
if (values[index].GetType() == typeof(int) || values[index].GetType() == typeof(float))
{
int data = (int)values[index];
// if (Double.IsNaN(data)) return 0;
return data;
}
else
{
Debug.LogWarning("Wrong type (asked for Int, but found " + values[index].GetType() + ")");
return 0;
}
}
public float GetFloat(int index)
{
if (values[index].GetType() == typeof(int))
{
float data = (int)values[index];
if (Double.IsNaN(data)) return 0f;
return data;
}
else if (values[index].GetType() == typeof(float))
{
float data = (float)values[index];
if (Double.IsNaN(data)) return 0f;
return data;
}
else
{
Debug.Log("Wrong type");
return 0f;
}
}
public string GetString(int index)
{
return values[index].ToString();
}
}
public delegate void OscMessageHandler(OscMessage oscM);
/// <summary>
/// The OSC class provides the methods required to send, receive, and manipulate OSC messages.
/// Several of the helper methods are static since a running OSC instance is not required for
/// their use.
///
/// When instantiated, the OSC class opens the PacketIO instance that's handed to it and
/// begins to run a reader thread. The instance is then ready to service Send OscMessage requests
/// and to start supplying OscMessages as received back.
///
/// The OSC class can be called to Send either individual messages or collections of messages
/// in an OSC Bundle. Receiving is done by delegate. There are two ways: either submit a method
/// to receive all incoming messages or submit a method to handle only one particular address.
///
/// Messages can be encoded and decoded from strings via the static methods on this class, or
/// can be hand assembled / disassembled since they're just a string (the address) and a list
/// of other parameters in Object form.
///
/// </summary>
[ExecuteInEditMode]
public class OSC : MonoBehaviour
{
public string myHost = "(detected at runtime)";
public string myIP = "(detected at runtime)";
public int inPort = 8001;
public string outIP = "127.0.0.1";
public int outPort = 8000;
public int messagesReceivedCount;
public int messagesSentCount;
private UDPPacketIO OscPacketIO;
Thread ReadThread;
private bool ReaderRunning;
private OscMessageHandler AllMessageHandler;
Hashtable AddressTable = new Hashtable();
ArrayList messagesReceived;
private object ReadThreadLock = new object();
byte[] buffer;
bool paused = false;
void HandleOnPlayModeChanged()
{
// This method is run whenever the playmode state is changed.
#if UNITY_EDITOR
paused = UnityEditor.EditorApplication.isPaused;
#endif
}
public static string LocalIPAddress()
{
IPHostEntry host;
string localIP = "0.0.0.0";
host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
localIP = ip.ToString();
break;
}
}
return localIP;
}
void OnEnable()
{
myIP = LocalIPAddress(); //Network.player.ipAddress;
myHost = Dns.GetHostName();
Debug.Log("Local host & address: "+myHost+" : "+myIP);
OscPacketIO = new UDPPacketIO(outIP, outPort, inPort);
// AddressTable = new Hashtable();
messagesReceived = new ArrayList();
// TODO: Is this the right size?
buffer = new byte[1000];
ReadThread = new Thread(Read);
ReaderRunning = true;
ReadThread.IsBackground = true;
ReadThread.Start();
#if UNITY_EDITOR
UnityEditor.EditorApplication.playmodeStateChanged = HandleOnPlayModeChanged;
#endif
}
void OnDisable()
{
Close();
}
/// <summary>
/// Add a method to call back on when a message with the specified
/// address is received. The method needs to have the OscMessageHandler signature - i.e.
/// void MyHandler( OscMessage oscM )
/// See also: RemoveAddressHandler(key, handler)
/// </summary>
/// <param name="key">Address string to be matched</param>
/// <param name="ah">The method to call back on.</param>
public void AddAddressHandler(string key, OscMessageHandler handler)
{
ArrayList list = (ArrayList)Hashtable.Synchronized(AddressTable)[key];
if (list == null)
{
list = new ArrayList();
Hashtable.Synchronized(AddressTable).Add(key, list);
}
list.Add(handler);
}
/// <summary>
/// Remove a method call back for the specified message address.
/// See also: AddAddressHandler(key, handler)
/// </summary>
/// <param name="key">Address string to be matched</param>
/// <param name="ah">The method to call back on.</param>
public void RemoveAddressHandler(string key, OscMessageHandler handler)
{
ArrayList list = (ArrayList)Hashtable.Synchronized(AddressTable)[key];
if (list != null)
{
list.Remove(handler);
}
}
void OnApplicationPause(bool pauseStatus)
{
#if !UNITY_EDITOR
paused = pauseStatus;
print ("Application paused : " + pauseStatus);
#endif
}
void Update()
{
if (messagesReceived.Count > 0)
{
//Debug.Log("received " + messagesReceived.Count + " messages");
lock (ReadThreadLock)
{
foreach (OscMessage message in messagesReceived)
{
messagesReceivedCount++;
Debug.Log(message);
debugLog += "RECV: " + message.ToString() + "\n";
if (AllMessageHandler != null)
{
AllMessageHandler(message);
}
ArrayList list = (ArrayList)Hashtable.Synchronized(AddressTable)[message.address];
if (list != null)
{
foreach (OscMessageHandler handler in list)
{
handler(message);
}
}
}
messagesReceived.Clear();
}
}
}
/// <summary>
/// Make sure the PacketExchange is closed.
/// </summary>
/*
~OSC()
{
Cancel();
//Debug.LogError("~Osc");
}
*/
public void Close()
{
if (ReaderRunning)
{
ReaderRunning = false;
ReadThread.Abort();
}
if (OscPacketIO != null && OscPacketIO.IsOpen())
{
OscPacketIO.Close();
OscPacketIO = null;
print("Closed OSC listener");
}
}
/// <summary>
/// Read Thread. Loops waiting for packets. When a packet is received, it is
/// dispatched to any waiting All Message Handler. Also, the address is looked up and
/// any matching handler is called.
/// </summary>
private void Read()
{
try
{
while (ReaderRunning)
{
int length = OscPacketIO.ReceivePacket(buffer);
if (length > 0)
{
lock (ReadThreadLock)
{
// if ( paused == false ) {
ArrayList newMessages = OSC.PacketToOscMessages(buffer, length);
messagesReceived.AddRange(newMessages);
// }
}
}
else
Thread.Sleep(5);
}
}
catch (Exception e)
{
Debug.Log("ThreadAbortException" + e);
}
}
/// <summary>
/// Send an individual OSC message. Internally takes the OscMessage object and
/// serializes it into a byte[] suitable for sending to the PacketIO.
/// </summary>
/// <param name="oscMessage">The OSC Message to send.</param>
public void Send(OscMessage oscMessage)
{
if (outPort == 0) return;
byte[] packet = new byte[1000];
int length = OSC.OscMessageToPacket(oscMessage, packet, 1000);
OscPacketIO.SendPacket(packet, length);
messagesSentCount++;
string msg = "SEND: " + oscMessage.ToString() + "\n";
Debug.Log(msg);
debugLog += msg;
}
/// <summary>
/// Sends a list of OSC Messages. Internally takes the OscMessage objects and
/// serializes them into a byte[] suitable for sending to the PacketExchange.
/// </summary>
/// <param name="oms">The OSC Message to send.</param>
public void Send(ArrayList oms)
{
if (outPort == 0) return;
byte[] packet = new byte[1000];
int length = OSC.OscMessagesToPacket(oms, packet, 1000);
OscPacketIO.SendPacket(packet, length);
messagesSentCount++;
string msg = "SEND: " + oms.Count + " messages in an array.\n";
Debug.Log(msg);
debugLog += msg;
}
/// <summary>
/// Set the method to call back on when any message is received.
/// The method needs to have the OscMessageHandler signature - i.e. void amh( OscMessage oscM )
/// </summary>
/// <param name="amh">The method to call back on.</param>
public void SetAllMessageHandler(OscMessageHandler amh)
{
AllMessageHandler = amh;
}
/// <summary>
/// Creates an OscMessage from a string - extracts the address and determines each of the values.
/// </summary>
/// <param name="message">The string to be turned into an OscMessage</param>
/// <returns>The OscMessage.</returns>
public static OscMessage StringToOscMessage(string message)
{
OscMessage oM = new OscMessage();
Console.WriteLine("Splitting " + message);
string[] ss = message.Split(new char[] { ' ' });
IEnumerator sE = ss.GetEnumerator();
if (sE.MoveNext())
oM.address = (string)sE.Current;
while (sE.MoveNext())
{
string s = (string)sE.Current;
// Console.WriteLine(" <" + s + ">");
if (s.StartsWith("\""))
{
StringBuilder quoted = new StringBuilder();
bool looped = false;
if (s.Length > 1)
quoted.Append(s.Substring(1));
else
looped = true;
while (sE.MoveNext())
{
string a = (string)sE.Current;
// Console.WriteLine(" q:<" + a + ">");
if (looped)
quoted.Append(" ");
if (a.EndsWith("\""))
{
quoted.Append(a.Substring(0, a.Length - 1));
break;
}
else
{
if (a.Length == 0)
quoted.Append(" ");
else
quoted.Append(a);
}
looped = true;
}
oM.values.Add(quoted.ToString());
}
else
{
if (s.Length > 0)
{
try
{
int i = int.Parse(s);
// Console.WriteLine(" i:" + i);
oM.values.Add(i);
}
catch
{
try
{
float f = float.Parse(s);
// Console.WriteLine(" f:" + f);
oM.values.Add(f);
}
catch
{
// Console.WriteLine(" s:" + s);
oM.values.Add(s);
}
}
}
}
}
return oM;
}
/// <summary>
/// Takes a packet (byte[]) and turns it into a list of OscMessages.
/// </summary>
/// <param name="packet">The packet to be parsed.</param>
/// <param name="length">The length of the packet.</param>
/// <returns>An ArrayList of OscMessages.</returns>
public static ArrayList PacketToOscMessages(byte[] packet, int length)
{
ArrayList messages = new ArrayList();
ExtractMessages(messages, packet, 0, length);
return messages;
}
/// <summary>
/// Puts an array of OscMessages into a packet (byte[]).
/// </summary>
/// <param name="messages">An ArrayList of OscMessages.</param>
/// <param name="packet">An array of bytes to be populated with the OscMessages.</param>
/// <param name="length">The size of the array of bytes.</param>
/// <returns>The length of the packet</returns>
public static int OscMessagesToPacket(ArrayList messages, byte[] packet, int length)
{
int index = 0;
if (messages.Count == 1)
index = OscMessageToPacket((OscMessage)messages[0], packet, 0, length);
else
{
// Write the first bundle bit
index = InsertString("#bundle", packet, index, length);
// Write a null timestamp (another 8bytes)
int c = 8;
while ((c--) > 0)
packet[index++]++;
// Now, put each message preceded by it's length
foreach (OscMessage oscM in messages)
{
int lengthIndex = index;
index += 4;
int packetStart = index;
index = OscMessageToPacket(oscM, packet, index, length);
int packetSize = index - packetStart;
packet[lengthIndex++] = (byte)((packetSize >> 24) & 0xFF);
packet[lengthIndex++] = (byte)((packetSize >> 16) & 0xFF);
packet[lengthIndex++] = (byte)((packetSize >> 8) & 0xFF);
packet[lengthIndex++] = (byte)((packetSize) & 0xFF);
}
}
return index;
}
/// <summary>
/// Creates a packet (an array of bytes) from a single OscMessage.
/// </summary>
/// <remarks>A convenience method, not requiring a start index.</remarks>
/// <param name="oscM">The OscMessage to be returned as a packet.</param>
/// <param name="packet">The packet to be populated with the OscMessage.</param>
/// <param name="length">The usable size of the array of bytes.</param>
/// <returns>The length of the packet</returns>
public static int OscMessageToPacket(OscMessage oscM, byte[] packet, int length)
{
return OscMessageToPacket(oscM, packet, 0, length);
}
/// <summary>
/// Creates an array of bytes from a single OscMessage. Used internally.
/// </summary>
/// <remarks>Can specify where in the array of bytes the OscMessage should be put.</remarks>
/// <param name="oscM">The OscMessage to be turned into an array of bytes.</param>
/// <param name="packet">The array of bytes to be populated with the OscMessage.</param>
/// <param name="start">The start index in the packet where the OscMessage should be put.</param>
/// <param name="length">The length of the array of bytes.</param>
/// <returns>The index into the packet after the last OscMessage.</returns>
private static int OscMessageToPacket(OscMessage oscM, byte[] packet, int start, int length)
{
int index = start;
index = InsertString(oscM.address, packet, index, length);
//if (oscM.values.Count > 0)
{
StringBuilder tag = new StringBuilder();
tag.Append(",");
int tagIndex = index;
index += PadSize(2 + oscM.values.Count);
foreach (object o in oscM.values)
{
if (o is int)
{
int i = (int)o;
tag.Append("i");
packet[index++] = (byte)((i >> 24) & 0xFF);
packet[index++] = (byte)((i >> 16) & 0xFF);
packet[index++] = (byte)((i >> 8) & 0xFF);
packet[index++] = (byte)((i) & 0xFF);
}
else
{
if (o is float)
{
float f = (float)o;
tag.Append("f");
byte[] buffer = new byte[4];
MemoryStream ms = new MemoryStream(buffer);
BinaryWriter bw = new BinaryWriter(ms);
bw.Write(f);
packet[index++] = buffer[3];
packet[index++] = buffer[2];
packet[index++] = buffer[1];
packet[index++] = buffer[0];
}
else
{
if (o is string)
{
tag.Append("s");
index = InsertString(o.ToString(), packet, index, length);
}
else
{
tag.Append("?");
}
}
}
}
InsertString(tag.ToString(), packet, tagIndex, length);
}
return index;
}
/// <summary>
/// Receive a raw packet of bytes and extract OscMessages from it. Used internally.
/// </summary>
/// <remarks>The packet may contain a OSC message or a bundle of messages.</remarks>
/// <param name="messages">An ArrayList to be populated with the OscMessages.</param>
/// <param name="packet">The packet of bytes to be parsed.</param>
/// <param name="start">The index of where to start looking in the packet.</param>
/// <param name="length">The length of the packet.</param>
/// <returns>The index after the last OscMessage read.</returns>
private static int ExtractMessages(ArrayList messages, byte[] packet, int start, int length)
{
int index = start;
switch ((char)packet[start])
{
case '/':
index = ExtractMessage(messages, packet, index, length);
break;
case '#':
string bundleString = ExtractString(packet, start, length);
if (bundleString == "#bundle")
{
// skip the "bundle" and the timestamp
index += 16;
while (index < length)
{
int messageSize = (packet[index++] << 24) + (packet[index++] << 16) + (packet[index++] << 8) + packet[index++];
/*int newIndex = */
ExtractMessages(messages, packet, index, length);
index += messageSize;
}
}
break;
}
return index;
}
/// <summary>
/// Extracts a messages from a packet.
/// </summary>
/// <param name="messages">An ArrayList to be populated with the OscMessage.</param>
/// <param name="packet">The packet of bytes to be parsed.</param>
/// <param name="start">The index of where to start looking in the packet.</param>
/// <param name="length">The length of the packet.</param>
/// <returns>The index after the OscMessage is read.</returns>
private static int ExtractMessage(ArrayList messages, byte[] packet, int start, int length)
{
OscMessage oscM = new OscMessage();
oscM.address = ExtractString(packet, start, length);
int index = start + PadSize(oscM.address.Length + 1);
string typeTag = ExtractString(packet, index, length);
index += PadSize(typeTag.Length + 1);
//oscM.values.Add(typeTag);
foreach (char c in typeTag)
{
switch (c)
{
case ',':
break;
case 's':
{
string s = ExtractString(packet, index, length);
index += PadSize(s.Length + 1);
oscM.values.Add(s);
break;
}
case 'i':
{
int i = (packet[index++] << 24) + (packet[index++] << 16) + (packet[index++] << 8) + packet[index++];
oscM.values.Add(i);
break;
}
case 'f':
{
byte[] buffer = new byte[4];
buffer[3] = packet[index++];
buffer[2] = packet[index++];
buffer[1] = packet[index++];
buffer[0] = packet[index++];
MemoryStream ms = new MemoryStream(buffer);
BinaryReader br = new BinaryReader(ms);
float f = br.ReadSingle();
oscM.values.Add(f);
break;
}
}
}
messages.Add(oscM);
return index;
}
/// <summary>
/// Removes a string from a packet. Used internally.
/// </summary>
/// <param name="packet">The packet of bytes to be parsed.</param>
/// <param name="start">The index of where to start looking in the packet.</param>
/// <param name="length">The length of the packet.</param>
/// <returns>The string</returns>
private static string ExtractString(byte[] packet, int start, int length)
{
StringBuilder sb = new StringBuilder();
int index = start;
while (packet[index] != 0 && index < length)
sb.Append((char)packet[index++]);
return sb.ToString();
}
private static string Dump(byte[] packet, int start, int length)
{
StringBuilder sb = new StringBuilder();
int index = start;
while (index < length)
sb.Append(packet[index++] + "|");
return sb.ToString();
}
/// <summary>
/// Inserts a string, correctly padded into a packet. Used internally.
/// </summary>
/// <param name="string">The string to be inserted</param>
/// <param name="packet">The packet of bytes to be parsed.</param>
/// <param name="start">The index of where to start looking in the packet.</param>
/// <param name="length">The length of the packet.</param>
/// <returns>An index to the next byte in the packet after the padded string.</returns>
private static int InsertString(string s, byte[] packet, int start, int length)
{
int index = start;
foreach (char c in s)
{
packet[index++] = (byte)c;
if (index == length)
return index;
}
packet[index++] = 0;
int pad = (s.Length + 1) % 4;
if (pad != 0)
{
pad = 4 - pad;
while (pad-- > 0)
packet[index++] = 0;
}
return index;
}
/// <summary>
/// Takes a length and returns what it would be if padded to the nearest 4 bytes.
/// </summary>
/// <param name="rawSize">Original size</param>
/// <returns>padded size</returns>
private static int PadSize(int rawSize)
{
int pad = rawSize % 4;
if (pad == 0)
return rawSize;
else
return rawSize + (4 - pad);
}
public string debugLog;
void OnGUI()
{
// GUILayout.BeginArea(new Rect(10, Screen.height - 100, Screen.width / 2, 100));
GUILayout.BeginArea(new Rect(10, 10, Screen.width / 2, Screen.height - 10));
GUILayout.Label("OSC\n"+
" HOST: " + myHost + "\n"+
" MY IP: " + myIP + " : PORT " + inPort + "\n"+
" OUT IP: " + outIP + " : PORT " + outPort + "\n"+
" IN / OUT: " + messagesReceivedCount + " / " + messagesSentCount);
GUILayout.TextArea(debugLog);
GUILayout.EndArea();
if (debugLog.Length > 1000) debugLog = debugLog.Substring(debugLog.Length - 1000);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment