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.
Diff: osc_sys.cpp
- 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