diff --git a/qController.Standard/Classes/Cell/QInstanceCell.cs b/qController.Standard/Classes/Cell/QInstanceCell.cs index e61f343..4b5c553 100644 --- a/qController.Standard/Classes/Cell/QInstanceCell.cs +++ b/qController.Standard/Classes/Cell/QInstanceCell.cs @@ -103,7 +103,7 @@ namespace qController { App.iNav.PushModalAsync(new NavigationPage(new ControlPage(nameLabel.Text,addressLabel.Text))); - Console.WriteLine("Connect To" + nameLabel.Text + " Pressed"); + Console.WriteLine("Connect To " + nameLabel.Text + " Pressed"); } } diff --git a/qController.Standard/Classes/Communication/QController.cs b/qController.Standard/Classes/Communication/QController.cs index 5d60c6e..b2cd5c9 100644 --- a/qController.Standard/Classes/Communication/QController.cs +++ b/qController.Standard/Classes/Communication/QController.cs @@ -24,7 +24,7 @@ namespace qController qUpdater.Start(); qParser.SelectedCueUpdated += this.OnSelectedCueUpdated; - qReceiver.PacketReceived += qParser.OnPacketReceived; + qReceiver.MessageReceived += qParser.OnMessageReceived; } @@ -32,6 +32,10 @@ namespace qController qSender.sendString(cmd); } + public void sendCommandUDP(string cmd) + { + qSender.sendStringUDP(cmd); + } public void updateCueValue(QCue cue, string property, object newValue){ // qSender.sendArgs("/cue_id/"+cue.uniqueID+"/"+property, newValue); } diff --git a/qController.Standard/Classes/Communication/QParser.cs b/qController.Standard/Classes/Communication/QParser.cs index 15dddb7..b0eb13f 100644 --- a/qController.Standard/Classes/Communication/QParser.cs +++ b/qController.Standard/Classes/Communication/QParser.cs @@ -1,5 +1,5 @@ using System; -using Rug.Osc; +using SharpOSC; using Newtonsoft.Json.Linq; namespace qController @@ -35,8 +35,7 @@ namespace qController public event SelectedCueUpdatedHandler SelectedCueUpdated; public event AudioLevelsUpdatedHandler AudioLevelsUpdated; - public void ParsePacket(OscPacket packet){ - OscMessage msg = (OscMessage)packet; + public void ParseMessage(OscMessage msg){ if (msg.Address.Contains("selectedCues")) ParseSelectedCueInfo(msg); else if (msg.Address.Contains("notes")) @@ -46,9 +45,9 @@ namespace qController } - public void OnPacketReceived(object source, PacketEventArgs args){ + public void OnMessageReceived(object source, MessageEventArgs args){ //Console.WriteLine("Packet Received"); - ParsePacket(args.Packet); + ParseMessage(args.Message); } public void ParseSelectedCueInfo(OscMessage msg){ @@ -67,13 +66,14 @@ namespace qController public void ParseNoteInfo(OscMessage msg){ //JToken cueInfo = OSC2JSON(msg); } + public void ParseLevelInfo(OscMessage msg){ JToken levels = OSC2JSON(msg)[0]; OnAudioLevelsUpdated(levels); } public JToken OSC2JSON(OscMessage Msg){ - JObject json = JObject.Parse(Msg.ToArray()[0].ToString()); + JObject json = JObject.Parse(Msg.Arguments.ToArray()[0].ToString()); return json.GetValue("data"); } diff --git a/qController.Standard/Classes/Communication/QReceiver.cs b/qController.Standard/Classes/Communication/QReceiver.cs index 289ccce..56de886 100644 --- a/qController.Standard/Classes/Communication/QReceiver.cs +++ b/qController.Standard/Classes/Communication/QReceiver.cs @@ -1,12 +1,12 @@ using System; -using Rug.Osc; +using SharpOSC; using System.Net; using System.Threading; namespace qController { - public class PacketEventArgs : EventArgs{ - public OscPacket Packet { + public class MessageEventArgs : EventArgs{ + public OscMessage Message { get; set; } @@ -14,21 +14,20 @@ namespace qController public class QReceiver { - static OscReceiver qReceiver; static Thread thread; private QController owner; + static UDPListener udpListener; + public delegate void MessageReceivedHandler(object source, MessageEventArgs args); - public delegate void PacketReceivedHandler(object source, PacketEventArgs args); - - public event PacketReceivedHandler PacketReceived; + public event MessageReceivedHandler MessageReceived; public QReceiver(int port, QController ctrl) { - qReceiver = new OscReceiver(port); + owner = ctrl; + + udpListener = new UDPListener(port); thread = new Thread(new ThreadStart(ListenLoop)); - owner = ctrl; - qReceiver.Connect(); thread.Start(); } @@ -36,45 +35,32 @@ namespace qController public void ListenLoop() { Console.WriteLine("Loop Started"); - try - { - while (qReceiver.State != OscSocketState.Closed) - { - // if we are in a state to recieve - if (qReceiver.State == OscSocketState.Connected) - { - // get the next message - // this will block until one arrives or the socket is closed - OscPacket packet = qReceiver.Receive(); - OnPacketReceived(packet); - // Write the packet to the console + while (true) + { + OscMessage messageReceived = null; + while(messageReceived == null) + { + messageReceived = (OscMessage)udpListener.Receive(); + Thread.Sleep(1); + } + Console.WriteLine("Received a message!"); + Console.WriteLine(messageReceived.Address); + OnMessageReceived(messageReceived); + } - // DO SOMETHING HERE! - } - } - } - catch (Exception ex) - { - // if the socket was connected when this happens - // then tell the user - if (qReceiver.State == OscSocketState.Connected) - { - Console.WriteLine("Exception in listen loop"); - Console.WriteLine(ex.Message); - } - } Console.WriteLine("Loop Ended"); - } - public void Close(){ - qReceiver.Close(); - thread.Abort(); } - protected virtual void OnPacketReceived(OscPacket pkt){ - if (PacketReceived != null) - PacketReceived(this, new PacketEventArgs(){Packet = pkt}); + public void Close(){ + thread.Abort(); + udpListener.Close(); + } + + protected virtual void OnMessageReceived(OscMessage msg){ + if (MessageReceived != null) + MessageReceived(this, new MessageEventArgs(){Message = msg}); } } } diff --git a/qController.Standard/Classes/Communication/QSender.cs b/qController.Standard/Classes/Communication/QSender.cs index 3d69d1d..60dd852 100644 --- a/qController.Standard/Classes/Communication/QSender.cs +++ b/qController.Standard/Classes/Communication/QSender.cs @@ -1,30 +1,34 @@ using System; using System.Net; -using Rug.Osc; +using SharpOSC; namespace qController { public class QSender { - OscSender qSender; + TCPSender tcpSender; + UDPSender udpSender; public QSender(string address, int port) { - IPAddress ipAddress = IPAddress.Parse(address); - - qSender = new OscSender(ipAddress, port); + tcpSender = new TCPSender(address, port); + udpSender = new UDPSender(address, port); } public void sendString(string address) { - qSender.Connect(); - qSender.Send(new OscMessage(address,new object[]{})); - qSender.Close(); + tcpSender.Send(new OscMessage(address)); } - public void sendArgs(string msg, object args){ - qSender.Connect(); - qSender.Send(new OscMessage(msg,args)); - qSender.Close(); + public void sendStringUDP(string address) + { + udpSender.Send(new OscMessage(address)); + } + public void sendArgs(string address, object args){ + tcpSender.Send(new OscMessage(address, args)); + } + public void sendArgsUDP(string address, object args) + { + udpSender.Send(new OscMessage(address, args)); } } diff --git a/qController.Standard/Classes/Communication/QUpdater.cs b/qController.Standard/Classes/Communication/QUpdater.cs index 740ecb6..2bb02a5 100644 --- a/qController.Standard/Classes/Communication/QUpdater.cs +++ b/qController.Standard/Classes/Communication/QUpdater.cs @@ -14,8 +14,8 @@ namespace qController public void Start(){ active = true; Device.StartTimer(TimeSpan.FromSeconds(0.01), () => { - qController.sendCommand("/selectedCues"); - qController.sendCommand("/cue/selected/levels"); + qController.sendCommandUDP("/selectedCues"); + qController.sendCommandUDP("/cue/selected/levels"); return active; }); } diff --git a/qController.Standard/Classes/Communication/SharpOSC/Extensions.cs b/qController.Standard/Classes/Communication/SharpOSC/Extensions.cs new file mode 100755 index 0000000..b469fe7 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/Extensions.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + internal static class Extensions + { + public static int FirstIndexAfter(this IEnumerable items, int start, Func predicate) + { + if (items == null) throw new ArgumentNullException("items"); + if (predicate == null) throw new ArgumentNullException("predicate"); + if (start >= items.Count()) throw new ArgumentOutOfRangeException("start"); + + int retVal = 0; + foreach (var item in items) + { + if (retVal >= start && predicate(item)) return retVal; + retVal++; + } + return -1; + } + + public static List> Split(this IEnumerable data, Func predicate) + { + var output = new List>(); + var curr = new List(); + output.Add(curr); + foreach (var x in data) + { + if (predicate(x)) + { + curr = new List(); + output.Add(curr); + } + else + curr.Add(x); + } + + return output; + } + + + + public static T[] SubArray(this T[] data, int index, int length) + { + T[] result = new T[length]; + Array.Copy(data, index, result, 0, length); + return result; + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/Midi.cs b/qController.Standard/Classes/Communication/SharpOSC/Midi.cs new file mode 100755 index 0000000..d494112 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/Midi.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public struct Midi + { + public byte Port; + public byte Status; + public byte Data1; + public byte Data2; + + public Midi(byte port, byte status, byte data1, byte data2) + { + this.Port = port; + this.Status = status; + this.Data1 = data1; + this.Data2 = data2; + } + + public override bool Equals(System.Object obj) + { + if (obj.GetType() == typeof(Midi)) + { + if (this.Port == ((Midi)obj).Port && this.Status == ((Midi)obj).Status && this.Data1 == ((Midi)obj).Data1 && this.Data2 == ((Midi)obj).Data2) + return true; + else + return false; + } + else if (obj.GetType() == typeof(byte[])) + { + if (this.Port == ((byte[])obj)[0] && this.Status == ((byte[])obj)[1] && this.Data1 == ((byte[])obj)[2] && this.Data2 == ((byte[])obj)[3]) + return true; + else + return false; + } + else + return false; + } + + public static bool operator ==(Midi a, Midi b) + { + if (a.Equals(b)) + return true; + else + return false; + } + + public static bool operator !=(Midi a, Midi b) + { + if (!a.Equals(b)) + return true; + else + return false; + } + + public override int GetHashCode() + { + return (Port << 24) + (Status << 16) + (Data1 << 8) + (Data2); + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/OscBundle.cs b/qController.Standard/Classes/Communication/SharpOSC/OscBundle.cs new file mode 100755 index 0000000..8d6a769 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/OscBundle.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public class OscBundle : OscPacket + { + Timetag _timetag; + + public UInt64 Timetag + { + get { return _timetag.Tag; } + set { _timetag.Tag = value; } + } + + public DateTime Timestamp + { + get { return _timetag.Timestamp; } + set { _timetag.Timestamp = value; } + } + + public List Messages; + + public OscBundle(UInt64 timetag, params OscMessage[] args) + { + _timetag = new Timetag(timetag); + Messages = new List(); + Messages.AddRange(args); + } + + public override byte[] GetBytes() + { + string bundle = "#bundle"; + int bundleTagLen = Utils.AlignedStringLength(bundle); + byte[] tag = setULong(_timetag.Tag); + + List outMessages = new List(); + foreach (OscMessage msg in Messages) + { + outMessages.Add(msg.GetBytes()); + } + + int len = bundleTagLen + tag.Length + outMessages.Sum(x => x.Length + 4); + + int i = 0; + byte[] output = new byte[len]; + Encoding.ASCII.GetBytes(bundle).CopyTo(output, i); + i += bundleTagLen; + tag.CopyTo(output, i); + i += tag.Length; + + foreach (byte[] msg in outMessages) + { + var size = setInt(msg.Length); + size.CopyTo(output, i); + i += size.Length; + + msg.CopyTo(output, i); + i += msg.Length; // msg size is always a multiple of 4 + } + + return output; + } + + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/OscMessage.cs b/qController.Standard/Classes/Communication/SharpOSC/OscMessage.cs new file mode 100755 index 0000000..fe166f8 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/OscMessage.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public class OscMessage : OscPacket + { + public string Address; + public List Arguments; + + public OscMessage(string address, params object[] args) + { + this.Address = address; + Arguments = new List(); + Arguments.AddRange(args); + } + + public override byte[] GetBytes() + { + List parts = new List(); + + List currentList = Arguments; + int ArgumentsIndex = 0; + + string typeString = ","; + int i = 0; + while (i < currentList.Count) + { + var arg = currentList[i]; + + string type = (arg != null) ? arg.GetType().ToString() : "null"; + switch (type) + { + case "System.Int32": + typeString += "i"; + parts.Add(setInt((int)arg)); + break; + case "System.Single": + if (float.IsPositiveInfinity((float)arg)) + { + typeString += "I"; + } + else + { + typeString += "f"; + parts.Add(setFloat((float)arg)); + } + break; + case "System.String": + typeString += "s"; + parts.Add(setString((string)arg)); + break; + case "System.Byte[]": + typeString += "b"; + parts.Add(setBlob((byte[])arg)); + break; + case "System.Int64": + typeString += "h"; + parts.Add(setLong((Int64)arg)); + break; + case "System.UInt64": + typeString += "t"; + parts.Add(setULong((UInt64)arg)); + break; + case "SharpOSC.Timetag": + typeString += "t"; + parts.Add(setULong(((Timetag)arg).Tag)); + break; + case "System.Double": + if (Double.IsPositiveInfinity((double)arg)) + { + typeString += "I"; + } + else + { + typeString += "d"; + parts.Add(setDouble((double)arg)); + } + break; + + case "SharpOSC.Symbol": + typeString += "S"; + parts.Add(setString(((Symbol)arg).Value)); + break; + + case "System.Char": + typeString += "c"; + parts.Add(setChar((char)arg)); + break; + case "SharpOSC.RGBA": + typeString += "r"; + parts.Add(setRGBA((RGBA)arg)); + break; + case "SharpOSC.Midi": + typeString += "m"; + parts.Add(setMidi((Midi)arg)); + break; + case "System.Boolean": + typeString += ((bool)arg) ? "T" : "F"; + break; + case "null": + typeString += "N"; + break; + + // This part handles arrays. It points currentList to the array and resets i + // The array is processed like normal and when it is finished we replace + // currentList back with Arguments and continue from where we left off + case "System.Object[]": + case "System.Collections.Generic.List`1[System.Object]": + if(arg.GetType() == typeof(object[])) + arg = ((object[])arg).ToList(); + + if (Arguments != currentList) + throw new Exception("Nested Arrays are not supported"); + typeString += "["; + currentList = (List)arg; + ArgumentsIndex = i; + i = 0; + continue; + + default: + throw new Exception("Unable to transmit values of type " + type); + } + + i++; + if (currentList != Arguments && i == currentList.Count) + { + // End of array, go back to main Argument list + typeString += "]"; + currentList = Arguments; + i = ArgumentsIndex+1; + } + } + + int addressLen = (Address.Length == 0 || Address == null ) ? 0 : Utils.AlignedStringLength(Address); + int typeLen = Utils.AlignedStringLength(typeString); + + int total = addressLen + typeLen + parts.Sum(x => x.Length); + + byte[] output = new byte[total]; + i = 0; + + Encoding.ASCII.GetBytes(Address).CopyTo(output, i); + i += addressLen; + + Encoding.ASCII.GetBytes(typeString).CopyTo(output, i); + i += typeLen; + + foreach (byte[] part in parts) + { + part.CopyTo(output, i); + i += part.Length; + } + + return output; + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/OscPacket.cs b/qController.Standard/Classes/Communication/SharpOSC/OscPacket.cs new file mode 100755 index 0000000..fdecce4 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/OscPacket.cs @@ -0,0 +1,485 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public abstract class OscPacket + { + public static OscPacket GetPacket(byte[] OscData) + { + if (OscData[0] == '#') + return parseBundle(OscData); + else + return parseMessage(OscData); + } + + public abstract byte[] GetBytes(); + + #region Parse OSC packages + + /// + /// Takes in an OSC bundle package in byte form and parses it into a more usable OscBundle object + /// + /// + /// Message containing various arguments and an address + private static OscMessage parseMessage(byte[] msg) + { + int index = 0; + + string address = null; + char[] types = new char[0]; + List arguments = new List(); + List mainArray = arguments; // used as a reference when we are parsing arrays to get the main array back + + // Get address + address = getAddress(msg, index); + index += msg.FirstIndexAfter(address.Length, x => x == ','); + + if (index % 4 != 0) + throw new Exception("Misaligned OSC Packet data. Address string is not padded correctly and does not align to 4 byte interval"); + + // Get type tags + types = getTypes(msg, index); + index += types.Length; + + while (index % 4 != 0) + index++; + + bool commaParsed = false; + + foreach (char type in types) + { + // skip leading comma + if (type == ',' && !commaParsed) + { + commaParsed = true; + continue; + } + + switch(type) + { + case ('\0'): + break; + + case('i'): + int intVal = getInt(msg, index); + arguments.Add(intVal); + index += 4; + break; + + case('f'): + float floatVal = getFloat(msg, index); + arguments.Add(floatVal); + index += 4; + break; + + case('s'): + string stringVal = getString(msg, index); + arguments.Add(stringVal); + index += stringVal.Length; + break; + + case('b'): + byte[] blob = getBlob(msg, index); + arguments.Add(blob); + index += 4 + blob.Length; + break; + + case ('h'): + Int64 hval = getLong(msg, index); + arguments.Add(hval); + index += 8; + break; + + case ('t'): + UInt64 sval = getULong(msg, index); + arguments.Add(new Timetag(sval)); + index += 8; + break; + + case ('d'): + double dval = getDouble(msg, index); + arguments.Add(dval); + index += 8; + break; + + case ('S'): + string SymbolVal = getString(msg, index); + arguments.Add(new Symbol(SymbolVal)); + index += SymbolVal.Length; + break; + + case ('c'): + char cval = getChar(msg, index); + arguments.Add(cval); + index += 4; + break; + + case ('r'): + RGBA rgbaval = getRGBA(msg, index); + arguments.Add(rgbaval); + index += 4; + break; + + case ('m'): + Midi midival = getMidi(msg, index); + arguments.Add(midival); + index += 4; + break; + + case ('T'): + arguments.Add(true); + break; + + case ('F'): + arguments.Add(false); + break; + + case ('N'): + arguments.Add(null); + break; + + case ('I'): + arguments.Add(double.PositiveInfinity); + break; + + case ('['): + if (arguments != mainArray) + throw new Exception("SharopOSC does not support nested arrays"); + arguments = new List(); // make arguments point to a new object array + break; + + case (']'): + mainArray.Add(arguments); // add the array to the main array + arguments = mainArray; // make arguments point back to the main array + break; + + default: + throw new Exception("OSC type tag '" + type + "' is unknown."); + } + + while (index % 4 != 0) + index++; + } + + return new OscMessage(address, arguments.ToArray()); + } + + /// + /// Takes in an OSC bundle package in byte form and parses it into a more usable OscBundle object + /// + /// + /// Bundle containing elements and a timetag + private static OscBundle parseBundle(byte[] bundle) + { + UInt64 timetag; + List messages = new List(); + + int index = 0; + + var bundleTag = Encoding.ASCII.GetString(bundle.SubArray(0, 8)); + index += 8; + + timetag = getULong(bundle, index); + index += 8; + + if (bundleTag != "#bundle\0") + throw new Exception("Not a bundle"); + + while (index < bundle.Length) + { + int size = getInt(bundle, index); + index += 4; + + byte[] messageBytes = bundle.SubArray(index, size); + var message = parseMessage(messageBytes); + + messages.Add(message); + + index += size; + while (index % 4 != 0) + index++; + } + + OscBundle output = new OscBundle(timetag, messages.ToArray()); + return output; + } + + #endregion + + #region Get arguments from byte array + + private static string getAddress(byte[] msg, int index) + { + int i = index; + string address = ""; + for (; i < msg.Length; i += 4) + { + if (msg[i] == ',') + { + if (i == 0) + return ""; + + address = Encoding.ASCII.GetString(msg.SubArray(index, i - 1)); + break; + } + } + + if (i >= msg.Length && address == null) + throw new Exception("no comma found"); + + return address.Replace("\0", ""); + } + + private static char[] getTypes(byte[] msg, int index) + { + int i = index + 4; + char[] types = null; + + for (; i < msg.Length; i += 4) + { + if (msg[i - 1] == 0) + { + types = Encoding.ASCII.GetChars(msg.SubArray(index, i - index)); + break; + } + } + + if (i >= msg.Length && types == null) + throw new Exception("No null terminator after type string"); + + return types; + } + + private static int getInt(byte[] msg, int index) + { + int val = (msg[index] << 24) + (msg[index + 1] << 16) + (msg[index + 2] << 8) + (msg[index + 3] << 0); + return val; + } + + private static float getFloat(byte[] msg, int index) + { + byte[] reversed = new byte[4]; + reversed[3] = msg[index]; + reversed[2] = msg[index+1]; + reversed[1] = msg[index+2]; + reversed[0] = msg[index + 3]; + float val = System.BitConverter.ToSingle(reversed, 0); + return val; + } + + private static string getString(byte[] msg, int index) + { + string output = null; + int i = index + 4; + for (; (i-1) < msg.Length; i += 4) + { + if (msg[i - 1] == 0) + { + output = Encoding.ASCII.GetString(msg.SubArray(index, i - index)); + break; + } + } + + if (i >= msg.Length && output == null) + throw new Exception("No null terminator after type string"); + + return output.Replace("\0", ""); + } + + private static byte[] getBlob(byte[] msg, int index) + { + int size = getInt(msg, index); + return msg.SubArray(index + 4, size); + } + + private static UInt64 getULong(byte[] msg, int index) + { + UInt64 val = ((UInt64)msg[index] << 56) + ((UInt64)msg[index + 1] << 48) + ((UInt64)msg[index + 2] << 40) + ((UInt64)msg[index + 3] << 32) + + ((UInt64)msg[index + 4] << 24) + ((UInt64)msg[index + 5] << 16) + ((UInt64)msg[index + 6] << 8) + ((UInt64)msg[index + 7] << 0); + return val; + } + + private static Int64 getLong(byte[] msg, int index) + { + byte[] var = new byte[8]; + var[7] = msg[index]; + var[6] = msg[index+1]; + var[5] = msg[index+2]; + var[4] = msg[index+3]; + var[3] = msg[index+4]; + var[2] = msg[index+5]; + var[1] = msg[index+6]; + var[0] = msg[index+7]; + + Int64 val = BitConverter.ToInt64(var, 0); + return val; + } + + private static double getDouble(byte[] msg, int index) + { + byte[] var = new byte[8]; + var[7] = msg[index]; + var[6] = msg[index + 1]; + var[5] = msg[index + 2]; + var[4] = msg[index + 3]; + var[3] = msg[index + 4]; + var[2] = msg[index + 5]; + var[1] = msg[index + 6]; + var[0] = msg[index + 7]; + + double val = BitConverter.ToDouble(var, 0); + return val; + } + + private static char getChar(byte[] msg, int index) + { + return (char)msg[index + 3]; + } + + private static RGBA getRGBA(byte[] msg, int index) + { + return new RGBA(msg[index], msg[index + 1], msg[index + 2], msg[index + 3]); + } + + private static Midi getMidi(byte[] msg, int index) + { + return new Midi(msg[index], msg[index + 1], msg[index + 2], msg[index + 3]); + } + + #endregion + + #region Create byte arrays for arguments + + protected static byte[] setInt(int value) + { + byte[] msg = new byte[4]; + + var bytes = BitConverter.GetBytes(value); + msg[0] = bytes[3]; + msg[1] = bytes[2]; + msg[2] = bytes[1]; + msg[3] = bytes[0]; + + return msg; + } + + protected static byte[] setFloat(float value) + { + byte[] msg = new byte[4]; + + var bytes = BitConverter.GetBytes(value); + msg[0] = bytes[3]; + msg[1] = bytes[2]; + msg[2] = bytes[1]; + msg[3] = bytes[0]; + + return msg; + } + + protected static byte[] setString(string value) + { + int len = value.Length + (4 - value.Length % 4); + if (len <= value.Length) len = len + 4; + + byte[] msg = new byte[len]; + + var bytes = Encoding.ASCII.GetBytes(value); + bytes.CopyTo(msg, 0); + + return msg; + } + + protected static byte[] setBlob(byte[] value) + { + int len = value.Length + 4; + len = len + (4 - len % 4); + + byte[] msg = new byte[len]; + byte[] size = setInt(value.Length); + size.CopyTo(msg, 0); + value.CopyTo(msg, 4); + return msg; + } + + protected static byte[] setLong(Int64 value) + { + byte[] rev = BitConverter.GetBytes(value); + byte[] output = new byte[8]; + output[0] = rev[7]; + output[1] = rev[6]; + output[2] = rev[5]; + output[3] = rev[4]; + output[4] = rev[3]; + output[5] = rev[2]; + output[6] = rev[1]; + output[7] = rev[0]; + return output; + } + + protected static byte[] setULong(UInt64 value) + { + byte[] rev = BitConverter.GetBytes(value); + byte[] output = new byte[8]; + output[0] = rev[7]; + output[1] = rev[6]; + output[2] = rev[5]; + output[3] = rev[4]; + output[4] = rev[3]; + output[5] = rev[2]; + output[6] = rev[1]; + output[7] = rev[0]; + return output; + } + + protected static byte[] setDouble(double value) + { + byte[] rev = BitConverter.GetBytes(value); + byte[] output = new byte[8]; + output[0] = rev[7]; + output[1] = rev[6]; + output[2] = rev[5]; + output[3] = rev[4]; + output[4] = rev[3]; + output[5] = rev[2]; + output[6] = rev[1]; + output[7] = rev[0]; + return output; + } + + protected static byte[] setChar(char value) + { + byte[] output = new byte[4]; + output[0] = 0; + output[1] = 0; + output[2] = 0; + output[3] = (byte)value; + return output; + } + + protected static byte[] setRGBA(RGBA value) + { + byte[] output = new byte[4]; + output[0] = value.R; + output[1] = value.G; + output[2] = value.B; + output[3] = value.A; + return output; + } + + protected static byte[] setMidi(Midi value) + { + byte[] output = new byte[4]; + output[0] = value.Port; + output[1] = value.Status; + output[2] = value.Data1; + output[3] = value.Data2; + return output; + } + + #endregion + + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/RGBA.cs b/qController.Standard/Classes/Communication/SharpOSC/RGBA.cs new file mode 100755 index 0000000..b17ef95 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/RGBA.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public struct RGBA + { + public byte R; + public byte G; + public byte B; + public byte A; + + public RGBA(byte red, byte green, byte blue, byte alpha) + { + this.R = red; + this.G = green; + this.B = blue; + this.A = alpha; + } + + public override bool Equals(System.Object obj) + { + if (obj.GetType() == typeof(RGBA)) + { + if (this.R == ((RGBA)obj).R && this.G == ((RGBA)obj).G && this.B == ((RGBA)obj).B && this.A == ((RGBA)obj).A) + return true; + else + return false; + } + else if (obj.GetType() == typeof(byte[])) + { + if (this.R == ((byte[])obj)[0] && this.G == ((byte[])obj)[1] && this.B == ((byte[])obj)[2] && this.A == ((byte[])obj)[3]) + return true; + else + return false; + } + else + return false; + } + + public static bool operator ==(RGBA a, RGBA b) + { + if (a.Equals(b)) + return true; + else + return false; + } + + public static bool operator !=(RGBA a, RGBA b) + { + if (!a.Equals(b)) + return true; + else + return false; + } + + public override int GetHashCode() + { + return (R << 24) + (G << 16) + (B << 8) + (A); + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/Symbol.cs b/qController.Standard/Classes/Communication/SharpOSC/Symbol.cs new file mode 100755 index 0000000..209b7fa --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/Symbol.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public class Symbol + { + public string Value; + + public Symbol() + { + Value = ""; + } + + public Symbol(string value) + { + this.Value = value; + } + + public string ToString() + { + return Value; + } + + public override bool Equals(System.Object obj) + { + if (obj.GetType() == typeof(Symbol)) + { + if (this.Value == ((Symbol)obj).Value) + return true; + else + return false; + } + else if (obj.GetType() == typeof(string)) + { + if (this.Value == ((string)obj)) + return true; + else + return false; + } + else + return false; + } + + public static bool operator ==(Symbol a, Symbol b) + { + if (a.Equals(b)) + return true; + else + return false; + } + + public static bool operator !=(Symbol a, Symbol b) + { + if (!a.Equals(b)) + return true; + else + return false; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/TCPSender.cs b/qController.Standard/Classes/Communication/SharpOSC/TCPSender.cs new file mode 100644 index 0000000..ef47e5e --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/TCPSender.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.IO; + +namespace SharpOSC +{ + public class TCPSender + { + public int Port + { + get { return _port; } + } + int _port; + + public string Address + { + get { return _address; } + } + string _address; + + TcpClient client; + + byte END = 0xc0; + byte ESC = 0xdb; + byte ESC_END = 0xDC; + byte ESC_ESC = 0xDD; + + + public TCPSender(string address, int port) + { + _port = port; + _address = address; + + } + + public void Send(byte[] message) + { + client = new TcpClient(Address, Port); + Console.WriteLine("Available: " + client.Available); + Console.WriteLine("Connected: " + client.Connected); + List slipData= new List(); + + byte[] esc_end = { ESC, ESC_END }; + byte[] esc_esc = { ESC, ESC_ESC }; + byte[] end = { END }; + + int length = message.Length; + for (int i = 0; i < length; i++) + { + if (message[i] == END) + { + slipData.AddRange(esc_end); + } else if (message[i] == ESC) + { + slipData.AddRange(esc_esc); + } + else + { + slipData.Add(message[i]); + } + } + slipData.AddRange(end); + NetworkStream netStream = client.GetStream(); + netStream.Write(slipData.ToArray(), 0, slipData.ToArray().Length); + netStream.Close(); + client.Close(); + } + + public void Receive() + { + NetworkStream netStream = client.GetStream(); + List responseData = new List(); + if (netStream.CanRead) + { + byte[] buffer = new byte[512]; + + int bytesRead = 0; + do + { + bytesRead = netStream.Read(buffer, 0, buffer.Length); + responseData.AddRange(buffer); + + } while (netStream.DataAvailable); + OscMessage response = (OscMessage)OscPacket.GetPacket(responseData.Skip(1).ToArray()); + } + netStream.Close(); + } + public void Send(OscPacket packet) + { + byte[] data = packet.GetBytes(); + Send(data); + } + + public void Close() + { + client.GetStream().Close(); + client.Close(); + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/Timetag.cs b/qController.Standard/Classes/Communication/SharpOSC/Timetag.cs new file mode 100755 index 0000000..6a6c369 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/Timetag.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public struct Timetag + { + public UInt64 Tag; + + public DateTime Timestamp + { + get + { + return Utils.TimetagToDateTime(Tag); + } + set + { + Tag = Utils.DateTimeToTimetag(value); + } + } + + /// + /// 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) + /// + public double Fraction + { + get + { + return Utils.TimetagToFraction(Tag); + } + set + { + Tag = (Tag & 0xFFFFFFFF00000000) + (UInt32)(value * 0xFFFFFFFF); + } + } + + public Timetag(UInt64 value) + { + this.Tag = value; + } + + public Timetag(DateTime value) + { + Tag = 0; + this.Timestamp = value; + } + + public override bool Equals(System.Object obj) + { + if (obj.GetType() == typeof(Timetag)) + { + if (this.Tag == ((Timetag)obj).Tag) + return true; + else + return false; + } + else if (obj.GetType() == typeof(UInt64)) + { + if (this.Tag == ((UInt64)obj)) + return true; + else + return false; + } + else + return false; + } + + public static bool operator ==(Timetag a, Timetag b) + { + if (a.Equals(b)) + return true; + else + return false; + } + + public static bool operator !=(Timetag a, Timetag b) + { + if (a.Equals(b)) + return true; + else + return false; + } + + public override int GetHashCode() + { + return (int)( ((uint)(Tag >> 32) + (uint)(Tag & 0x00000000FFFFFFFF)) / 2); + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/UDPListener.cs b/qController.Standard/Classes/Communication/SharpOSC/UDPListener.cs new file mode 100755 index 0000000..9fc2f78 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/UDPListener.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; + +namespace SharpOSC +{ + public delegate void HandleOscPacket(OscPacket packet); + public delegate void HandleBytePacket(byte[] packet); + + public class UDPListener : IDisposable + { + public int Port { get; private set; } + + object callbackLock; + + UdpClient receivingUdpClient; + IPEndPoint RemoteIpEndPoint; + + HandleBytePacket BytePacketCallback = null; + HandleOscPacket OscPacketCallback = null; + + Queue queue; + ManualResetEvent ClosingEvent; + + public UDPListener(int port) + { + Port = port; + queue = new Queue(); + ClosingEvent = new ManualResetEvent(false); + callbackLock = new object(); + + // try to open the port 10 times, else fail + for (int i = 0; i < 10; i++) + { + try + { + receivingUdpClient = new UdpClient(port); + break; + } + catch (Exception) + { + // Failed in ten tries, throw the exception and give up + if (i >= 9) + throw; + + Thread.Sleep(5); + } + } + RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0); + + // setup first async event + AsyncCallback callBack = new AsyncCallback(ReceiveCallback); + receivingUdpClient.BeginReceive(callBack, null); + } + + public UDPListener(int port, HandleOscPacket callback) : this(port) + { + this.OscPacketCallback = callback; + } + + public UDPListener(int port, HandleBytePacket callback) : this(port) + { + this.BytePacketCallback = callback; + } + + void ReceiveCallback(IAsyncResult result) + { + Monitor.Enter(callbackLock); + Byte[] bytes = null; + + try + { + bytes = receivingUdpClient.EndReceive(result, ref RemoteIpEndPoint); + } + catch (ObjectDisposedException e) + { + // Ignore if disposed. This happens when closing the listener + } + + // Process bytes + if (bytes != null && bytes.Length > 0) + { + if (BytePacketCallback != null) + { + BytePacketCallback(bytes); + } + else if (OscPacketCallback != null) + { + OscPacket packet = null; + try + { + packet = OscPacket.GetPacket(bytes); + } + catch (Exception e) + { + // If there is an error reading the packet, null is sent to the callback + } + + OscPacketCallback(packet); + } + else + { + lock (queue) + { + queue.Enqueue(bytes); + } + } + } + + if (closing) + ClosingEvent.Set(); + else + { + // Setup next async event + AsyncCallback callBack = new AsyncCallback(ReceiveCallback); + receivingUdpClient.BeginReceive(callBack, null); + } + Monitor.Exit(callbackLock); + } + + bool closing = false; + public void Close() + { + lock (callbackLock) + { + ClosingEvent.Reset(); + closing = true; + receivingUdpClient.Close(); + } + ClosingEvent.WaitOne(); + + } + + public void Dispose() + { + this.Close(); + } + + public OscPacket Receive() + { + if (closing) throw new Exception("UDPListener has been closed."); + + lock (queue) + { + if (queue.Count() > 0) + { + byte[] bytes = queue.Dequeue(); + var packet = OscPacket.GetPacket(bytes); + return packet; + } + else + return null; + } + } + + public byte[] ReceiveBytes() + { + if (closing) throw new Exception("UDPListener has been closed."); + + lock (queue) + { + if (queue.Count() > 0) + { + byte[] bytes = queue.Dequeue(); + return bytes; + } + else + return null; + } + } + + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/UDPSender.cs b/qController.Standard/Classes/Communication/SharpOSC/UDPSender.cs new file mode 100755 index 0000000..aadb108 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/UDPSender.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; + +namespace SharpOSC +{ + public class UDPSender + { + public int Port + { + get { return _port; } + } + int _port; + + public string Address + { + get { return _address; } + } + string _address; + + IPEndPoint RemoteIpEndPoint; + Socket sock; + + public UDPSender(string address, int port) + { + _port = port; + _address = address; + + sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + var addresses = System.Net.Dns.GetHostAddresses(address); + if (addresses.Length == 0) throw new Exception("Unable to find IP address for " + address); + + RemoteIpEndPoint = new IPEndPoint(addresses[0], port); + } + + public void Send(byte[] message) + { + sock.SendTo(message, RemoteIpEndPoint); + } + + public void Send(OscPacket packet) + { + byte[] data = packet.GetBytes(); + Send(data); + } + + public void Close() + { + sock.Close(); + } + } +} diff --git a/qController.Standard/Classes/Communication/SharpOSC/Utils.cs b/qController.Standard/Classes/Communication/SharpOSC/Utils.cs new file mode 100755 index 0000000..165c0f7 --- /dev/null +++ b/qController.Standard/Classes/Communication/SharpOSC/Utils.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SharpOSC +{ + public class Utils + { + public static DateTime TimetagToDateTime(UInt64 val) + { + if (val == 1) + return DateTime.Now; + + UInt32 seconds = (UInt32)(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; + } + + public static double TimetagToFraction(UInt64 val) + { + if (val == 1) + return 0.0; + + UInt32 seconds = (UInt32)(val & 0x00000000FFFFFFFF); + double fraction = (double)seconds / (UInt32)(0xFFFFFFFF); + return fraction; + } + + public static UInt64 DateTimeToTimetag(DateTime value) + { + UInt64 seconds = (UInt32)(value - DateTime.Parse("1900-01-01 00:00:00.000")).TotalSeconds; + UInt64 fraction = (UInt32)(0xFFFFFFFF * ((double)value.Millisecond / 1000)); + + UInt64 output = (seconds << 32) + fraction; + return output; + } + + public static int AlignedStringLength(string val) + { + int len = val.Length + (4 - val.Length % 4); + if (len <= val.Length) len += 4; + + return len; + } + } +}