Control of mbed using OSC. Based on code from the Make Controller. Right now you can turn the onboard LEDs on/off and toggle 8 digital out pins. More I/O will be done in the future.

Dependencies:   mbed

Revision:
0:439354122597
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/osc_sys.cpp	Wed Mar 17 03:17:38 2010 +0000
@@ -0,0 +1,1756 @@
+/*
+ * osc_sys.cpp
+ * adapting Make controller's OSC to mBed platform
+ * Pehr Hovey
+ */
+/*********************************************************************************
+
+ Copyright 2006-2009 MakingThings
+
+ Licensed under the Apache License,
+ Version 2.0 (the "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied. See the License for
+ the specific language governing permissions and limitations under the License.
+
+*********************************************************************************/
+
+/** @defgroup OSC OSC
+	Communicate with the Make Controller Kit via OSC.
+
+	\section osc OSC
+	"Open Sound Control (OSC) is a protocol for communication among computers, sound synthesizers,
+	and other multimedia devices that is optimized for modern networking technology."
+	With OSC implemented on the Make Controller Kit, it can already talk to
+	a wide variety of environments and devices like Java, Max/MSP, Pd, Flash, Processing, SuperCollider,
+	and many others.
+
+	OSC is based on the notion of \b messages, which are composed of an address, and the data to
+	be sent to that address.  The address looks a lot like a URL that you might type into
+	your internet browser.  Each element in the address is like a directory, with other
+	elements inside it, and each element is separated from the next by a slash (/).  Each OSC
+	message must also start with a slash.
+
+	\par Example:
+	For instance, the OSC address
+	\code /make/controller/kit \endcode
+	says, "start in the 'make' directory, go down to the 'controller' directory, and
+	finally to the 'kit' directory.
+
+	Any number of \b argument \b values can be sent to that address by including them in the message
+	after the address.  These values can be integers (ints), floats, or strings.
+
+	\par Example:
+	If we wanted to send the value 35.4 to the address above, we would create the message
+	\code /make/controller/kit 35.4 \endcode
+	with a space between the address and the data.\n\n
+	Additional data can be added, each separated by a space
+	\code /make/controller/kit 35.4 lawn 12 \endcode
+
+	\section osc_mck OSC & the Make Controller Kit
+	Many devices on the Make Controller Kit can be addressed via OSC.  In sending messages to them,
+	we need to know the OSC address, and the appropriate argument values to send.
+
+	The Make Controller Kit is orgranized, for OSC, into \b subsystems.  Each subsystem has one or more
+	\b devices, and each device has one or more \b properties.  To address a particular device, you'll
+	need to create an OSC message specifying the address in that format:
+	\code /subsystem/device/property \endcode
+	Each of the modules above provide the details for each of the subsystems on the board, along with their
+	devices and properties.  A simple example is given below.
+
+	\par Example:
+	To create an OSC message to turn an LED on, first identify the appropriate \b subsystem.
+	In this case, the subsystem is called \b appled.\n\n
+	There are 4 LEDs, so we need to specify which one to control.
+	The LEDs are numbered 0 -3, so choosing the first LED means the \b device value is 0.\n\n
+	The \b property of the LED that turns it on and off is its 'state'.\n\n
+	Lastly, we must specify what the state should actually be, by including an \b argument value after the address.
+	To turn it on, this value should be 1.  0 would turn it off.\n
+	The complete OSC message looks like
+	\code /appled/0/state 1 \endcode
+*/
+
+
+#include "osc_sys.h"
+#include "lwip/udp.h"
+
+
+#include <string.h>
+#include <stdio.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+
+
+
+int Osc_Quicky( int channel, char* preamble, char* string );
+char* Osc_WritePaddedString( char* buffer, int* length, char* string );
+char* Osc_WritePaddedBlob( char* buffer, int* length, char* blob, int blen );
+char* Osc_WriteTimetag( char* buffer, int* length, int a, int b );
+int Osc_EndianSwap( int a );
+int Osc_ReceiveMessage( int channel, char* message, int length );
+char* Osc_CreateBundle( char* buffer, int* length, int a, int b );
+int Osc_PropertyLookup( char** properties, char* property );
+int Osc_ReadInt( char* buffer );
+float Osc_ReadFloat( char* buffer );
+
+
+//void Osc_AsyncTask( void* p );
+
+void Osc_ResetChannel( OscChannel* ch );
+int Osc_SendPacketInternal( OscChannel* ch );
+
+int Osc_SendMessage( int channel, char* message, int length );
+int Osc_ReceiveMessage( int channel, char* message, int length );
+int Osc_Poll( int channel, char* buffer, int maxLength, int* length );
+
+//osc_patternmatch.c
+bool Osc_PatternMatch(const char * pattern, const char * test);
+
+int Osc_Quicky( int channel, char* preamble, char* string );
+char* Osc_WritePaddedString( char* buffer, int* length, char* string );
+char* Osc_WriteTimetag( char* buffer, int* length, int a, int b );
+int Osc_EndianSwap( int a );
+
+char* Osc_CreateBundle( char* buffer, int* length, int a, int b );
+char* Osc_CreateMessageInternal( char* bp, int* length, char* address, char* format, va_list args );
+int Osc_CreateMessageToBuf( char* bp, int* length, char* address, char* format, ... );
+
+int Osc_ReadInt( char* buffer );
+float Osc_ReadFloat( char* buffer );
+int Osc_UdpPacketSend( char* packet, int length, struct ip_addr * replyAddress, int replyPort );
+
+int OscBusy;
+
+//Structures
+
+
+
+typedef struct OscSubsystem_
+{
+  const char* name;
+  int (*receiveMessage)( int channel, char* buffer, int length );
+  int (*async)( int channel );
+}OscSubsystem;
+
+typedef struct Osc_
+{
+  int users;
+  int running;
+  int subsystemHighest;
+
+  OscChannel* channel[ OSC_CHANNEL_COUNT ];
+  OscSubsystem* subsystem[ OSC_SUBSYSTEM_COUNT ];
+  int registeredSubsystems; //counter
+  char scratch1[ OSC_SCRATCH_SIZE ], scratch2[ OSC_SCRATCH_SIZE ];
+  //UDP stuff
+  struct udp_pcb * osc_pcb; //used to send packets
+
+}OscStruct;//OscStruct is the type name
+
+OscStruct* Osc; //The allocated Osc struct that we refer to
+
+
+void Osc_SystemInit( struct udp_pcb * pcb )
+{
+
+	Osc = (OscStruct *) malloc( sizeof( OscStruct ));
+    Osc->subsystemHighest = 0;
+    Osc->registeredSubsystems = 0;
+
+    int i;
+    for( i = 0; i < OSC_CHANNEL_COUNT; i++ )
+      Osc->channel[ i ] = NULL;
+    for( i = 0; i < OSC_SUBSYSTEM_COUNT; i++ )
+      Osc->subsystem[ i ] = NULL;
+
+    Osc->users = 1;
+    Osc->running = true;
+
+    Osc_UdpInit(OSC_CHANNEL_UDP, pcb);
+
+  return;
+}
+
+OscChannel * Osc_GetChannel(int  channel){
+	if(channel < OSC_CHANNEL_COUNT)
+		return Osc->channel[channel];
+	else
+		return NULL;
+
+}
+//Initialize the UDP channel
+void Osc_UdpInit( int channel, struct udp_pcb * pcb)
+{
+  Osc->osc_pcb = pcb;
+  Osc->channel[ channel ] = (OscChannel *) malloc( sizeof( OscChannel ));
+  OscChannel *ch = Osc->channel[ channel ]; //get the struct so we can assign things to it
+
+  Osc_SetReplyPort( channel, UDP_BROADCAST_PORT); //broadcast by default
+  Osc_SetReplyAddress( channel, IP_ADDR_BROADCAST); //broadcast by default
+  ch->sendMessage = Osc_UdpPacketSend; //the function used to send packets
+  Osc_ResetChannel( ch );
+
+  ch->running = true;
+  //will get data from the udp_recv callback (external right now)
+}
+
+int Osc_UdpPacketSend( char* packet, int length, struct ip_addr * replyAddress, int replyPort )
+{
+  if ( replyAddress != 0 && replyPort != 0 )
+  {
+    //Use LWIP raw API to send a packet
+	  //make packet
+	 	  struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT,length,PBUF_RAM);
+	 	  memcpy (p->payload, packet, length);
+	 	  //send packet
+	 	 int retval = udp_sendto(Osc->osc_pcb, p, replyAddress, replyPort);
+	 	  //free packet
+	 	  pbuf_free(p);
+
+    return retval;
+  }
+  else
+    return CONTROLLER_ERROR_NO_ADDRESS;
+}
+
+
+void Osc_AsyncTask( void* p )
+{
+//  (void)p;
+//  int channel;
+//  int i;
+//  OscSubsystem* sub;
+//  int newMsgs = 0;
+//  while( 1 )
+//  {
+//    channel = System_GetAsyncDestination( );
+//    if( channel >= 0 )
+//    {
+//      for( i = 0; i < Osc->registeredSubsystems; i++ )
+//      {
+//        sub = Osc->subsystem[ i ];
+//        if( sub->async != NULL )
+//          newMsgs += (sub->async)( channel );
+//      }
+//      if( newMsgs > 0 )
+//      {
+//        Osc_SendPacket( channel );
+//        newMsgs = 0;
+//      }
+//      Sleep( System_GetAutoSendInterval( ) );
+//    }
+//    else
+//      Sleep( 1000 );
+//  }
+}
+
+int Osc_SetReplyAddress( int channel, struct ip_addr * replyAddress )
+{
+  if ( channel < 0 || channel >= OSC_CHANNEL_COUNT )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  OscChannel* ch =  Osc->channel[ channel ];
+
+  ch->replyAddress = replyAddress;
+
+  return CONTROLLER_OK;
+}
+
+int Osc_SetReplyPort( int channel, int replyPort )
+{
+  if ( channel < 0 || channel >= OSC_CHANNEL_COUNT )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  OscChannel* ch =  Osc->channel[ channel ];
+
+  ch->replyPort = replyPort;
+
+  return CONTROLLER_OK;
+}
+
+void Osc_ResetChannel( OscChannel* ch )
+{
+  ch->bufferPointer = ch->buffer;
+  ch->bufferRemaining = OSC_MAX_MESSAGE_OUT;
+  ch->messages = 0;
+}
+
+int Osc_SendMessage( int channel, char* message, int length )
+{
+  (void)channel;
+  (void)message;
+  (void)length;
+  return CONTROLLER_OK;
+}
+
+int Osc_ReceivePacket( int channel, char* packet, int length )
+{
+  // Got a packet.  Unpacket.
+  int status = -1;
+  switch ( *packet )
+  {
+    case '/':
+      status = Osc_ReceiveMessage( channel, packet, length );
+      break;
+    case '#':
+      if ( strcmp( packet, "#bundle" ) == 0 )
+      {
+        // skip bundle text and timetag
+        packet += 16;
+        length -= 16;
+        while ( length > 0 )
+        {
+          // read the length (pretend packet is a pointer to integer)
+          int messageLength = Osc_EndianSwap( *((int*)packet) );
+          packet += 4;
+          length -= 4;
+          if ( messageLength <= length )
+            Osc_ReceivePacket( channel, packet, messageLength );
+          length -= messageLength;
+          packet += messageLength;
+        }
+      }
+      break;
+    default:
+      // Something else?
+      Osc_CreateMessage( channel, "/error", ",s", "Packet Error" );
+      break;
+  }
+
+  return Osc_SendPacket( channel );
+}
+
+int Osc_ReceiveMessage( int channel, char* message, int length )
+{
+  // Got a packet.  Unpacket.
+
+  // Confirm it's a message
+  if ( *message == '/' )
+  {
+    if( strlen(message) > (unsigned int)length )
+      return CONTROLLER_ERROR_BAD_DATA;
+
+    int i;
+    char* nextChar = message + 1; // first, try to see if it was a "help" query
+    if( *nextChar == '\0' || *nextChar == ' ' )
+    {
+      for ( i = 0; i < Osc->registeredSubsystems; i++ )
+      {
+        OscSubsystem* sub = Osc->subsystem[ i ];
+        Osc_CreateMessage( channel, "/", ",s", sub->name );
+      }
+      return CONTROLLER_OK;
+    }
+
+    char* nextSlash = strchr( message + 1, '/' );
+    if ( nextSlash != NULL )
+      *nextSlash = 0;
+
+    int count = 0;
+    for ( i = 0; i < Osc->registeredSubsystems; i++ )
+    {
+      OscSubsystem* sub = Osc->subsystem[ i ];
+      if ( Osc_PatternMatch( message + 1, sub->name ) )
+      {
+        count++;
+        if ( nextSlash )
+          (sub->receiveMessage)( channel, nextSlash + 1, length - ( nextSlash - message ) - 1 );
+        else
+        {
+          char* noNextSlash = message + strlen(message);
+          (sub->receiveMessage)( channel, noNextSlash, 0 );
+        }
+      }
+    }
+    if ( count == 0 )
+    {
+
+      snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "No Subsystem Match - %s", message + 1 );
+			Osc_CreateMessage( channel, "/error", ",s", Osc->scratch1 );
+
+		}
+  }
+  else
+  {
+    return CONTROLLER_ERROR_BAD_DATA;
+  }
+  return CONTROLLER_OK;
+}
+
+int Osc_SendPacket( int channel )
+{
+  if ( channel < 0 || channel >= OSC_CHANNEL_COUNT )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  OscChannel* ch = Osc->channel[ channel ];
+
+  if ( ch->messages == 0 )
+    return CONTROLLER_OK;
+
+
+  int ret = Osc_SendPacketInternal( ch );
+ printf("Sent packets for channel # %d, ret=%d \r\n",channel,ret);
+
+  return ret;
+}
+
+int Osc_SendPacketInternal( OscChannel* ch )
+{
+  //printf("Trying to send packets - we have %d msgs\r\n",ch->messages);
+	if ( ch->messages == 0 )
+    return CONTROLLER_OK;
+
+  // set the buffer and length up
+  char* buffer = ch->buffer;
+  int length = OSC_MAX_MESSAGE_OUT - ch->bufferRemaining;
+
+  // see if we can dispense with the bundle business
+  if ( ch->messages == 1 )
+  {
+    // skip 8 bytes of "#bundle" and 8 bytes of timetag and 4 bytes of size
+    buffer += 20;
+    // shorter too
+    length -= 20;
+  }
+  //call the stored function to do the sending (Osc_UdpPacketSend if its UDP)
+  (*ch->sendMessage)( buffer, length, ch->replyAddress, ch->replyPort );
+
+  Osc_ResetChannel( ch );
+
+  return CONTROLLER_OK;
+}
+
+int Osc_Poll( int channel, char* buffer, int maxLength, int* length )
+{
+  (void)buffer;
+  (void)maxLength;
+  (void)length;
+  (void)channel;
+  return CONTROLLER_OK;
+}
+
+int Osc_Quicky( int channel, char* preamble, char* string )
+{
+  return Osc_CreateMessage( channel, "/debug", ",ss", preamble, string );
+}
+
+/** @defgroup OSCAPI OSC API
+	Make use of the OSC infrastructure for your own subsystems.
+	You can use the existing OSC infrastructure to create your own OSC subsystems.  It's expected that you'll have
+	a few things lined up to use this API:
+	-# The name of your subsystem
+	-# The properties that your subsystem will support.  This must be in an array with '0' as the last element.
+	-# Getters and setters for each of those properties and functions to call them by property index.
+	-# A function to call the correct helper when Osc calls you.
+	-# Finally, once you've done all this, you'll need to add your subsystem to the list that Osc knows about.
+
+	\par Example
+	So this might look something like:
+	\code
+	// our system name and a function to get it
+	static char* MySubsystemOsc_Name = "my-system";
+	const char* MySubsystemOsc_GetName( void )
+	{
+		return MySubsystemOsc_Name;
+	}
+
+	// our property names
+	static char* MySubsystemOsc_PropertyNames[] = { "prop0", "prop1", 0 }; // must end with a zero
+
+	// A getter and setter, each dealing with the property given to us by the OSC system
+	int MyGPSOsc_PropertySet( int index, int property, int value )
+  {
+    switch ( property )
+    {
+      case 0:
+        MySubsystem_SetProperty0( index, value );
+        break;
+      case 1:
+        MySubsystem_SetProperty1( index, value );
+        break;
+     }
+    return CONTROLLER_OK;
+  }
+
+  int MySubsystemOsc_PropertyGet( int index, int property )
+  {
+    int value;
+     switch ( property )
+     {
+      case 0:
+        value = MySubsystem_GetProperty0( index );
+        break;
+      case 1:
+        value = MySubsystem_GetProperty1( index );
+        break;
+    }
+    return value;
+  }
+
+	// this is called when the OSC system determines an incoming message is for you.
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+     // depending on your subsystem, use one of the OSC helpers to parse the incoming message
+		 return Osc_IndexGeneralReceiverHelper( channel, message, length,
+                                        MySubsystemOsc_Name,
+                                        MySubsystemOsc_PropertySet,
+                                        MySubsystemOsc_PropertyGet,
+                                        MySubsystemOsc_PropertyNames );
+	}
+
+	// lastly, we'll need to register our system with OSC
+	Run( ) // this is our startup task in make.c
+	{
+		// other startup stuff
+		Osc_RegisterSubsystem( MySubsystemOsc_GetName(), MySubsystemOsc_ReceiveMessage, NULL );
+	}
+	\endcode
+
+	Check the how-to at http://www.makingthings.com/documentation/how-to/create-your-own-osc-subsystem for details.
+
+	\ingroup Core
+*/
+
+/**
+	Register your subsystem with the OSC system.
+	You'll usually want to do this on startup.
+	@param name The name of your subsystem.
+	@param subsystem_ReceiveMessage The function to call when an OSC message for this subsystem has arrived.
+	@param subsystem_Async The function to be called by the OSC Async system.  This is a task that will call you at regular intervals,
+	if enabled, so you can check for status changes and send a message out automatically if you like.  See the analog in source for
+	an example.  Pass in NULL if you don't want to use the Async system.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	Run( ) // this is our startup task in make.c
+	{
+		// other startup stuff
+		Osc_RegisterSubsystem( MySubsystemOsc_GetName(), MySubsystemOsc_ReceiveMessage, NULL );
+	}
+  \endcode
+*/
+int Osc_RegisterSubsystem( const char *name, int (*subsystem_ReceiveMessage)( int channel, char* buffer, int length ), int (*subsystem_Async)( int channel ) )
+{
+  int subsystem = Osc->registeredSubsystems;
+  if ( Osc->registeredSubsystems++ > OSC_SUBSYSTEM_COUNT )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  Osc->subsystem[ subsystem ] = (OscSubsystem *) malloc( sizeof( OscSubsystem ));
+  OscSubsystem* sub = Osc->subsystem[ subsystem ];
+  sub->name = name;
+  sub->receiveMessage = subsystem_ReceiveMessage;
+  sub->async = subsystem_Async;
+  return CONTROLLER_OK;
+}
+
+/**
+	Send an error back via OSC from your subsystem.
+	You'll usually want to call this when the OSC system calls you with a new message, you've tried to
+	parse it, and you've gotten an error.
+	@param channel An index for which OSC channel is being used (usually USB or Ethernet).  Usually provided for you
+	by the OSC system.
+	@param subsystem The name of the subsystem sending the error.
+	@param string The actual contents of the error message.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	// this is where OSC calls us when an incoming message for us has arrived
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+		int status = Osc_IntReceiverHelper( channel, message, length,
+																				MySubsystemOsc_Name,
+																				MySubsystemOsc_PropertySet, MySubsystemOsc_PropertyGet,
+																				MySubsystemOsc_PropertyNames );
+
+		if ( status != CONTROLLER_OK )
+			Osc_SubsystemError( channel, MySubsystemOsc_Name, "Oh no. Bad data." );
+	}
+  \endcode
+*/
+int Osc_SubsystemError( int channel, char* subsystem, char* string )
+{
+  int retval;
+
+  snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s/error", subsystem );
+  retval = Osc_CreateMessage( channel, Osc->scratch1, ",s", string );
+
+  return retval;
+}
+
+
+/**
+	Receive an OSC blob for a subsystem with no indexes.
+	You'll usually want to call this when the OSC system calls you with a new message.
+	@param channel An index for which OSC channel is being used (usually USB or Ethernet).  Usually provided for you
+	by the OSC system.
+	@param message The OSC message being received.  Usually provided for you by the OSC system.
+	@param length The length of the incoming message.  Usually provided for you by the OSC system.
+	@param subsystemName The name of your subsystem.
+	@param blobPropertySet A pointer to the function to be called in order to write a property of your subsystem.
+	@param blobPropertyGet A pointer to the function to be called in order to read a property of your subsystem.
+	@param blobPropertyNames An array of all the property names in your subsystem.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	// this is where OSC calls us when an incoming message for us has arrived
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+		int status = Osc_BlobReceiverHelper( channel, message, length,
+																			MySubsystemOsc_Name,
+																			MySubsystemOsc_BlobPropertySet, MySubsystemOsc_BlobPropertyGet,
+																			MySubsystemOsc_BlobPropertyNames );
+
+		if ( status != CONTROLLER_OK )
+			return Osc_SendError( channel, MySubsystemOsc_Name, status );
+		return CONTROLLER_OK;
+	}
+  \endcode
+*/
+int Osc_BlobReceiverHelper( int channel, char* message, int length,
+                           char* subsystemName,
+                           int (*blobPropertySet)( int property, uchar* buffer, int length ),
+                           int (*blobPropertyGet)( int property, uchar* buffer, int size ),
+                           char* blobPropertyNames[] )
+{
+  if ( message == NULL )
+    return CONTROLLER_ERROR_NO_PROPERTY;
+
+  int propertyIndex = Osc_PropertyLookup( blobPropertyNames, message );
+  if ( propertyIndex == -1 )
+    return CONTROLLER_ERROR_UNKNOWN_PROPERTY;
+
+  // Sometime after the address, the data tag begins - this is the description
+  // of the data in the rest of the message.  It starts with a comma.  Return
+  // where it is into 'type'.  If there is no comma, this is bad.
+  char* type = Osc_FindDataTag( message, length ); //typetag
+  if ( type == NULL )
+    return CONTROLLER_ERROR_NO_TYPE_TAG;
+
+  // We can tell if there's data by seeing if the character after the comma
+  // is a zero or not.
+  if ( type[ 1 ] != 0 )
+  {
+    if ( type[ 1 ] == 'b' )
+    {
+      unsigned char *buffer;
+      int size;
+      int count = Osc_ExtractData( type, "b", &buffer, &size );
+      if ( count != 1 )
+        return CONTROLLER_ERROR_BAD_DATA;
+
+      (*blobPropertySet)( propertyIndex, buffer, size );
+    }
+    else
+    {
+      if ( type[ 1 ] == 's' )
+      {
+        unsigned char *buffer;
+        int count = Osc_ExtractData( type, "s", &buffer );
+        if ( count != 1 )
+          return CONTROLLER_ERROR_BAD_DATA;
+
+        (*blobPropertySet)( propertyIndex, buffer, strlen( (char*)buffer ) );
+      }
+      else
+        return CONTROLLER_ERROR_BAD_DATA;
+    }
+
+  }
+  else
+  {
+    // No data, then.  I guess it was a read.  The XXXXOsc getters
+    // take the channel number and use it to call
+    // Osc_CreateMessage() which adds a new message to the outgoing
+    // stack
+
+    int size = (*blobPropertyGet)( propertyIndex, (uchar*)Osc->scratch1, OSC_SCRATCH_SIZE );
+    snprintf( Osc->scratch2, OSC_SCRATCH_SIZE, "/%s/%s", subsystemName, blobPropertyNames[ propertyIndex ] );
+    Osc_CreateMessage( channel, Osc->scratch2, ",b", Osc->scratch1, size );
+
+  }
+
+  return CONTROLLER_OK;
+}
+
+/**
+	Receive an OSC blob for a subsystem with indexes.
+	You'll usually want to call this when the OSC system calls you with a new message.
+	@param channel An index for which OSC channel is being used (usually USB or Ethernet).  Usually provided for you
+	by the OSC system.
+	@param message The OSC message being received.  Usually provided for you by the OSC system.
+	@param length The length of the incoming message.  Usually provided for you by the OSC system.
+	@param indexCount The number of indexes in your subsystem.
+	@param subsystemName The name of your subsystem.
+	@param blobPropertySet A pointer to the function to be called in order to write a property of your subsystem.
+	@param blobPropertyGet A pointer to the function to be called in order to read a property of your subsystem.
+	@param blobPropertyNames An array of all the property names in your subsystem.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	// this is where OSC calls us when an incoming message for us has arrived
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+		int status = Osc_IndexBlobReceiverHelper( channel, message, length, 2,
+																				MySubsystemOsc_Name,
+																				MySubsystemOsc_BlobPropertySet, MySubsystemOsc_BlobPropertyGet,
+																				MySubsystemOsc_BlobPropertyNames );
+
+		if ( status != CONTROLLER_OK )
+			return Osc_SendError( channel, MySubsystemOsc_Name, status );
+		return CONTROLLER_OK;
+	}
+  \endcode
+*/
+int Osc_IndexBlobReceiverHelper( int channel, char* message, int length,
+                                int indexCount, char* subsystemName,
+                                int (*blobPropertySet)( int index, int property, uchar* buffer, int length ),
+                                int (*blobPropertyGet)( int index, int property, uchar* buffer, int length ),
+                                char* blobPropertyNames[] )
+{
+  // Look for the next slash - being the one that separates the index
+  // from the property.  Note that this won't go off on a search through the buffer
+  // since there will soon be a string terminator (i.e. a 0)
+  char* prop = strchr( message, '/' );
+  if ( prop == NULL )
+    return CONTROLLER_ERROR_BAD_FORMAT;
+
+  // Now that we know where the property is, we can see if we can find it.
+  // This is a little cheap, since we're also implying that there are no
+  // more address terms after the property.  That is, if testing for "speed", while
+  // "speed" would match, "speed/other_stuff" would not.
+  int propertyIndex = Osc_PropertyLookup( blobPropertyNames, prop + 1 );
+  if ( propertyIndex == -1 )
+    return CONTROLLER_ERROR_UNKNOWN_PROPERTY;
+
+  // Here's where we try to understand what index we got.  In the world of
+  // OSC, this could be a pattern.  So while we could get "0/speed" we could
+  // also get "*/speed" or "[0-4]/speed".  This is kind of a drag, but it is
+  // quite nice from the user's perspective.
+  // So to deal with this take a look at the text "0" or "{1,2}" or whatever
+  // and produce either a nice integer in the simplest case or a set of bits
+  // where each bit corresponds to one of the indicies.  Clearly we don't have
+  // to go crazy, since there are only a small finite number of them.
+  // Osc_NumberMatch() does the work for us, producing either number = -1 and
+  // bits == -1 if there was no index match, or number != -1 for there was a single
+  // number, or bits != -1 if there were several.
+
+  // note that we tweak the string a bit here to make sure the next '/' is not
+  // mixed up with this.  Insert a string terminator.
+  *prop = 0;
+
+  int bits;
+  int number = Osc_NumberMatch( indexCount, message, &bits );
+  if ( number == -1 && bits == -1 )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  // We tweaked the '/' before - now put it back
+  *prop = '/';
+
+  // Sometime after the address, the data tag begins - this is the description
+  // of the data in the rest of the message.  It starts with a comma.  Return
+  // where it is into 'type'.  If there is no comma, this is bad.
+  char* type = Osc_FindDataTag( message, length );
+  if ( type == NULL )
+    return CONTROLLER_ERROR_NO_TYPE_TAG;
+
+  // We can tell if there's data by seeing if the character after the comma
+  // is a zero or not.
+  if ( type[ 1 ] == 'b' || type[ 1 ] == 's' )
+  {
+    // If there was blob or string data, it was a WRITE.
+    // So, sort of scanf-like, go get the data.  Here we pass in where the data is
+    // thanks to the previous routine and then specify what we expect to find there
+    // in tag terms (i.e. "b", "s").  Finally we pass in a set
+    // of pointers to the data types we want to extract.  Osc_ExtractData()
+    // will rummage around in the message magically grabbing values for you,
+    // reporting how many it got.  It will grab convert strings if necessary.
+    unsigned char *buffer;
+    int size;
+    int count = Osc_ExtractData( type, "b", &buffer, &size );
+    if ( count != 1 )
+      return CONTROLLER_ERROR_INCORRECT_DATA_TYPE;
+
+    // Now with the data we need to decide what to do with it.
+    // Is there one or many here?
+    if ( number != -1 )
+      (*blobPropertySet)( number, propertyIndex, buffer, size );
+    else
+    {
+      int index = 0;
+      while ( bits > 0 && index < indexCount )
+      {
+        if ( bits & 1 )
+          (*blobPropertySet)( index, propertyIndex, buffer, size  );
+        bits >>= 1;
+        index++;
+      }
+    }
+  }
+  else
+  {
+    // No data, then.  I guess it was a read.  The XXXXOsc getters
+    // take the channel number and use it to call
+    // Osc_CreateMessage() which adds a new message to the outgoing
+    // stack
+    if ( number != -1 )
+    {
+
+      int size = (*blobPropertyGet)( number, propertyIndex, (uchar*)Osc->scratch1, OSC_SCRATCH_SIZE );
+      snprintf( Osc->scratch2, OSC_SCRATCH_SIZE, "/%s/%d/%s", subsystemName, number, blobPropertyNames[ propertyIndex ] );
+      Osc_CreateMessage( channel, Osc->scratch2, ",b", Osc->scratch1, size );
+
+    }
+    else
+    {
+      int index = 0;
+      while ( bits > 0 && index < indexCount )
+      {
+        if ( bits & 1 )
+        {
+
+          int size = (*blobPropertyGet)( index, propertyIndex, (uchar*)Osc->scratch1, OSC_SCRATCH_SIZE );
+          snprintf( Osc->scratch2, OSC_SCRATCH_SIZE, "/%s/%d/%s", subsystemName, index, blobPropertyNames[ propertyIndex ] );
+          Osc_CreateMessage( channel, Osc->scratch2, ",b", Osc->scratch1, size );
+
+        }
+        bits >>= 1;
+        index++;
+      }
+    }
+  }
+
+  return CONTROLLER_OK;
+}
+
+/**
+	Receive an integer for a subsystem with no indexes.
+	You'll usually want to call this when the OSC system calls you with a new message.
+	@param channel An index for which OSC channel is being used (usually USB or Ethernet).  Usually provided for you
+	by the OSC system.
+	@param message The OSC message being received.  Usually provided for you by the OSC system.
+	@param length The length of the incoming message.  Usually provided for you by the OSC system.
+	@param subsystemName The name of your subsystem.
+	@param propertySet A pointer to the function to be called in order to write a property of your subsystem.
+	@param propertyGet A pointer to the function to be called in order to read a property of your subsystem.
+	@param propertyNames An array of all the property names in your subsystem.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	// this is where OSC calls us when an incoming message for us has arrived
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+		int status = Osc_IntReceiverHelper( channel, message, length,
+																				MySubsystemOsc_Name,
+																				MySubsystemOsc_PropertySet, MySubsystemOsc_PropertyGet,
+																				MySubsystemOsc_PropertyNames );
+
+		if ( status != CONTROLLER_OK )
+			return Osc_SendError( channel, MySubsystemOsc_Name, status );
+		return CONTROLLER_OK;
+	}
+  \endcode
+*/
+int Osc_IntReceiverHelper( int channel, char* message, int length,
+                           char* subsystemName,
+                           int (*propertySet)( int property, int value ),
+                           int (*propertyGet)( int property ),
+                           char* propertyNames[] )
+{
+  if( *message == '\0' || *message == ' ' ) // first, try to see if it was a property "help" query
+  {
+    int i = 0;
+    while( true )
+    {
+      if( propertyNames[i] != 0 )
+      {
+
+        snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s", subsystemName );
+        Osc_CreateMessage( channel, Osc->scratch1, ",s", propertyNames[i] );
+
+        i++;
+      }
+      else
+        return CONTROLLER_OK;
+    }
+  }
+
+  int propertyIndex = Osc_PropertyLookup( propertyNames, message );
+  if ( propertyIndex == -1 )
+    return CONTROLLER_ERROR_UNKNOWN_PROPERTY;
+
+/*
+  int bits;
+  int number = Osc_NumberMatch( indexCount, message, &bits );
+  if ( number == -1 && bits == -1 )
+    return Osc_SubsystemError( channel, subsystemName, "Bad index" );
+*/
+
+  // Sometime after the address, the data tag begins - this is the description
+  // of the data in the rest of the message.  It starts with a comma.  Return
+  // where it is into 'type'.  If there is no comma, this is bad.
+  char* type = Osc_FindDataTag( message, length );
+  if ( type == NULL )
+    return CONTROLLER_ERROR_NO_TYPE_TAG;
+
+  // We can tell if there's data by seeing if the character after the comma
+  // is a zero or not.
+  if ( type[ 1 ] == 'i' || type[ 1 ] == 'f' )
+  {
+    // If there was int or float data, it was a WRITE.
+    // So, sort of scanff-like, go get the data.  Here we pass in where the data is
+    // thanks to the previous routine and then specify what we expect to find there
+    // in tag terms (i.e. "i", "s", "f" and others).  Finally we pass in a set
+    // of pointers to the data types we want to extract.  Osc_ExtractData()
+    // will rummage around in the message magically grabbing values for you,
+    // reporting how many it got.  It will convert ints and floats if necessary.
+    int value;
+    int count = Osc_ExtractData( type, "i", &value );
+    if ( count != 1 )
+      return CONTROLLER_ERROR_BAD_DATA;
+
+    (*propertySet)( propertyIndex, value );
+  }
+  else
+  {
+    // No data, then.  I guess it was a read.  The XXXXOsc getters
+    // take the channel number and use it to call
+    // Osc_CreateMessage() which adds a new message to the outgoing
+    // stack
+    int value = (*propertyGet)( propertyIndex );
+
+    snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s/%s", subsystemName, propertyNames[ propertyIndex ] );
+    Osc_CreateMessage( channel, Osc->scratch1, ",i", value );
+
+  }
+
+  return CONTROLLER_OK;
+}
+
+/**
+	Receive data for a subsystem that receives a variety of different data types.
+	An example of this kind of situation is the network system - you have a variety of different properties,
+	several of which are both ints and strings.
+
+	You'll usually want to call this when the OSC system calls you with a new message.
+	@param channel An index for which OSC channel is being used (usually USB or Ethernet).  Usually provided for you
+	by the OSC system.
+	@param message The OSC message being received.  Usually provided for you by the OSC system.
+	@param length The length of the incoming message.  Usually provided for you by the OSC system.
+	@param subsystemName The name of your subsystem.
+	@param propertySet A pointer to the function to be called in order to write a property of your subsystem.
+	@param propertyGet A pointer to the function to be called in order to read a property of your subsystem.
+	@param propertyNames An array of all the property names in your subsystem.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	// this is where OSC calls us when an incoming message for us has arrived
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+		int status = Osc_GeneralReceiverHelper( channel, message, length,
+																				MySubsystemOsc_Name,
+																				MySubsystemOsc_PropertySet, MySubsystemOsc_PropertyGet,
+																				MySubsystemOsc_PropertyNames );
+
+		if ( status != CONTROLLER_OK )
+			return Osc_SendError( channel, MySubsystemOsc_Name, status );
+		return CONTROLLER_OK;
+	}
+  \endcode
+*/
+int Osc_GeneralReceiverHelper( int channel, char* message, int length,
+                           char* subsystemName,
+                           int (*propertySet)( int property, char* typedata, int channel ),
+                           int (*propertyGet)( int property, int channel ),
+                           char* propertyNames[] )
+{
+  if ( message == NULL )
+    return CONTROLLER_ERROR_NO_PROPERTY;
+
+  if( *message == '\0' || *message == ' ' ) // first, try to see if it was a property "help" query
+  {
+    int i = 0;
+    while( true )
+    {
+      if( propertyNames[i] != 0 )
+      {
+
+        snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s", subsystemName );
+        Osc_CreateMessage( channel, Osc->scratch1, ",s", propertyNames[i] );
+
+        i++;
+      }
+      else
+        return CONTROLLER_OK;
+    }
+  }
+
+  int propertyIndex = Osc_PropertyLookup( propertyNames, message );
+  if ( propertyIndex == -1 )
+    return CONTROLLER_ERROR_UNKNOWN_PROPERTY;
+
+/*
+  int bits;
+  int number = Osc_NumberMatch( indexCount, message, &bits );
+  if ( number == -1 && bits == -1 )
+    return Osc_SubsystemError( channel, subsystemName, "Bad index" );
+*/
+
+  // Sometime after the address, the data tag begins - this is the description
+  // of the data in the rest of the message.  It starts with a comma.  Return
+  // where it is into 'type'.  If there is no comma, this is bad.
+  char* type = Osc_FindDataTag( message, length );
+  if ( type == NULL )
+    return Osc_SubsystemError( channel, subsystemName, "No type tag" );
+
+  // Debug( 1, "Osc General Type[1] = %d", type[ 1 ] );
+
+  // We can tell if there's data by seeing if the character after the comma
+  // is a zero or not.
+  if ( type[ 1 ] != 0 )
+  {
+    // If there was data, it was a WRITE.
+    int status;
+    status = (*propertySet)( propertyIndex, type, channel );
+    if ( status != CONTROLLER_OK )
+      return CONTROLLER_ERROR_BAD_DATA;
+  }
+  else
+  {
+    // No data, then.  I guess it was a read.  The XXXXOsc getters
+    // take the channel number and use it to call
+    // Osc_CreateMessage() which adds a new message to the outgoing
+    // stack
+
+    (*propertyGet)( propertyIndex, channel );
+  }
+
+  return CONTROLLER_OK;
+}
+
+/**
+	Receive integers for a subsystem with multiple indexes.
+	An example of this kind of situation is the analog in system - you have 8 channels (indexes) and you're only
+	going to be receiving integers.
+
+	You'll usually want to call this when the OSC system calls you with a new message.
+	@param channel An index for which OSC channel is being used (usually USB or Ethernet).  Usually provided for you
+	by the OSC system.
+	@param message The OSC message being received.  Usually provided for you by the OSC system.
+	@param length The length of the incoming message.  Usually provided for you by the OSC system.
+	@param indexCount The number of indexes in your subsystem.
+	@param subsystemName The name of your subsystem.
+	@param propertySet A pointer to the function to be called in order to write a property of your subsystem.
+	@param propertyGet A pointer to the function to be called in order to read a property of your subsystem.
+	@param propertyNames An array of all the property names in your subsystem.
+	\ingroup OSCAPI
+
+  \par Example
+  \code
+	// this is where OSC calls us when an incoming message for us has arrived
+	int MySubsystemOsc_ReceiveMessage( int channel, char* message, int length )
+	{
+		int status = Osc_GeneralReceiverHelper( channel, message, length,
+																				5, // our index count
+																				MySubsystemOsc_Name,
+																				MySubsystemOsc_PropertySet, MySubsystemOsc_PropertyGet,
+																				MySubsystemOsc_PropertyNames );
+
+		if ( status != CONTROLLER_OK )
+			return Osc_SendError( channel, MySubsystemOsc_Name, status );
+		return CONTROLLER_OK;
+	}
+  \endcode
+*/
+
+//validIndex is a func to tell if an index is valid... some indexes between 0 and indexCount may not be valid
+int Osc_IndexIntReceiverHelper( int channel, char* message, int length,
+                                int indexCount,  bool (*validIndex)( int index ), char* subsystemName,
+                                int (*propertySet)( int index, int property, int value ),
+                                int (*propertyGet)( int index, int property ),
+                                char* propertyNames[] )
+{
+
+  int i;
+  if( *message == '\0' || *message == ' ' ) // first, try to see if it was an index "help" query
+  {
+    for ( i = 0; i < indexCount; i++ )
+    {
+      if(validIndex == NULL || (*validIndex)(i)){ //use index validator if we have one
+      //list each valid index
+      snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s", subsystemName );
+      Osc_CreateMessage( channel, Osc->scratch1, ",i", i );
+      }
+
+    }
+    return CONTROLLER_OK;
+  }
+ //printf("IIReHelper looking for next slash\r\n");
+  // Look for the next slash - being the one that separates the index
+  // from the property.  Note that this won't go off on a search through the buffer
+  // since there will soon be a string terminator (i.e. a 0)
+  char* prop = strchr( message, '/' );
+  char* propHelp = NULL;
+  if ( prop == NULL ){ //index only, no property found   /led/5
+
+	//  printf("IIRhelper no property found for msg %s\r\n",message);
+     propHelp = strchr(message,'\0');//try to get ptr to end of string
+   //propHelp = message + strlen(message);//go to end of string this might have issues
+
+  }else{ //there is a '/', but is there anything after it?
+
+//	  if( *(prop+1) =='\0'){
+//	 		printf("IIRHelper - message ends in '/' so its malformed\r\n");
+//	 		  return  CONTROLLER_ERROR_BAD_FORMAT;
+//	 	}
+	//  printf("IIRhelper found potential property '%s' for msg %s\r\n",prop+1,message);
+  }
+
+  // Here's where we try to understand what index we got.  In the world of
+  // OSC, this could be a pattern.  So while we could get "0/speed" we could
+  // also get "*/speed" or "[0-4]/speed".  This is kind of a drag, but it is
+  // quite nice from the user's perspective.
+  // So to deal with this take a look at the text "0" or "{1,2}" or whatever
+  // and produce either a nice integer in the simplest case or a set of bits
+  // where each bit corresponds to one of the indicies.  Clearly we don't have
+  // to go crazy, since there are only a small finite number of them.
+  // Osc_NumberMatch() does the work for us, producing either number = -1 and
+  // bits == -1 if there was no index match, or number != -1 for there was a single
+  // number, or bits != -1 if there were several.
+
+  // note that we tweak the string a bit here to make sure the next '/' is not
+  // mixed up with this.  Insert a string terminator.
+ if(prop!=NULL)
+	 *prop = 0;
+
+  //printf("IIRHelper number matching %s to figure out index\r\n",message);
+  int bits;
+  //Need the number even to list properties (for constructing OSC addr)
+  int number = Osc_NumberMatch( indexCount, message, &bits );
+  if ( number == -1 && bits == -1 )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  //printf("IIRHelper number matching returned num=%d bits=%d for message %s \r\n",number,bits,message);
+  // We tweaked the '/' before - now put it back
+  if(prop!=NULL)
+	  *prop = '/';
+
+  //char* propHelp = prop + 1; //set propHelp to end of string
+  // first, try to see if it was an index "help" query
+  //print all properties if we do not have one
+  if( *prop == '\0' || *prop == ' ' || prop==NULL )
+  {
+	 //printf("IIRHelper listing properties for message %s \r\n",message);
+    i = 0;
+    while( true )
+    {
+      if( propertyNames[i] != 0 ) //list each valid property
+      {
+
+        snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s/%d", subsystemName, number );
+        Osc_CreateMessage( channel, Osc->scratch1, ",s", propertyNames[i] );
+
+        i++;
+      }
+      else
+        return CONTROLLER_OK;
+    }
+  }
+
+  // Now that we know where the property is, we can see if we can find it.
+  // This is a little cheap, since we're also implying that there are no
+  // more address terms after the property.  That is, if testing for "speed", while
+  // "speed" would match, "speed/other_stuff" would not.
+  int propertyIndex = Osc_PropertyLookup( propertyNames, prop + 1 ); //do +1 to skip the '/'
+  if ( propertyIndex == -1 ){
+    return CONTROLLER_ERROR_UNKNOWN_PROPERTY;
+  }else{
+	 // printf("IIRH: found prop index %d for property %s\r\n",propertyIndex,prop);
+  }
+
+
+  // Sometime after the address, the data tag begins - this is the description
+  // of the data in the rest of the message.  It starts with a comma.  Return
+  // where it is into 'type'.  If there is no comma, this is bad.
+  char* type = Osc_FindDataTag( message, length );
+  if ( type == NULL )
+    return CONTROLLER_ERROR_NO_TYPE_TAG;
+  //else
+	//  printf("IIRHelper - found typetag %s\r\n",type);
+
+  // We can tell if there's data by seeing if the character after the comma
+  // is a zero or not.
+  if ( type[ 1 ] == 'i' || type[ 1 ] == 'f' )
+  {
+    // If there was int or float data, it was a WRITE.
+    // So, sort of scanff-like, go get the data.  Here we pass in where the data is
+    // thanks to the previous routine and then specify what we expect to find there
+    // in tag terms (i.e. "i", "s", "f" and others).  Finally we pass in a set
+    // of pointers to the data types we want to extract.  Osc_ExtractData()
+    // will rummage around in the message magically grabbing values for you,
+    // reporting how many it got.  It will convert ints and floats if necessary.
+    int value;
+    int count = Osc_ExtractData( type, "i", &value );
+    if ( count != 1 )
+      return CONTROLLER_ERROR_INCORRECT_DATA_TYPE;
+
+    // Now with the data we need to decide what to do with it.
+    // Is there one or many here?
+    if ( number != -1 )
+      (*propertySet)( number, propertyIndex, value );
+    else
+    {
+      int index = 0;
+      while ( bits > 0 && index < indexCount )
+      {
+        if ( bits & 1 )
+          (*propertySet)( index, propertyIndex, value );
+        bits >>= 1;
+        index++;
+      }
+    }
+  }
+  else
+  {
+    // No data, then.  I guess it was a read.  The XXXXOsc getters
+    // take the channel number and use it to call
+    // Osc_CreateMessage() which adds a new message to the outgoing
+    // stack
+    if ( number != -1 )
+    {
+      int value = (*propertyGet)( number, propertyIndex );
+
+      snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s/%d/%s", subsystemName, number, propertyNames[ propertyIndex ] );
+      Osc_CreateMessage( channel, Osc->scratch1, ",i", value );
+
+    }
+    else
+    {
+      int index = 0;
+      while ( bits > 0 && index < indexCount )
+      {
+        if ( bits & 1 )
+        {
+          int value = (*propertyGet)( index, propertyIndex );
+
+          snprintf( Osc->scratch1, OSC_SCRATCH_SIZE, "/%s/%d/%s", subsystemName, index, propertyNames[ propertyIndex ] );
+          Osc_CreateMessage( channel, Osc->scratch1, ",i", value );
+
+        }
+        bits >>= 1;
+        index++;
+      }
+    }
+  }
+
+  return CONTROLLER_OK;
+}
+
+int Osc_SendError( int channel, char* subsystemName, int error )
+{
+  char* errorText;
+  switch ( error )
+  {
+    case CONTROLLER_ERROR_UNKNOWN_PROPERTY:
+      errorText = "Unknown Property";
+      break;
+    case CONTROLLER_ERROR_NO_PROPERTY:
+      errorText = "No Property";
+      break;
+    case CONTROLLER_ERROR_INCORRECT_DATA_TYPE:
+      errorText = "Incorrect Data Type";
+      break;
+    case CONTROLLER_ERROR_ILLEGAL_INDEX:
+      errorText = "Bad Index";
+      break;
+    case CONTROLLER_ERROR_BAD_FORMAT:
+      errorText = "Bad Format";
+      break;
+    case CONTROLLER_ERROR_NO_TYPE_TAG:
+      errorText = "No Type Tag";
+      break;
+    case CONTROLLER_ERROR_BAD_DATA:
+      errorText = "Bad Data";
+      break;
+    default:
+      errorText = "Error";
+      break;
+  }
+  return Osc_SubsystemError( channel, subsystemName, errorText );
+}
+
+// OSC_ExtractData takes a buffer (i.e. a point in the incoming OSC message)
+// And a format e.g. "i" "bb", etc. and unpacks them to the var args
+// The var args need to be pointers to memory ready to receive the values.
+// In the case of blobs, there need to be three parameters: char** buffer,
+// and int* size on the param list.  The buffer gets a pointer into the
+// right place in the incoming buffer and the size value gets assigned
+int Osc_ExtractData( char* buffer, char* format, ... )
+{
+  // Set up to iterate through the arguments
+  va_list args;
+  va_start( args, format );
+  int count = 0;
+
+  // figure out where the data starts
+  int tagLen = strlen( buffer ) + 1;
+  int pad = tagLen % 4;
+  if ( pad != 0 )
+    tagLen += ( 4 - pad );
+  char* data = buffer + tagLen;
+
+  // Going to be walking the tag string, the format string and the data
+  char* fp;
+  char* tp = buffer + 1; // need to skip the comma ','
+  bool cont = true;
+  for ( fp = format; *fp && cont; fp++ )
+  {
+    cont = false;
+    switch ( *fp )
+    {
+      case 'i':
+        if ( *tp == 'i' )
+        {
+          *(va_arg( args, int* )) = Osc_ReadInt( data );
+          data += 4;
+          count++;
+          cont = true;
+        }
+        if ( *tp == 'f' )
+        {
+          *(va_arg( args, int* )) = (int)Osc_ReadFloat( data );
+          data += 4;
+          count++;
+          cont = true;
+        }
+
+        break;
+      case 'f':
+        if ( *tp == 'f' )
+        {
+          *(va_arg( args, float* )) = Osc_ReadFloat( data );
+          data += 4;
+          count++;
+          cont = true;
+        }
+        if ( *tp == 'i' )
+        {
+          *(va_arg( args, float* )) = (float)Osc_ReadInt( data );
+          data += 4;
+          count++;
+          cont = true;
+        }
+        break;
+      case 's':
+        if ( *tp == 's' )
+        {
+          *(va_arg( args, char** )) = data;
+          int len = strlen( data ) + 1;
+          int pad = len % 4;
+          if ( pad != 0 )
+            len += ( 4 - pad );
+          data += len;
+          count++;
+          cont = true;
+        }
+        break;
+      case 'b':
+        if ( *tp == 'b' )
+        {
+          int length = Osc_ReadInt( data );
+          data += 4;
+          *(va_arg( args, char** )) = data;
+          *(va_arg( args, int* )) = length;
+          int pad = length % 4;
+          if ( pad != 0 )
+            length += ( 4 - pad );
+          data += length;
+          count++;
+          cont = true;
+        }
+        else
+        {
+          if ( *tp == 's' )
+          {
+            *(va_arg( args, char** )) = data;
+            int len = strlen( data ) + 1;
+            *(va_arg( args, int* )) = len;
+            int pad = len % 4;
+            if ( pad != 0 )
+              len += ( 4 - pad );
+            data += len;
+            count++;
+            cont = true;
+          }
+        }
+        break;
+    }
+    tp++;
+  }
+
+  //va_end( args );
+
+  return count;
+}
+
+int Osc_ReadInt( char* buffer )
+{
+  int v = *((int*)buffer);
+  v = Osc_EndianSwap( v );
+  return v;
+}
+
+float Osc_ReadFloat( char* buffer )
+{
+  int v = *((int*)buffer);
+  v = Osc_EndianSwap( v );
+  return  *(float*)&v;
+}
+
+/**
+  Osc_CreateMessage
+  Must put the "," as the first format letter
+  */
+int Osc_CreateMessage( int channel, char* address, char* format, ... )
+{
+  if ( address == NULL || format == NULL || *format != ',' )
+    return CONTROLLER_ERROR_BAD_DATA;
+
+  if ( channel < 0 || channel >= OSC_CHANNEL_COUNT )
+    return CONTROLLER_ERROR_ILLEGAL_INDEX;
+
+  OscChannel* ch = Osc->channel[ channel ];
+
+  if ( !ch->running )
+    return CONTROLLER_ERROR_SUBSYSTEM_INACTIVE;
+
+  if ( channel == OSC_CHANNEL_UDP && ch->replyAddress == 0 )
+    return CONTROLLER_ERROR_NO_ADDRESS;
+
+  // Check for sender
+  if ( ch->sendMessage == NULL )
+    return CONTROLLER_ERROR_RESOURCE_MISSING;
+
+
+
+  if ( ch->bufferPointer == NULL )
+    Osc_ResetChannel( ch );
+
+  // try to send this message - if there's a problem somewhere,
+  // send the existing buffer - freeing up space, then try (once) again.
+  int count = 0;
+  char *bp;
+  do
+  {
+    count++;
+
+    char* buffer = ch->bufferPointer;
+    int length = ch->bufferRemaining;
+
+    bp = buffer;
+
+    // First message in the buffer?
+    if ( bp == ch->buffer )
+    {
+      bp = Osc_CreateBundle( bp, &length, 0, 0 );
+      if ( bp == NULL )
+        return CONTROLLER_ERROR_INSUFFICIENT_RESOURCES;
+    }
+
+    // Make room for the new message
+    int* lp = (int *)bp;
+    bp += 4;
+    length -= 4;
+
+    // remember the start of the message
+    char* mp = bp;
+
+    if ( length > 0 )
+    {
+      // Set up to iterate through the arguments
+      va_list args;
+      va_start( args, format );
+
+      bp = Osc_CreateMessageInternal( bp, &length, address, format, args );
+
+      //va_end( args );
+    }
+    else
+      bp = 0;
+
+    if ( bp != 0 )
+    {
+      // Set the size
+      *lp = Osc_EndianSwap( bp - mp );
+
+      ch->bufferPointer = bp;
+      ch->bufferRemaining = length;
+      ch->messages++;
+    }
+    else
+    {
+      Osc_SendPacketInternal( ch );
+    }
+  } while ( bp == 0 && count == 1 );
+
+
+  return ( bp != 0 ) ? CONTROLLER_OK : CONTROLLER_ERROR_ILLEGAL_PARAMETER_VALUE;
+}
+
+int Osc_CreateMessageToBuf( char* bp, int* length, char* address, char* format, ... )
+{
+  if ( address == NULL || format == NULL || *format != ',' )
+    return CONTROLLER_ERROR_BAD_DATA;
+
+  va_list args;
+  va_start( args, format );
+
+  Osc_CreateMessageInternal( bp, length, address, format, args );
+  return CONTROLLER_OK;
+}
+
+char* Osc_CreateMessageInternal( char* bp, int* length, char* address, char* format, va_list args )
+{
+  // do the address
+  bp = Osc_WritePaddedString( bp, length, address );
+  if ( bp == NULL )
+    return 0;
+
+  // do the type
+  bp = Osc_WritePaddedString( bp, length, format );
+  if ( bp == NULL )
+    return 0;
+
+  // Going to be walking the tag string, the format string and the data
+  // skip the ',' comma
+  char* fp;
+  bool cont = true;
+  for ( fp = format + 1; *fp && cont; fp++ )
+  {
+    switch ( *fp )
+    {
+      case 'i':
+          *length -= 4;
+          if ( *length >= 0 )
+          {
+            int v = va_arg( args, int );
+            v = Osc_EndianSwap( v );
+            *((int*)bp) = v;
+            bp += 4;
+          }
+          else
+            cont = false;
+        break;
+      case 'f':
+        *length -= 4;
+        if ( *length >= 0 )
+        {
+          int v;
+          *((float*)&v) = (float)( va_arg( args, double ) );
+          v = Osc_EndianSwap( v );
+          *((int*)bp) = v;
+          bp += 4;
+        }
+        else
+          cont = false;
+        break;
+      case 's':
+      {
+        char* s = va_arg( args, char* );
+        bp = Osc_WritePaddedString( bp, length, s );
+        if ( bp == NULL )
+          cont = false;
+        break;
+      }
+      case 'b':
+      {
+        char* b = va_arg( args, char* );
+        int blen = va_arg( args, int );
+        bp = Osc_WritePaddedBlob( bp, length, b, blen  );
+        if ( bp == NULL )
+          cont = false;
+        break;
+      }
+      default:
+        cont = false;
+    }
+  }
+
+  return ( cont ) ? bp : NULL;
+}
+
+char* Osc_CreateBundle( char* buffer, int* length, int a, int b )
+{
+  char *bp = buffer;
+
+  // do the bundle bit
+  bp = Osc_WritePaddedString( bp, length, "#bundle" );
+  if ( bp == NULL )
+    return 0;
+
+  // do the timetag
+  bp = Osc_WriteTimetag( bp, length, a, b );
+  if ( bp == NULL )
+    return 0;
+
+  return bp;
+}
+
+
+
+int Osc_NumberMatch( int count, char* message, int* bits )
+{
+  int n = 0;
+  int digits = 0;
+  while ( isdigit( *message ) )
+  {
+    digits++;
+    n = n * 10 + ( *message++ - '0' );
+  }
+
+  *bits = -1;
+  if ( n >= count )
+    return -1;
+
+  switch ( *message )
+  {
+    case '*':
+    case '?':
+    case '[':
+    case '{':
+    {
+      int i;
+      int b = 0;
+      char s[ 5 ];
+      for ( i = count - 1; i >=0 ; i-- )
+      {
+        b <<= 1;
+        sprintf( s, "%d", i );
+        if ( Osc_PatternMatch( message, s ) )
+          b |= 1;
+      }
+      *bits = b;
+      return -1;
+    }
+    default:
+      if ( digits == 0 )
+        return -1;
+      return n;
+  }
+}
+
+// Looks the named property up, returning an index
+// Note that we need to be careful - there may be other stuff there in the string
+// Probably best to eventually do something better with it.
+int Osc_PropertyLookup( char** properties, char* property )
+{
+  char** p = properties;
+  int index = 0;
+  while (*p != NULL )
+  {
+    if ( strcmp( property, *p++ ) == 0 )
+      return index;
+    index++;
+  }
+  return -1;
+}
+
+char *Osc_FindDataTag( char* message, int length )
+{
+  while ( *message != ',' && length-- > 0 )
+    message++;
+  if ( length <= 0 )
+    return NULL;
+  else
+    return message;
+}
+
+char* Osc_WritePaddedString( char* buffer, int* length, char* string )
+{
+  int tagLen = strlen( string ) + 1;
+  int tagPadLen = tagLen;
+  int pad = ( tagPadLen ) % 4;
+  if ( pad != 0 )
+    tagPadLen += ( 4 - pad );
+
+  *length -= tagPadLen;
+
+  if ( *length >= 0 )
+  {
+    strcpy( buffer, string );
+    int i;
+    buffer += tagLen;
+    for ( i = tagLen; i < tagPadLen; i++ )
+      *buffer++ = 0;
+  }
+  else
+    return NULL;
+
+  return buffer;
+}
+
+char* Osc_WritePaddedBlob( char* buffer, int* length, char* blob, int blen )
+{
+  int i;
+  int padLength = blen;
+  int pad = ( padLength ) % 4;
+  if ( pad != 0 )
+    padLength += ( 4 - pad );
+
+  if ( *length < ( padLength + 4 ) )
+    return 0;
+
+  // add the length of the blob
+  int l = Osc_EndianSwap( blen );
+  *((int*)buffer) = l;
+  buffer += 4;
+  *length -= 4;
+
+  memcpy( buffer, blob, blen );
+  buffer += blen;
+  // reduce the remaining buffer size
+  *length -= padLength;
+
+  for ( i = blen; i < padLength; i++ )
+      *buffer++ = 0;
+
+  return buffer;
+}
+
+char* Osc_WriteTimetag( char* buffer, int* length, int a, int b )
+{
+  if ( *length < 8 )
+    return NULL;
+
+  *((int*)buffer) = Osc_EndianSwap( a );
+  buffer += 4;
+  *((int*)buffer) = Osc_EndianSwap( b );
+  buffer += 4;
+  *length -= 8;
+
+  return buffer;
+}
+
+int Osc_EndianSwap( int a )
+{
+  return ( ( a & 0x000000FF ) << 24 ) |
+         ( ( a & 0x0000FF00 ) << 8 )  |
+         ( ( a & 0x00FF0000 ) >> 8 )  |
+         ( ( a & 0xFF000000 ) >> 24 );
+
+}
+
+
+
+  
\ No newline at end of file