Created
December 20, 2009 18:21
-
-
Save atsushieno/260595 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// OSC implementation (haven't ever used; it just compiles yet) | |
// | |
// Author: | |
// Atsushi Eno ( http://github.com/atsushieno ) | |
// | |
// | |
// 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. | |
// | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
namespace Commons.Net.Osc | |
{ | |
public class OscProtocol | |
{ | |
public OscProtocol () | |
{ | |
CustomTypeParsers = new List<OscCustomTypeParser> (); | |
} | |
public IList<OscCustomTypeParser> CustomTypeParsers { get; private set; } | |
protected internal OscCustomTypeParser FindCustomTypeParser (byte c) | |
{ | |
var ret = CustomTypeParsers.FirstOrDefault (p => p.TypeId == c); | |
if (ret != null) | |
return ret; | |
throw new NotSupportedException (String.Format ("Type tag '{0}' (0x{1:X02}) is not supported by this OSC protocol", (char) c, c)); | |
} | |
public void WriteOscMessage (Stream stream, OscMessage msg) | |
{ | |
new OscWriter (stream, this).Write (msg); | |
} | |
public void WriteOscBundle (Stream stream, OscBundle bundle) | |
{ | |
new OscWriter (stream, this).Write (bundle); | |
} | |
public OscPacket ReadOscPacket (Stream stream) | |
{ | |
return new OscReader (stream, this).ReadPacket (); | |
} | |
} | |
public abstract class OscPacket | |
{ | |
} | |
public class OscBundle : OscPacket | |
{ | |
public OscBundle () | |
{ | |
Elements = new List<OscPacket> (); | |
} | |
// FIXME: might be better to define own structure for fixed-point number. | |
public long TimeTag { get; set; } | |
public IList<OscPacket> Elements { get; private set; } | |
} | |
public class OscMessage : OscPacket | |
{ | |
public string AddressPattern { get; set; } | |
public IList<OscValue> Arguments { get; internal set; } | |
public OscMessage () | |
{ | |
Arguments = new List<OscValue> (); | |
} | |
public string GetTypeTag () | |
{ | |
if (Arguments.Count == 0) | |
return ","; | |
if (Arguments.Count == 1) | |
return "," + Arguments [0].TypeId; | |
var sb = new StringBuilder (); | |
sb.Append (','); | |
foreach (var arg in Arguments) | |
sb.Append (arg.TypeId); | |
return sb.ToString (); | |
} | |
} | |
public class OscValue | |
{ | |
public OscValue (int value) : this ((byte) 'i', value) {} | |
public OscValue (float value) : this ((byte) 'f', value) {} | |
public OscValue (string value) : this ((byte) 's', value) {} | |
public OscValue (byte [] value) : this ((byte) 'b', value) {} | |
public OscValue (byte typeId, object value) | |
{ | |
TypeId = typeId; | |
Value = value; | |
} | |
public byte TypeId { get; private set; } | |
public object Value { get; private set; } | |
} | |
public abstract class OscCustomTypeParser | |
{ | |
protected OscCustomTypeParser (byte typeId) | |
{ | |
TypeId = typeId; | |
} | |
public byte TypeId { get; private set; } | |
public abstract object Read (Stream stream); | |
public abstract void Write (Stream stream, object value); | |
} | |
public class OscReader | |
{ | |
Stream stream; | |
OscProtocol protocol; | |
public OscReader (Stream stream, OscProtocol protocol) | |
{ | |
if (stream == null) | |
throw new ArgumentNullException ("stream"); | |
this.stream = stream; | |
this.protocol = protocol; | |
} | |
int peek; | |
int Peek () | |
{ | |
if (peek >= 0) | |
return peek; | |
peek = stream.ReadByte (); | |
return peek; | |
} | |
byte ReadByte () | |
{ | |
int val = peek; | |
if (peek >= 0) | |
peek = -1; | |
else | |
val = stream.ReadByte (); | |
if (val < 0) | |
throw new ArgumentException ("Unexpected end of stream"); | |
return (byte) val; | |
} | |
public OscPacket ReadPacket () | |
{ | |
byte b = ReadByte (); | |
switch ((char) b) { | |
case '/': | |
// OSC Message | |
var msg = new OscMessage (); | |
msg.AddressPattern = '/' + ReadString (1); | |
foreach (var sect in msg.AddressPattern.Split ('/')) | |
CheckAddressLetter (sect); | |
b = ReadByte (); | |
if (b != ',') { | |
throw new NotSupportedException ("What value is expected when the Type Tag String is missing? It is unclear in OSC specification 1.0"); | |
} else { | |
var typeTag = ReadString (1); | |
foreach (char c in typeTag) | |
msg.Arguments.Add (ReadValue ()); | |
} | |
return msg; | |
case '#': | |
// OSC Bundle | |
var s = ReadString (1); | |
if (s != "bundle") | |
throw new ArgumentException (String.Format ("Expected '#bundle', but got '#{0}'", s)); | |
var bundle = new OscBundle (); | |
bundle.TimeTag = (ReadInt32 () << 32) + ReadInt32 (); | |
while (Peek () >= 0) { | |
ReadInt32 (); // size, we don't verify it so far. | |
bundle.Elements.Add (ReadPacket ()); | |
} | |
return bundle; | |
default: | |
throw new ArgumentException (String.Format ("Invalid OSC packet contents: it must be wither a message (starts with '/') or a bundle (starts with '#'), while it started with '{0}' (0x{1})", (char) b, b)); | |
} | |
} | |
public OscValue ReadValue () | |
{ | |
byte b = ReadByte (); | |
switch ((char) b) { | |
case 'i': return new OscValue (b, ReadInt32 ()); | |
case 'f': return new OscValue (b, ReadFloat32 ()); | |
case 's': return new OscValue (b, ReadString ()); | |
case 'b': return new OscValue (b, ReadBlob ()); | |
default: | |
return new OscValue (b, protocol.FindCustomTypeParser (b).Read (stream)); | |
} | |
} | |
void CheckAddressLetter (string s) | |
{ | |
foreach (char c in s) | |
CheckAddressLetter ((byte) c); | |
} | |
void CheckAddressLetter (byte b) | |
{ | |
switch ((char) b) { | |
case '\0': case ' ': case '#': case '=': case '*': | |
case ',': case '/': case '?': | |
case '[': case ']': case '{': case '}': case '\x7F': | |
break; | |
default: | |
if (b >= 0x20) | |
return; | |
break; | |
} | |
throw new ArgumentException (String.Format ("Not allowed address character: '{0}' (0x{1})", (char) b, b)); | |
} | |
// primitive | |
public byte [] ReadBlob () | |
{ | |
int size = ReadInt32 (); | |
var ret = new byte [size]; | |
stream.Read (ret, 0, size); | |
for (int i = size; i % 4 != 0; i++) | |
ReadByte (); | |
return ret; | |
} | |
public string ReadString () | |
{ | |
return ReadString (0); | |
} | |
public string ReadString (int offset) | |
{ | |
StringBuilder sb = new StringBuilder (); | |
byte b; | |
while ((b = ReadByte ()) != 0) | |
sb.Append ((char) b); | |
for (int i = sb.Length + offset; i % 4 != 0; i++) | |
ReadByte (); | |
return sb.ToString (); | |
} | |
public int ReadInt32 () | |
{ | |
return (ReadByte () << 24) + (ReadByte () << 16) + (ReadByte () << 8) + ReadByte (); | |
} | |
byte [] float_bytes; | |
public float ReadFloat32 () | |
{ | |
if (stream.Read (float_bytes, 0, 4) != 4) | |
throw new InvalidOperationException ("Insufficient stream"); | |
if (float_bytes == null) | |
float_bytes = new byte [4]; | |
byte b = float_bytes [0]; | |
float_bytes [0] = float_bytes [3]; | |
float_bytes [3] = b; | |
b = float_bytes [1]; | |
float_bytes [1] = float_bytes [2]; | |
float_bytes [2] = b; | |
return BitConverter.ToSingle (float_bytes, 0); | |
} | |
} | |
public class OscWriter | |
{ | |
Stream stream; | |
OscProtocol protocol; | |
public OscWriter (Stream stream, OscProtocol protocol) | |
{ | |
if (stream == null) | |
throw new ArgumentNullException ("stream"); | |
this.stream = stream; | |
this.protocol = protocol; | |
} | |
public void Write (OscBundle bundle) | |
{ | |
throw new NotImplementedException (); | |
} | |
public void Write (OscMessage msg) | |
{ | |
if (msg == null) | |
throw new ArgumentNullException ("msg"); | |
Write (msg.AddressPattern); | |
foreach (var arg in msg.Arguments) | |
stream.WriteByte (arg.TypeId); | |
int count = msg.Arguments.Count; | |
for (int i = -1; i < count % 4; i++) | |
stream.WriteByte (0); | |
foreach (var val in msg.Arguments) { | |
switch ((char) val.TypeId) { | |
case 'i': Write ((int) val.Value); break; | |
case 'f': Write ((float) val.Value); break; | |
case 's': Write ((string) val.Value); break; | |
case 'b': Write ((byte []) val.Value); break; | |
default: | |
if (protocol == null) | |
throw new InvalidOperationException ("Custom OSC Type Tag is used, but no custom type parser is specified."); | |
protocol.FindCustomTypeParser (val.TypeId).Write (stream, val.Value); | |
break; | |
} | |
} | |
} | |
public void Write (string s) | |
{ | |
var bytes = Encoding.UTF8.GetBytes (s); | |
stream.Write (bytes, 0, bytes.Length); | |
for (int i = bytes.Length % 4; i < 4; i++) | |
stream.WriteByte (0); | |
} | |
public void Write (int i) | |
{ | |
for (int x = 3; x >= 0; x--) | |
stream.WriteByte ((byte) ((i >> (8 * x)) & 0xFF)); | |
} | |
public void Write (float f) | |
{ | |
// sigh. | |
stream.Write (BitConverter.GetBytes (f), 0, 4); | |
} | |
public void Write (byte [] blob) | |
{ | |
stream.Write (blob, 0, blob.Length); | |
for (int i = blob.Length % 4; i < 4; i++) | |
stream.WriteByte (0); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment