commit 64ba0a83e394be3472768c0f95d329c5c76b4025 Author: gauthiier Date: Sun Mar 20 12:03:34 2022 +0100 "Initial commit" diff --git a/blocks/OSC/src/cinder/osc/Osc.cpp b/blocks/OSC/src/cinder/osc/Osc.cpp new file mode 100644 index 0000000..866775d --- /dev/null +++ b/blocks/OSC/src/cinder/osc/Osc.cpp @@ -0,0 +1,1803 @@ +/* + Copyright (c) 2015, The Cinder Project, All rights reserved. + + This code is intended for use with the Cinder C++ library: http://libcinder.org + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that + the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Toshiro Yamada + Portions Copyright (c) 2011 + Distributed under the BSD-License. + https://github.com/toshiroyamada/tnyosc + */ + +#include "Osc.h" +#include "cinder/Log.h" + +using namespace std; +using namespace asio; +using namespace asio::ip; +using namespace std::placeholders; + +// Following code snippets were taken from +// http://www.jbox.dk/sanos/source/include/net/inet.h.html +#ifndef htons +#define htons(n) (((((uint16_t)(n) & 0xFF)) << 8) | (((uint16_t)(n) & 0xFF00) >> 8)) +#endif +#ifndef ntohs +#define ntohs(n) (((((uint16_t)(n) & 0xFF)) << 8) | (((uint16_t)(n) & 0xFF00) >> 8)) +#endif +#ifndef htonl +#define htonl(n) (((((uint32_t)(n) & 0xFF)) << 24) | ((((uint32_t)(n) & 0xFF00)) << 8) | ((((uint32_t)(n) & 0xFF0000)) >> 8) | ((((uint32_t)(n) & 0xFF000000)) >> 24)) +#endif +#ifndef ntohl +#define ntohl(n) (((((uint32_t)(n) & 0xFF)) << 24) | ((((uint32_t)(n) & 0xFF00)) << 8) | ((((uint32_t)(n) & 0xFF0000)) >> 8) | ((((uint32_t)(n) & 0xFF000000)) >> 24)) +#endif + +// Following ntohll() and htonll() code snippets were taken from +// http://www.codeproject.com/KB/cpp/endianness.aspx?msg=1661457 +#ifndef ntohll +/// Convert 64-bit little-endian integer to a big-endian network format +#define ntohll(x) (((int64_t)(ntohl((int32_t)((x << 32) >> 32))) << 32) | (uint32_t)ntohl(((int32_t)(x >> 32)))) +#endif +#ifndef htonll +/// Convert 64-bit big-endian network format to a little-endian integer +#define htonll(x) ntohll(x) +#endif + +namespace cinder { +namespace osc { + +/// Convert 32-bit float to a big-endian network format +inline int32_t htonf( float x ) { return (int32_t) htonl( *(int32_t*) &x ); } +/// Convert 64-bit float (double) to a big-endian network format +inline int64_t htond( double x ) { return (int64_t) htonll( *(int64_t*) &x ); } +/// Convert 32-bit big-endian network format to float +inline double ntohf( int32_t x ) { x = ntohl( x ); return *(float*) &x; } +/// Convert 64-bit big-endian network format to double +inline double ntohd( int64_t x ) { return (double) ntohll( x ); } + +//////////////////////////////////////////////////////////////////////////////////////// +//// MESSAGE + +Message::Message() +: mIsCached( false ), mSenderPort( 0 ) +{ +} + +Message::Message( const std::string& address ) +: mAddress( address ), mIsCached( false ), mSenderPort( 0 ) +{ +} + +Message::Message( Message &&message ) NOEXCEPT +: mAddress( move( message.mAddress ) ), mDataBuffer( move( message.mDataBuffer ) ), + mDataViews( move( message.mDataViews ) ), mIsCached( message.mIsCached ), + mCache( move( message.mCache ) ), mSenderIpAddress( move( message.mSenderIpAddress ) ), + mSenderPort( message.mSenderPort ) +{ + for( auto & dataView : mDataViews ) { + dataView.mOwner = this; + } +} + +Message& Message::operator=( Message &&message ) NOEXCEPT +{ + if( this != &message ) { + mAddress = move( message.mAddress ); + mDataBuffer = move( message.mDataBuffer ); + mDataViews = move( message.mDataViews ); + mIsCached = message.mIsCached; + mCache = move( message.mCache ); + mSenderIpAddress = move( message.mSenderIpAddress ); + mSenderPort = message.mSenderPort; + for( auto & dataView : mDataViews ) { + dataView.mOwner = this; + } + } + return *this; +} + +Message::Message( const Message &message ) +: mAddress( message.mAddress ), mDataBuffer( message.mDataBuffer ), + mDataViews( message.mDataViews ), mIsCached( message.mIsCached ), + mCache( mIsCached ? new ByteBuffer( *(message.mCache) ) : nullptr ), + mSenderIpAddress( message.mSenderIpAddress ), + mSenderPort( message.mSenderPort ) +{ + for( auto & dataView : mDataViews ) { + dataView.mOwner = this; + } +} + +Message& Message::operator=( const Message &message ) +{ + if( this != &message ) { + mAddress = message.mAddress; + mDataBuffer = message.mDataBuffer; + mDataViews = message.mDataViews; + mIsCached = message.mIsCached; + mCache.reset( mIsCached ? new ByteBuffer( *(message.mCache) ) : nullptr ); + mSenderIpAddress = message.mSenderIpAddress; + mSenderPort = message.mSenderPort; + for( auto & dataView : mDataViews ) { + dataView.mOwner = this; + } + } + return *this; +} + +using Argument = Message::Argument; + +Argument::Argument() +: mOwner( nullptr ), mType( ArgType::NULL_T ), mSize( 0 ), mOffset( -1 ) +{ +} + +Argument::Argument( Message *owner, ArgType type, int32_t offset, uint32_t size, bool needsSwap ) +: mOwner( owner ), mType( type ), mOffset( offset ), + mSize( size ), mNeedsEndianSwapForTransmit( needsSwap ) +{ +} + +Argument::Argument( Argument &&arg ) NOEXCEPT +: mOwner( arg.mOwner ), mType( arg.mType ), mOffset( arg.mOffset ), + mSize( arg.mSize ), mNeedsEndianSwapForTransmit( arg.mNeedsEndianSwapForTransmit ) +{ +} + +Argument& Argument::operator=( Argument &&arg ) NOEXCEPT +{ + if( this != &arg ) { + mOwner = arg.mOwner; + mType = arg.mType; + mOffset = arg.mOffset; + mSize = arg.mSize; + mNeedsEndianSwapForTransmit = arg.mNeedsEndianSwapForTransmit; + } + return *this; +} + +Argument::Argument( const Argument &arg ) +: mOwner( arg.mOwner ), mType( arg.mType ), mOffset( arg.mOffset ), +mSize( arg.mSize ), mNeedsEndianSwapForTransmit( arg.mNeedsEndianSwapForTransmit ) +{ +} + +Argument& Argument::operator=( const Argument &arg ) +{ + if( this != &arg ) { + mOwner = arg.mOwner; + mType = arg.mType; + mOffset = arg.mOffset; + mSize = arg.mSize; + mNeedsEndianSwapForTransmit = arg.mNeedsEndianSwapForTransmit; + } + return *this; +} + +bool Argument::operator==(const Argument &arg ) const +{ + return mType == arg.mType && + mOffset == arg.mOffset && + mSize == arg.mSize; +} + +const char* Message::Argument::argTypeToString( ArgType type ) +{ + switch ( type ) { + case ArgType::INTEGER_32: return "INTEGER_32"; break; + case ArgType::FLOAT: return "FLOAT"; break; + case ArgType::DOUBLE: return "DOUBLE"; break; + case ArgType::STRING: return "STRING"; break; + case ArgType::BLOB: return "BLOB"; break; + case ArgType::MIDI: return "MIDI"; break; + case ArgType::TIME_TAG: return "TIME_TAG"; break; + case ArgType::INTEGER_64: return "INTEGER_64"; break; + case ArgType::BOOL_T: return "BOOL_T"; break; + case ArgType::BOOL_F: return "BOOL_F"; break; + case ArgType::CHAR: return "CHAR"; break; + case ArgType::NULL_T: return "NULL_T"; break; + case ArgType::IMPULSE: return "IMPULSE"; break; + default: return "Unknown ArgType"; break; + } +} + +ArgType Argument::translateCharTypeToArgType( char type ) +{ + return static_cast(type); +} + +char Argument::translateArgTypeToCharType( ArgType type ) +{ + return static_cast(type); +} + +void Argument::swapEndianForTransmit( uint8_t *buffer ) const +{ + auto ptr = &buffer[mOffset]; + switch ( mType ) { + case ArgType::INTEGER_32: + case ArgType::CHAR: + case ArgType::BLOB: { + int32_t a = htonl( *reinterpret_cast(ptr) ); + memcpy( ptr, &a, sizeof( int32_t ) ); + } + break; + case ArgType::INTEGER_64: + case ArgType::TIME_TAG: { + uint64_t a = htonll( *reinterpret_cast(ptr) ); + memcpy( ptr, &a, sizeof( uint64_t ) ); + } + break; + case ArgType::FLOAT: { + int32_t a = htonf( *reinterpret_cast(ptr) ); + memcpy( ptr, &a, sizeof( float ) ); + } + break; + case ArgType::DOUBLE: { + int64_t a = htond( *reinterpret_cast(ptr) ); + memcpy( ptr, &a, sizeof( double ) ); + } + break; + default: break; + } +} + +void Argument::outputValueToStream( std::ostream &ostream ) const +{ + ostream << "<" << argTypeToString( mType ) << ">: "; + switch ( mType ) { + case ArgType::INTEGER_32: ostream << *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::FLOAT: ostream << *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::STRING: ostream << reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::BLOB: ostream << "Size: " << *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::INTEGER_64: ostream << *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::TIME_TAG: ostream << *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::DOUBLE: ostream << *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); break; + case ArgType::CHAR: { + char v = *reinterpret_cast( &mOwner->mDataBuffer[mOffset] ); + ostream << int(v); + } + break; + case ArgType::MIDI: { + auto ptr = &mOwner->mDataBuffer[mOffset]; + ostream << " Port: " << int( *( ptr + 0 ) ) << + " Status: " << int( *( ptr + 1 ) ) << + " Data1: " << int( *( ptr + 2 ) ) << + " Data2: " << int( *( ptr + 3 ) ) ; + } + break; + case ArgType::BOOL_T: ostream << "True"; break; + case ArgType::BOOL_F: ostream << "False"; break; + case ArgType::NULL_T: ostream << "Null"; break; + case ArgType::IMPULSE: ostream << "IMPULSE"; break; + default: ostream << "Unknown"; break; + } +} + +void Message::append( int32_t v ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::INTEGER_32, getCurrentOffset(), 4, true ); + appendDataBuffer( &v, sizeof(int32_t) ); +} + +void Message::append( float v ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::FLOAT, getCurrentOffset(), 4, true ); + appendDataBuffer( &v, sizeof(float) ); +} + +void Message::append( const std::string& v ) +{ + mIsCached = false; + auto trailingZeros = getTrailingZeros( v.size() ); + auto size = v.size() + trailingZeros; + CI_ASSERT_MSG( size <= std::numeric_limits::max(), + "Argument size must fit in uint32_t" ); + mDataViews.emplace_back( this, ArgType::STRING, getCurrentOffset(), static_cast( size ) ); + appendDataBuffer( v.data(), v.size(), trailingZeros ); +} + +void Message::append( const char *v ) +{ + mIsCached = false; + auto stringLength = strlen( v ); + auto trailingZeros = getTrailingZeros( stringLength ); + auto size = stringLength + trailingZeros; + CI_ASSERT_MSG( size <= std::numeric_limits::max(), + "Argument size must fit in uint32_t" ); + mDataViews.emplace_back( this, ArgType::STRING, getCurrentOffset(), static_cast( size ) ); + appendDataBuffer( v, stringLength, trailingZeros ); +} + +void Message::appendBlob( void* blob, uint32_t size ) +{ + mIsCached = false; + auto trailingZeros = getTrailingZeros( size ); + mDataViews.emplace_back( this, ArgType::BLOB, getCurrentOffset(), size, true ); + appendDataBuffer( &size, sizeof(uint32_t) ); + appendDataBuffer( blob, size, trailingZeros ); +} + +void Message::append( const ci::Buffer &buffer ) +{ + CI_ASSERT_MSG( buffer.getSize() <= std::numeric_limits::max(), + "Blob size must fit in uint32_t" ); + appendBlob( (void*)buffer.getData(), static_cast( buffer.getSize() ) ); +} + +void Message::appendTimeTag( uint64_t v ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::TIME_TAG, getCurrentOffset(), 8, true ); + appendDataBuffer( &v, sizeof( uint64_t ) ); +} + +void Message::appendCurrentTime() +{ + appendTimeTag( time::get_current_ntp_time() ); +} + +void Message::append( bool v ) +{ + mIsCached = false; + if( v ) + mDataViews.emplace_back( this, ArgType::BOOL_T, -1, 0 ); + else + mDataViews.emplace_back( this, ArgType::BOOL_F, -1, 0 ); +} + +void Message::append( int64_t v ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::INTEGER_64, getCurrentOffset(), 8, true ); + appendDataBuffer( &v, sizeof( int64_t ) ); +} + +void Message::append( double v ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::DOUBLE, getCurrentOffset(), 8, true ); + appendDataBuffer( &v, sizeof( double ) ); +} + +void Message::append( char v ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::CHAR, getCurrentOffset(), 4, true ); + ByteArray<4> b; + b.fill( 0 ); + b[0] = v; + appendDataBuffer( b.data(), b.size() ); +} + +void Message::appendMidi( uint8_t port, uint8_t status, uint8_t data1, uint8_t data2 ) +{ + mIsCached = false; + mDataViews.emplace_back( this, ArgType::MIDI, getCurrentOffset(), 4 ); + ByteArray<4> b; + b[0] = port; + b[1] = status; + b[2] = data1; + b[3] = data2; + appendDataBuffer( b.data(), b.size() ); +} + +void Message::createCache() const +{ + // Check for debug to allow for Default Constructing. + CI_ASSERT_MSG( mAddress.size() > 0 && mAddress[0] == '/', + "All OSC Address Patterns must at least start with '/' (forward slash)" ); + + size_t addressLen = mAddress.size() + getTrailingZeros( mAddress.size() ); + // adding one for ',' character, which was the sourc of a particularly ugly bug + auto typesSize = mDataViews.size() + 1; + std::vector typesArray( typesSize + getTrailingZeros( typesSize ) , 0 ); + + typesArray[0] = ','; + int i = 1; + for( auto & dataView : mDataViews ) + typesArray[i++] = Argument::translateArgTypeToCharType( dataView.getType() ); + + if( ! mCache ) + mCache = ByteBufferRef( new ByteBuffer() ); + + size_t typesArrayLen = typesArray.size(); + ByteArray<4> sizeArray; + CI_ASSERT_MSG( addressLen + typesArrayLen + mDataBuffer.size() <= std::numeric_limits::max(), + "Message size must fit in int32_t" ); + int32_t messageSize = static_cast(addressLen + typesArrayLen + mDataBuffer.size()); + auto endianSize = htonl( messageSize ); + memcpy( sizeArray.data(), reinterpret_cast( &endianSize ), 4 ); + + mCache->resize( 4 + messageSize ); + + std::copy( sizeArray.begin(), sizeArray.end(), mCache->begin() ); + std::copy( mAddress.begin(), mAddress.end(), mCache->begin() + 4 ); + std::copy( typesArray.begin(), typesArray.end(), mCache->begin() + 4 + addressLen ); + std::copy( mDataBuffer.begin(), mDataBuffer.end(), mCache->begin() + 4 + addressLen + typesArrayLen ); + + // Now that cached (transportable) buffer is created, swap endian for transmit. + auto dataPtr = mCache->data() + 4 + addressLen + typesArrayLen; + for( auto & dataView : mDataViews ) { + if( dataView.needsEndianSwapForTransmit() ) + dataView.swapEndianForTransmit( dataPtr ); + } + + mIsCached = true; +} + +ByteBufferRef Message::getSharedBuffer() const +{ + if( ! mIsCached ) + createCache(); + return mCache; +} + +template +const Argument& Message::getDataView( uint32_t index ) const +{ + if( index >= mDataViews.size() ) + throw ExcIndexOutOfBounds( mAddress, index ); + + return mDataViews[index]; +} + +void Message::appendDataBuffer( const void *begin, size_t size, uint32_t trailingZeros ) +{ + auto ptr = reinterpret_cast( begin ); + mDataBuffer.insert( mDataBuffer.end(), ptr, ptr + size ); + if( trailingZeros != 0 ) + mDataBuffer.resize( mDataBuffer.size() + trailingZeros, 0 ); +} + +const Argument& Message::operator[]( uint32_t index ) const +{ + if( index >= mDataViews.size() ) + throw ExcIndexOutOfBounds( mAddress, index ); + + return mDataViews[index]; +} + +bool Message::operator==( const Message &message ) const +{ + auto sameAddress = message.mAddress == mAddress; + if( ! sameAddress ) + return false; + + auto sameDataViewSize = message.mDataViews.size() == mDataViews.size(); + if( ! sameDataViewSize ) + return false; + for( size_t i = 0; i < mDataViews.size(); i++ ) { + auto sameDataView = message.mDataViews[i] == mDataViews[i]; + if( ! sameDataView ) + return false; + } + + auto sameDataBufferSize = mDataBuffer.size() == message.mDataBuffer.size(); + if( ! sameDataBufferSize ) + return false; + auto sameDataBuffer = ! memcmp( mDataBuffer.data(), message.mDataBuffer.data(), mDataBuffer.size() ); + if( ! sameDataBuffer ) + return false; + + return true; +} + +bool Message::operator!=( const Message &message ) const +{ + return ! (*this == message); +} + +std::string Message::getTypeTagString() const +{ + std::string ret( mDataViews.size(), 0 ); + std::transform( mDataViews.begin(), mDataViews.end(), ret.begin(), + []( const Argument &arg ){ + return Argument::translateArgTypeToCharType( arg.getType() ); + }); + return ret; +} + +int32_t Argument::int32() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::INTEGER_32, getType() ); + + return *reinterpret_cast(&mOwner->mDataBuffer[getOffset()]);; +} + +int64_t Argument::int64() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::INTEGER_64, getType() ); + + return *reinterpret_cast(&mOwner->mDataBuffer[getOffset()]);; +} + +float Argument::flt() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::FLOAT, getType() ); + + return *reinterpret_cast(&mOwner->mDataBuffer[getOffset()]);; +} + +double Argument::dbl() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::DOUBLE, getType() ); + + return *reinterpret_cast(&mOwner->mDataBuffer[getOffset()]);; +} + +bool Argument::boolean() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::BOOL_T, getType() ); + + return getType() == ArgType::BOOL_T; +} + +void Argument::midi( uint8_t *port, uint8_t *status, uint8_t *data1, uint8_t *data2 ) const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::MIDI, getType() ); + + int32_t midiVal = *reinterpret_cast(&mOwner->mDataBuffer[getOffset()]); + *port = midiVal; + *status = midiVal >> 8; + *data1 = midiVal >> 16; + *data2 = midiVal >> 24; +} + +ci::Buffer Argument::blob() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::BLOB, getType() ); + + // skip the first 4 bytes, as they are the size + const uint8_t* data = reinterpret_cast( &mOwner->mDataBuffer[getOffset()+4] ); + ci::Buffer ret( getSize() ); + memcpy( ret.getData(), data, getSize() ); + return ret; +} + +void Argument::blobData( const void **dataPtr, size_t *size ) const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::BLOB, getType() ); + + // skip the first 4 bytes, as they are the size + *dataPtr = reinterpret_cast( &mOwner->mDataBuffer[getOffset()+4] ); + *size = getSize(); +} + +char Argument::character() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::CHAR, getType() ); + + return mOwner->mDataBuffer[getOffset()]; +} + +std::string Argument::string() const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::STRING, getType() ); + + const char* head = reinterpret_cast(&mOwner->mDataBuffer[getOffset()]); + return std::string( head ); +} + +void Argument::stringData( const char **dataPtr, uint32_t *size ) const +{ + if( ! convertible() ) + throw ExcNonConvertible( mOwner->getAddress(), ArgType::STRING, getType() ); + + *dataPtr = reinterpret_cast(&mOwner->mDataBuffer[getOffset()]); + *size = mSize; +} + +template +bool Argument::convertible() const +{ + switch ( mType ) { + case ArgType::INTEGER_32: return std::is_same::value; + case ArgType::FLOAT: return std::is_same::value; + case ArgType::STRING: return std::is_same::value; + case ArgType::BLOB: return std::is_same::value; + case ArgType::INTEGER_64: return std::is_same::value; + case ArgType::TIME_TAG: return std::is_same::value; + case ArgType::DOUBLE: return std::is_same::value; + case ArgType::CHAR: return std::is_same::value; + case ArgType::MIDI: return std::is_same::value; + case ArgType::BOOL_T: return std::is_same::value; + case ArgType::BOOL_F: return std::is_same::value; + case ArgType::NULL_T: return false; + case ArgType::IMPULSE: return false; + default: return false; + } +} + +ArgType Message::getArgType( uint32_t index ) const +{ + if( index >= mDataViews.size() ) + throw ExcIndexOutOfBounds( mAddress, index ); + + auto &dataView = mDataViews[index]; + return dataView.getType(); +} + +int32_t Message::getArgInt32( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.int32(); +} + +float Message::getArgFloat( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.flt(); +} + +std::string Message::getArgString( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.string(); +} + +void Message::getArgStringData( uint32_t index, const char **dataPtr, uint32_t *size ) const +{ + auto &dataView = getDataView( index ); + dataView.stringData( dataPtr, size ); +} + +int64_t Message::getArgTime( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.int64(); +} + +int64_t Message::getArgInt64( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.int64(); +} + +double Message::getArgDouble( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.dbl(); +} + +bool Message::getArgBool( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.boolean(); +} + +char Message::getArgChar( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.character(); +} + +void Message::getArgMidi( uint32_t index, uint8_t *port, uint8_t *status, uint8_t *data1, uint8_t *data2 ) const +{ + auto &dataView = getDataView( index ); + dataView.midi( port, status, data1, data2 ); +} + +ci::Buffer Message::getArgBlob( uint32_t index ) const +{ + auto &dataView = getDataView( index ); + return dataView.blob(); +} + +void Message::getArgBlobData( uint32_t index, const void **dataPtr, size_t *size ) const +{ + auto &dataView = getDataView( index ); + dataView.blobData( dataPtr, size ); +} + +bool Message::bufferCache( uint8_t *data, size_t size ) +{ + uint8_t *head, *tail; + uint32_t i = 0; + size_t remain = size; + + // extract address + head = tail = data; + while( tail[i] != '\0' && ++i < remain ); + if( i == remain ) { + CI_LOG_E( "Problem Parsing Message: No address." ); + return false; + } + + mAddress.insert( 0, (char*)head, i ); + + head += i + getTrailingZeros( i ); + remain = size - ( head - data ); + + i = 0; + tail = head; + if( head[i++] != ',' ) { + CI_LOG_E( "Problem Parsing Message: Mesage with address [" << mAddress << "] not properly formatted; no , seperator." ); + return false; + } + + // extract types + while( tail[i] != '\0' && ++i < remain ); + if( i == remain ) { + CI_LOG_E( "Problem Parsing Message: Mesage with address [" << mAddress << "] not properly formatted; Types not complete." ); + return false; + } + + std::vector types( i - 1 ); + std::copy( head + 1, head + i, types.begin() ); + head += i + getTrailingZeros( i ); + remain = size - ( head - data ); + + // extract data + uint32_t int32; + uint64_t int64; + + mDataViews.resize( types.size() ); + int j = 0; + for( auto & dataView : mDataViews ) { + dataView.mOwner = this; + dataView.mType = Argument::translateCharTypeToArgType( types[j] ); + switch( types[j] ) { + case 'i': + case 'f': + case 'r': { + dataView.mSize = sizeof( uint32_t ); + dataView.mOffset = getCurrentOffset(); + memcpy( &int32, head, sizeof( uint32_t ) ); + int32 = htonl( int32 ); + appendDataBuffer( &int32, sizeof( uint32_t ) ); + head += sizeof( uint32_t ); + remain -= sizeof( uint32_t ); + } + break; + case 'b': { + memcpy( &int32, head, 4 ); + head += 4; + remain -= 4; + int32 = htonl( int32 ); + if( int32 > remain ) { + CI_LOG_E( "Problem Parsing Message: Mesage with address [" << mAddress << "] not properly formatted; Blobs size is too long." ); + return false; + } + auto trailingZeros = getTrailingZeros( int32 ); + dataView.mSize = int32; + dataView.mOffset = getCurrentOffset(); + appendDataBuffer( &dataView.mSize, sizeof( uint32_t ) ); + appendDataBuffer( head, int32, trailingZeros ); + head += int32 + trailingZeros; + remain -= int32 + trailingZeros; + } + break; + case 's': + case 'S': { + tail = head; + i = 0; + while( tail[i] != '\0' && ++i < remain ); + dataView.mSize = i + getTrailingZeros( i ); + dataView.mOffset = getCurrentOffset(); + appendDataBuffer( head, i, getTrailingZeros( i ) ); + i += getTrailingZeros( i ); + head += i; + remain -= i; + } + break; + case 'h': + case 'd': + case 't': { + memcpy( &int64, head, sizeof( uint64_t ) ); + int64 = htonll( int64 ); + dataView.mSize = sizeof( uint64_t ); + dataView.mOffset = getCurrentOffset(); + appendDataBuffer( &int64, sizeof( uint64_t ) ); + head += sizeof( uint64_t ); + remain -= sizeof( uint64_t ); + } + break; + case 'c': { + dataView.mSize = 4; + dataView.mOffset = getCurrentOffset(); + memcpy( &int32, head, 4 ); + auto character = (int) htonl( int32 ); + appendDataBuffer( &character, 4 ); + head += sizeof( int ); + remain -= sizeof( int ); + } + break; + case 'm': { + dataView.mSize = sizeof( int ); + dataView.mOffset = getCurrentOffset(); + appendDataBuffer( head, sizeof( int ) ); + head += sizeof( int ); + remain -= sizeof( int ); + } + break; + } + j++; + } + + return true; +} + +void Message::setAddress( const std::string& address ) +{ + mIsCached = false; + mAddress = address; +} + +size_t Message::getPacketSize() const +{ + if( ! mIsCached ) + createCache(); + return mCache->size(); +} + +void Message::clear() +{ + mIsCached = false; + mAddress.clear(); + mDataViews.clear(); + mDataBuffer.clear(); + mCache.reset(); +} + +std::ostream& operator<<( std::ostream &os, const Message &rhs ) +{ + os << "Address: " << rhs.getAddress() << std::endl; + if( ! rhs.getSenderIpAddress().is_unspecified() ) + os << "Sender Ip Address: " << rhs.getSenderIpAddress() << " Port: " << rhs.getSenderPort() << std::endl; + for( auto &dataView : rhs.mDataViews ) + os << "\t" << dataView << std::endl; + return os; +} + +std::ostream& operator<<( std::ostream &os, const Argument &rhs ) +{ + rhs.outputValueToStream( os ); + return os; +} + +//////////////////////////////////////////////////////////////////////////////////////// +//// Bundle + +Bundle::Bundle() +{ + initializeBuffer(); +} + +void Bundle::setTimetag( uint64_t ntp_time ) +{ + uint64_t a = htonll( ntp_time ); + ByteArray<8> b; + memcpy( b.data(), reinterpret_cast( &a ), 8 ); + mDataBuffer->insert( mDataBuffer->begin() + 12, b.begin(), b.end() ); +} + +void Bundle::initializeBuffer() +{ + static const std::string id = "#bundle"; + mDataBuffer.reset( new std::vector( 20 ) ); + std::copy( id.begin(), id.end(), mDataBuffer->begin() + 4 ); + (*mDataBuffer)[19] = 1; +} + +void Bundle::appendData( const ByteBufferRef& data ) +{ + // Size is already the first 4 bytes of every message. + mDataBuffer->insert( mDataBuffer->end(), data->begin(), data->end() ); +} + +ByteBufferRef Bundle::getSharedBuffer() const +{ + int32_t a = htonl( getPacketSize() - 4 ); + memcpy( mDataBuffer->data(), reinterpret_cast( &a ), 4 ); + return mDataBuffer; +} + +//////////////////////////////////////////////////////////////////////////////////////// +//// SenderBase + +std::string SenderBase::extractOscAddress( const ByteBufferRef &transportData ) +{ + std::string oscAddress; + auto foundBegin = find( transportData->begin(), transportData->end(), (uint8_t)'/' ); + if( foundBegin != transportData->end() ) { + auto foundEnd = find( foundBegin, transportData->end(), 0 ); + oscAddress = std::string( foundBegin, foundEnd ); + } + return oscAddress; +} + +void SenderBase::send( const Message &message, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) +{ + sendImpl( message.getSharedBuffer(), std::move( onErrorFn ), std::move( onCompleteFn ) ); +} + +void SenderBase::send( const Bundle &bundle, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) +{ + sendImpl( bundle.getSharedBuffer(), std::move( onErrorFn ), std::move( onCompleteFn ) ); +} + +//////////////////////////////////////////////////////////////////////////////////////// +//// SenderUdp + +SenderUdp::SenderUdp( uint16_t localPort, const std::string &destinationHost, uint16_t destinationPort, const protocol &protocol, asio::io_context &service ) +: mSocket( new udp::socket( service ) ), mLocalEndpoint( protocol, localPort ), + mRemoteEndpoint( udp::endpoint( address::from_string( destinationHost ), destinationPort ) ) +{ +} + +SenderUdp::SenderUdp( uint16_t localPort, const protocol::endpoint &destination, const protocol &protocol, asio::io_context &service ) +: mSocket( new udp::socket( service ) ), mLocalEndpoint( protocol, localPort ), + mRemoteEndpoint( destination ) +{ +} + +SenderUdp::SenderUdp( const UdpSocketRef &socket, const protocol::endpoint &destination ) +: mSocket( socket ), mLocalEndpoint( socket->local_endpoint() ), mRemoteEndpoint( destination ) +{ +} + +void SenderUdp::bindImpl() +{ + asio::error_code ec; + mSocket->open( mLocalEndpoint.protocol(), ec ); + if( ec ) + throw osc::Exception( ec ); + + mSocket->bind( mLocalEndpoint, ec ); + if( ec ) + throw osc::Exception( ec ); +} + +void SenderUdp::sendImpl( const ByteBufferRef &data, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) +{ + if( ! mSocket->is_open() ) + return; + + // data's first 4 bytes(int) comprise the size of the buffer, which datagram doesn't need. + mSocket->async_send_to( asio::buffer( data->data() + 4, data->size() - 4 ), mRemoteEndpoint, + // copy data pointer to persist the asynchronous send + [&, data, onErrorFn, onCompleteFn]( const asio::error_code& error, size_t bytesTransferred ) + { + if( error ) { + if( onErrorFn ) + onErrorFn( error ); + else + CI_LOG_E( "Udp Send: " << error.message() << " - Code: " << error.value() ); + } + else if( onCompleteFn ) { + onCompleteFn(); + } + }); +} + +void SenderUdp::closeImpl() +{ + asio::error_code ec; + mSocket->close( ec ); + if( ec ) + throw osc::Exception( ec ); +} + +//////////////////////////////////////////////////////////////////////////////////////// +//// SenderTcp + +SenderTcp::SenderTcp( uint16_t localPort, const string &destinationHost, uint16_t destinationPort, const protocol &protocol, io_context &service, PacketFramingRef packetFraming ) +: mSocket( new tcp::socket( service ) ), mPacketFraming( packetFraming ), mLocalEndpoint( protocol, localPort ), + mRemoteEndpoint( tcp::endpoint( address::from_string( destinationHost ), destinationPort ) ) +{ +} + +SenderTcp::SenderTcp( uint16_t localPort, const protocol::endpoint &destination, const protocol &protocol, io_context &service, PacketFramingRef packetFraming ) +: mSocket( new tcp::socket( service ) ), mPacketFraming( packetFraming ), mLocalEndpoint( protocol, localPort ), mRemoteEndpoint( destination ) +{ +} + +SenderTcp::SenderTcp( const TcpSocketRef &socket, const protocol::endpoint &destination, PacketFramingRef packetFraming ) +: mSocket( socket ), mPacketFraming( packetFraming ), mLocalEndpoint( socket->local_endpoint() ), mRemoteEndpoint( destination ) +{ +} + +SenderTcp::~SenderTcp() +{ + try { + SenderTcp::closeImpl(); + } + catch( osc::Exception ex ) { + CI_LOG_EXCEPTION( "Closing TCP Sender", ex ); + } +} + +void SenderTcp::bindImpl() +{ + asio::error_code ec; + mSocket->open( mLocalEndpoint.protocol(), ec ); + if( ec ) + throw osc::Exception( ec ); + + mSocket->bind( mLocalEndpoint, ec ); + if( ec ) + throw osc::Exception( ec ); +} + +void SenderTcp::connect( OnConnectFn onConnectFn ) +{ + if( ! mSocket->is_open() ) { + CI_LOG_E( "Socket not open." ); + return; + } + + mSocket->async_connect( mRemoteEndpoint, + [&, onConnectFn]( const asio::error_code &error ){ + if( onConnectFn ) + onConnectFn( error ); + else if( error ) + CI_LOG_E( "Asio Error: " << error.message() << " - Code: " << error.value() ); + }); +} + +void SenderTcp::shutdown( asio::socket_base::shutdown_type shutdownType ) +{ + if( ! mSocket->is_open() ) + return; + + asio::error_code ec; + mSocket->shutdown( shutdownType, ec ); + // the other side may have already shutdown the connection. + if( ec == asio::error::not_connected ) + ec = asio::error_code(); + if( ec ) + throw osc::Exception( ec ); +} + +void SenderTcp::sendImpl( const ByteBufferRef &data, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) +{ + if( ! mSocket->is_open() ) + return; + + ByteBufferRef transportData = data; + if( mPacketFraming ) + transportData = mPacketFraming->encode( transportData ); + mSocket->async_send( asio::buffer( *transportData ), + // copy data pointer to persist the asynchronous send + [&, transportData, onErrorFn, onCompleteFn]( const asio::error_code& error, size_t bytesTransferred ) + { + if( error ) { + if( onErrorFn ) + onErrorFn( error ); + else + CI_LOG_E( "Tcp Send: " << error.message() << " - Code: " << error.value() ); + } + else if( onCompleteFn ) { + onCompleteFn(); + } + }); +} + +void SenderTcp::closeImpl() +{ + shutdown(); + asio::error_code ec; + mSocket->close( ec ); + if( ec ) + throw osc::Exception( ec ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +//// ReceiverBase + +void ReceiverBase::setListener( const std::string &address, ListenerFn listener ) +{ + std::lock_guard lock( mListenerMutex ); + auto foundListener = std::find_if( mListeners.begin(), mListeners.end(), + [address]( const std::pair &listener ) { + return address == listener.first; + }); + if( foundListener != mListeners.end() ) + foundListener->second = listener; + else + mListeners.push_back( { address, listener } ); +} + +void ReceiverBase::removeListener( const std::string &address ) +{ + std::lock_guard lock( mListenerMutex ); + auto foundListener = std::find_if( mListeners.begin(), mListeners.end(), + [address]( const std::pair &listener ) { + return address == listener.first; + }); + if( foundListener != mListeners.end() ) + mListeners.erase( foundListener ); +} + +void ReceiverBase::dispatchMethods( uint8_t *data, uint32_t size, const asio::ip::address &senderIpAddress, uint16_t senderPort ) +{ + std::vector messages; + decodeData( data, size, messages ); + if( messages.empty() ) + return; + + std::lock_guard lock( mListenerMutex ); + // iterate through all the messages and find matches with registered methods + for( auto & message : messages ) { + bool dispatchedOnce = false; + auto &address = message.getAddress(); + message.mSenderIpAddress = senderIpAddress; + message.mSenderPort = senderPort; + for( auto & listener : mListeners ) { + if( patternMatch( address, listener.first ) ) { + listener.second( message ); + dispatchedOnce = true; + } + } + if( ! dispatchedOnce ) { + if( mDisregardedAddresses.count( address ) == 0 ) { + mDisregardedAddresses.insert( address ); + CI_LOG_W("Message: " << address << " doesn't have a listener. Disregarding."); + } + } + } +} + +bool ReceiverBase::decodeData( uint8_t *data, uint32_t size, std::vector &messages, uint64_t timetag ) const +{ + if( ! memcmp( data, "#bundle\0", 8 ) ) { + data += 8; size -= 8; + + uint64_t timestamp; + memcpy( ×tamp, data, 8 ); data += 8; size -= 8; + + while( size != 0 ) { + uint32_t seg_size; + memcpy( &seg_size, data, 4 ); + data += 4; size -= 4; + + seg_size = ntohl( seg_size ); + if( seg_size > size ) { + CI_LOG_E( "Problem Parsing Bundle: Segment Size is greater than bundle size." ); + return false; + } + if( !decodeData( data, seg_size, messages, ntohll( timestamp ) ) ) + return false; + + data += seg_size; size -= seg_size; + } + } + else { + if( ! decodeMessage( data, size, messages, timetag ) ) + return false; + } + + return true; +} + +bool ReceiverBase::decodeMessage( uint8_t *data, uint32_t size, std::vector &messages, uint64_t timetag ) const +{ + Message message; + if( ! message.bufferCache( data, size ) ) + return false; + + messages.push_back( std::move( message ) ); + return true; +} + +bool ReceiverBase::patternMatch( const std::string& lhs, const std::string& rhs ) const +{ + bool negate = false; + bool mismatched = false; + std::string::const_iterator seq_tmp; + std::string::const_iterator seq = lhs.begin(); + std::string::const_iterator seq_end = lhs.end(); + std::string::const_iterator pattern = rhs.begin(); + std::string::const_iterator pattern_end = rhs.end(); + while( seq != seq_end && pattern != pattern_end ) { + switch( *pattern ) { + case '?': + break; + case '*': { + // if * is the last pattern, return true + if( ++pattern == pattern_end ) return true; + while( *seq != *pattern && seq != seq_end ) ++seq; + // if seq reaches to the end without matching pattern + if( seq == seq_end ) return false; + } + break; + case '[': { + negate = false; + mismatched = false; + if( *( ++pattern ) == '!' ) { + negate = true; + ++pattern; + } + if( *( pattern + 1 ) == '-' ) { + // range matching + char c_start = *pattern; ++pattern; + //assert(*pattern == '-'); + char c_end = *( ++pattern ); ++pattern; + //assert(*pattern == ']'); + // swap c_start and c_end if c_start is larger + if( c_start > c_end ) { + char tmp = c_start; + c_end = c_start; + c_start = tmp; + } + mismatched = ( c_start <= *seq && *seq <= c_end ) ? negate : !negate; + if( mismatched ) return false; + } + else { + // literal matching + while( *pattern != ']' ) { + if( *seq == *pattern ) { + mismatched = negate; + break; + } + ++pattern; + } + if( mismatched ) return false; + while( *pattern != ']' ) ++pattern; + } + } + break; + case '{': { + seq_tmp = seq; + mismatched = true; + while( *( ++pattern ) != '}' ) { + // this assumes that there's no sequence like "{,a}" where ',' is + // follows immediately after '{', which is illegal. + if( *pattern == ',' ) { + mismatched = false; + break; + } + else if( *seq != *pattern ) { + // fast forward to the next ',' or '}' + while( *( ++pattern ) != ',' && *pattern != '}' ); + if( *pattern == '}' ) return false; + // redo seq matching + seq = seq_tmp; + mismatched = true; + } + else { + // matched + ++seq; + mismatched = false; + } + } + if( mismatched ) return false; + while( *pattern != '}' ) ++pattern; + --seq; + } + break; + default: // non-special character + if( *seq != *pattern ) return false; + break; + } + ++seq; ++pattern; + } + if( seq == seq_end && pattern == pattern_end ) return true; + else return false; +} + +///////////////////////////////////////////////////////////////////////////////////////// +//// ReceiverUdp + +ReceiverUdp::ReceiverUdp( uint16_t port, const asio::ip::udp &protocol, asio::io_context &service ) +: mSocket( new udp::socket( service ) ), mLocalEndpoint( protocol, port ), mAmountToReceive( 4096 ) +{ +} + +ReceiverUdp::ReceiverUdp( const asio::ip::udp::endpoint &localEndpoint, asio::io_context &io ) +: mSocket( new udp::socket( io ) ), mLocalEndpoint( localEndpoint ), mAmountToReceive( 4096 ) +{ +} + +ReceiverUdp::ReceiverUdp( UdpSocketRef socket ) +: mSocket( socket ), mLocalEndpoint( socket->local_endpoint() ), mAmountToReceive( 4096 ) +{ +} + +void ReceiverUdp::bindImpl() +{ + asio::error_code ec; + mSocket->open( mLocalEndpoint.protocol(), ec ); + if( ec ) + throw osc::Exception( ec ); + + mSocket->bind( mLocalEndpoint, ec ); + if( ec ) + throw osc::Exception( ec ); +} + +void ReceiverUdp::setAmountToReceive( uint32_t amountToReceive ) +{ + mAmountToReceive.store( amountToReceive ); +} + +void ReceiverUdp::listen( OnSocketErrorFn onSocketErrorFn ) +{ + if ( ! mSocket->is_open() ) + return; + + uint32_t prepareAmount = mAmountToReceive.load(); + auto tempBuffer = mBuffer.prepare( prepareAmount ); + auto uniqueEndpoint = std::make_shared(); + mSocket->async_receive_from( tempBuffer, *uniqueEndpoint, + [&, uniqueEndpoint, onSocketErrorFn]( const asio::error_code &error, size_t bytesTransferred ) { + if( error ) { + if( onSocketErrorFn ) { + if( ! onSocketErrorFn( error, *uniqueEndpoint ) ) + return; + } + else { + CI_LOG_E( "Udp Message: " << error.message() << " - Code: " << error.value() + << ", Endpoint: " << uniqueEndpoint->address().to_string() ); + CI_LOG_W( "Exiting Listen loop." ); + return; + } + } + else { + mBuffer.commit( bytesTransferred ); + auto data = std::unique_ptr( new uint8_t[ bytesTransferred + 1 ] ); + data[ bytesTransferred ] = 0; + istream stream( &mBuffer ); + stream.read( reinterpret_cast( data.get() ), bytesTransferred ); + CI_ASSERT_MSG( bytesTransferred <= std::numeric_limits::max(), + "Dispatch size must fit in uint32_t" ); + dispatchMethods( data.get(), static_cast( bytesTransferred ), uniqueEndpoint->address(), uniqueEndpoint->port() ); + } + listen( std::move( onSocketErrorFn ) ); + }); +} + +void ReceiverUdp::closeImpl() +{ + asio::error_code ec; + mSocket->close( ec ); + if( ec ) + throw osc::Exception( ec ); +} + +///////////////////////////////////////////////////////////////////////////////////////// +//// ReceiverTcp + +ReceiverTcp::~ReceiverTcp() +{ + ReceiverTcp::closeImpl(); +} + +ReceiverTcp::Connection::Connection( TcpSocketRef socket, ReceiverTcp *receiver, uint64_t identifier ) +: mSocket( socket ), mReceiver( receiver ), mIdentifier( identifier ), mIsConnected( true ) +{ +} + +ReceiverTcp::Connection::~Connection() +{ + mReceiver = nullptr; +} + +asio::error_code ReceiverTcp::Connection::shutdown( asio::socket_base::shutdown_type shutdownType ) +{ + asio::error_code ec; + if( ! mSocket->is_open() || ! mIsConnected ) + return ec; + + mSocket->shutdown( asio::socket_base::shutdown_both, ec ); + mIsConnected.store( false ); + // the other side may have already shutdown the connection. + if( ec == asio::error::not_connected ) + ec = asio::error_code(); + return ec; +} + +using iterator = asio::buffers_iterator; + +std::pair ReceiverTcp::Connection::readMatchCondition( iterator begin, iterator end ) +{ + iterator i = begin; + ByteArray<4> data; + int inc = 0; + while ( i != end && inc < 4 ) + data[inc++] = *i++; + + int numBytes = *reinterpret_cast( data.data() ); + // swap for big endian from the other side + numBytes = ntohl( numBytes ); + + if( inc == 4 && numBytes > 0 && numBytes + 4 <= std::distance( begin, end ) ) + return { begin + numBytes + 4, true }; + else + return { begin, false }; +} + +void ReceiverTcp::Connection::read() +{ + if( ! mSocket->is_open() ) + return; + + auto receiver = mReceiver; + + std::function( iterator, iterator )> match = &readMatchCondition; + if( mReceiver->mPacketFraming ) + match = std::bind( &PacketFraming::messageComplete, mReceiver->mPacketFraming, + std::placeholders::_1, std::placeholders::_2 ); + asio::async_read_until( *mSocket, mBuffer, match, + [&, receiver]( const asio::error_code &error, size_t bytesTransferred ) { + if( error ) { + std::lock_guard lock( receiver->mConnectionErrorFnMutex ); + if( receiver->mConnectionErrorFn ) + receiver->mConnectionErrorFn( error, mIdentifier ); + else + CI_LOG_E( error.message() << ", didn't receive message from " << mSocket->remote_endpoint().address().to_string() ); + receiver->closeConnection( mIdentifier ); + } + else { + ByteBufferRef data = ByteBufferRef( new ByteBuffer( bytesTransferred ) ); + istream stream( &mBuffer ); + stream.read( reinterpret_cast( data->data() ), bytesTransferred ); + + uint8_t *dataPtr = nullptr; + size_t dataSize = 0; + + if( mReceiver->mPacketFraming ) { + data = mReceiver->mPacketFraming->decode( data ); + dataPtr = data->data(); + dataSize = data->size(); + } + else { + dataPtr = data->data() + 4; + dataSize = data->size() - 4; + } + + CI_ASSERT_MSG( dataSize <= std::numeric_limits::max(), + "Dispatch size must fit in uint32_t" ); + receiver->dispatchMethods( dataPtr, static_cast( dataSize ), mSocket->remote_endpoint().address(), mSocket->remote_endpoint().port() ); + + read(); + } + }); +} + +ReceiverTcp::ReceiverTcp( uint16_t port, const protocol &protocol, asio::io_context &service, PacketFramingRef packetFraming ) +: mAcceptor( new tcp::acceptor( service ) ), mPacketFraming( packetFraming ), mLocalEndpoint( protocol, port ), + mConnectionIdentifiers( 0 ), mIsShuttingDown( false ) +{ +} + +ReceiverTcp::ReceiverTcp( const protocol::endpoint &localEndpoint, asio::io_context &service, PacketFramingRef packetFraming ) +: mAcceptor( new tcp::acceptor( service ) ), mPacketFraming( packetFraming ), mLocalEndpoint( localEndpoint ), + mConnectionIdentifiers( 0 ), mIsShuttingDown( false ) +{ +} + +ReceiverTcp::ReceiverTcp( AcceptorRef acceptor, PacketFramingRef packetFraming ) +: mAcceptor( acceptor ), mLocalEndpoint( mAcceptor->local_endpoint() ), mPacketFraming( packetFraming ), + mConnectionIdentifiers( 0 ), mIsShuttingDown( false ) +{ +} + +ReceiverTcp::ReceiverTcp( TcpSocketRef socket, PacketFramingRef packetFraming ) +: mAcceptor( nullptr ), mLocalEndpoint( socket->local_endpoint() ), mPacketFraming( packetFraming ), + mConnectionIdentifiers( 0 ), mIsShuttingDown( false ) +{ + auto identifier = mConnectionIdentifiers++; + std::lock_guard lock( mConnectionMutex ); + mConnections.emplace_back( new Connection( socket, this, identifier ) ); + mConnections.back()->read(); +} + +void ReceiverTcp::bindImpl() +{ + if( ! mAcceptor || mAcceptor->is_open() ) + return; + + asio::error_code ec; + mIsShuttingDown.store( false ); + + mAcceptor->open( mLocalEndpoint.protocol(), ec ); + if( ec ) + throw osc::Exception( ec ); + + mAcceptor->set_option( socket_base::reuse_address( true ) ); + mAcceptor->bind( mLocalEndpoint, ec ); + if( ec ) + throw osc::Exception( ec ); + + mAcceptor->listen( socket_base::max_connections, ec ); + if( ec ) + throw osc::Exception( ec ); +} + +void ReceiverTcp::accept( OnAcceptErrorFn onAcceptErrorFn, OnAcceptFn onAcceptFn ) +{ + if( ! mAcceptor || ! mAcceptor->is_open() ) + return; + + auto socket = std::make_shared( mAcceptor->get_executor() ); + + mAcceptor->async_accept( *socket, std::bind( + [&, onAcceptErrorFn, onAcceptFn]( TcpSocketRef socket, const asio::error_code &error ) { + if( ! error ) { + auto identifier = mConnectionIdentifiers++; + { + bool shouldAdd = true; + if( onAcceptFn ) + shouldAdd = onAcceptFn( socket, identifier ); + + if( shouldAdd ) { + std::lock_guard lock( mConnectionMutex ); + mConnections.emplace_back( new Connection( socket, this, identifier ) ); + mConnections.back()->read(); + } + } + } + else { + if( onAcceptErrorFn ) { + auto endpoint = socket->remote_endpoint(); + if( ! onAcceptErrorFn( error, endpoint ) ) + return; + } + else { + CI_LOG_E( "Tcp Accept: " << error.message() << " - Code: " << error.value() ); + CI_LOG_W( "Exiting Accept loop." ); + return; + } + } + + accept( onAcceptErrorFn, onAcceptFn ); + }, socket, _1 ) ); +} + +void ReceiverTcp::setConnectionErrorFn( ConnectionErrorFn errorFn ) +{ + std::lock_guard lock( mConnectionErrorFnMutex ); + mConnectionErrorFn = errorFn; +} + +void ReceiverTcp::closeAcceptor() +{ + if( ! mAcceptor || mIsShuttingDown.load() ) + return; + + asio::error_code ec; + mAcceptor->close( ec ); + if( ec ) + throw osc::Exception( ec ); +} + +void ReceiverTcp::closeImpl() +{ + // if there's an error on a socket while shutting down the receiver, it could + // cause a recursive run on the mConnectionMutex by someone listening for + // connection error and attempting to close the connection on error through + // the closeConnection function. This blocks against that ability. Basically, + // if we're shutting down we disregard the closeConnection function. + mIsShuttingDown.store( true ); + closeAcceptor(); + std::lock_guard lock( mConnectionMutex ); + for( auto & connection : mConnections ) + connection->shutdown( socket_base::shutdown_both ); + mConnections.clear(); + +} + +asio::error_code ReceiverTcp::closeConnection( uint64_t connectionIdentifier, asio::socket_base::shutdown_type shutdownType ) +{ + asio::error_code ec; + if( mIsShuttingDown ) + return ec; + + std::lock_guard lock( mConnectionMutex ); + auto rem = find_if( mConnections.begin(), mConnections.end(), + [connectionIdentifier]( const UniqueConnection &cached ) { + return cached->mIdentifier == connectionIdentifier; + } ); + if( rem != mConnections.end() ) { + ec = (*rem)->shutdown( shutdownType ); + mConnections.erase( rem ); + } + return ec; +} + +ByteBufferRef SLIPPacketFraming::encode( ByteBufferRef bufferToEncode ) +{ + // buffers in this system begin with the size, which will be removed in the case of Packet Framing. + auto maxEncodedSize = 2 * (bufferToEncode->size() - 4) + 2; + auto encodeBuffer = ByteBufferRef( new ByteBuffer( maxEncodedSize ) ); + auto finalEncodedSize = encode( bufferToEncode->data() + 4, bufferToEncode->size() - 4, encodeBuffer->data() ); + encodeBuffer->resize( finalEncodedSize ); + return encodeBuffer; +} + +ByteBufferRef SLIPPacketFraming::decode( ByteBufferRef bufferToDecode ) +{ + // should not assume double-ENDed variant + auto maxDecodedSize = bufferToDecode->size() - 1; + auto decodeBuffer = ByteBufferRef( new ByteBuffer( maxDecodedSize ) ); + auto finalDecodedSize = decode( bufferToDecode->data(), bufferToDecode->size(), decodeBuffer->data() ); + decodeBuffer->resize( finalDecodedSize ); + return decodeBuffer; +} + +std::pair SLIPPacketFraming::messageComplete( iterator begin, iterator end ) +{ + iterator i = begin; + while( i != end ) { + if( i != begin && (uint8_t)*i == SLIP_END ) { + // Send back 1 past finding SLIP_END, which in this case will either + // be iterator end or the next SLIP_END, beginning the next message + return { i + 1, true }; + } + i++; + } + return { begin, false }; +} + +size_t SLIPPacketFraming::encode( const uint8_t* data, size_t size, uint8_t* encodedData ) +{ + size_t readIDX = 0, writeIDX = 0; + + // double-ENDed variant, will flush any accumulated line noise + encodedData[writeIDX++] = SLIP_END; + + while (readIDX < size) { + uint8_t value = data[readIDX++]; + + if (value == SLIP_END) { + encodedData[writeIDX++] = SLIP_ESC; + encodedData[writeIDX++] = SLIP_ESC_END; + } + else if (value == SLIP_ESC) { + encodedData[writeIDX++] = SLIP_ESC; + encodedData[writeIDX++] = SLIP_ESC_ESC; + } + else + encodedData[writeIDX++] = value; + } + encodedData[writeIDX++] = SLIP_END; + + return writeIDX; +} + +size_t SLIPPacketFraming::decode(const uint8_t* data, size_t size, uint8_t* decodedData) +{ + size_t readIDX = 0, writeIDX = 0; + + while (readIDX < size) { + uint8_t value = data[readIDX++]; + + if (value == SLIP_END) { + // flush or done + } + else if (value == SLIP_ESC) { + value = data[readIDX++]; + if (value == SLIP_ESC_END) { + decodedData[writeIDX++] = SLIP_END; + } + else if (value == SLIP_ESC_ESC) { + decodedData[writeIDX++] = SLIP_ESC; + } + else { + // protocol violation + } + } + else { + decodedData[writeIDX++] = value; + } + } + return writeIDX; +} + +namespace time { + +uint64_t get_current_ntp_time( milliseconds offsetMillis ) +{ + auto now = std::chrono::system_clock::now() + offsetMillis; + auto sec = std::chrono::duration_cast( now.time_since_epoch() ).count() + 0x83AA7E80; + auto usec = std::chrono::duration_cast( now.time_since_epoch() ).count() + 0x7D91048BCA000; + + return ( sec << 32 ) + ( usec % 1000000L ); +} + +uint64_t getFutureClockWithOffset( milliseconds offsetFuture, int64_t localOffsetSecs, int64_t localOffsetUSecs ) +{ + uint64_t ntp_time = get_current_ntp_time( offsetFuture ); + + uint64_t secs = ( ntp_time >> 32 ) + localOffsetSecs; + int64_t usecs = ( ntp_time & uint32_t( ~0 ) ) + localOffsetUSecs; + + if( usecs < 0 ) { + secs += usecs / 1000000; + usecs += ( usecs / 1000000 ) * 1000000; + } + else { + secs += usecs / 1000000; + usecs -= ( usecs / 1000000 ) * 1000000; + } + + return ( secs << 32 ) + usecs; +} + +void getDate( uint64_t ntpTime, uint32_t *year, uint32_t *month, uint32_t *day, uint32_t *hours, uint32_t *minutes, uint32_t *seconds ) +{ + // Convert to unix timestamp. + std::time_t sec_since_epoch = ( ntpTime - ( uint64_t( 0x83AA7E80 ) << 32 ) ) >> 32; + +#ifdef CINDER_MSW + struct tm tm_buf{}; + localtime_s( &tm_buf, &sec_since_epoch ); + auto tm = &tm_buf; +#else + auto tm = std::localtime(&sec_since_epoch); +#endif // CINDER_MSW + if( year ) *year = tm->tm_year + 1900; + if( month ) *month = tm->tm_mon + 1; + if( day ) *day = tm->tm_mday; + if( hours ) *hours = tm->tm_hour; + if( minutes ) *minutes = tm->tm_min; + if( seconds )*seconds = tm->tm_sec; +} + +std::string getClockString( uint64_t ntpTime, bool includeDate ) +{ + uint32_t year, month, day, hours, minutes, seconds; + getDate( ntpTime, &year, &month, &day, &hours, &minutes, &seconds ); + + char buffer[128]; + +#ifdef CINDER_MSW +#define SPRINTF sprintf_s +#else +#define SPRINTF sprintf +#endif // CINDER_MSW + + if( includeDate ) + SPRINTF( buffer, "%d/%d/%d %02d:%02d:%02d", month, day, year, hours, minutes, seconds ); + else + SPRINTF( buffer, "%02d:%02d:%02d", hours, minutes, seconds ); + + return std::string( buffer ); +} + +void calcOffsetFromSystem( uint64_t ntpTime, int64_t *localOffsetSecs, int64_t *localOffsetUSecs ) +{ + uint64_t current_ntp_time = time::get_current_ntp_time(); + + *localOffsetSecs = ( ntpTime >> 32 ) - ( current_ntp_time >> 32 ); + *localOffsetUSecs = ( ntpTime & uint32_t( ~0 ) ) - ( current_ntp_time & uint32_t( ~0 ) ); +} + +} // namespace time + +} // namespace osc + +} // namespace cinder diff --git a/blocks/OSC/src/cinder/osc/Osc.h b/blocks/OSC/src/cinder/osc/Osc.h new file mode 100644 index 0000000..65e830e --- /dev/null +++ b/blocks/OSC/src/cinder/osc/Osc.h @@ -0,0 +1,924 @@ +/* + Copyright (c) 2015, The Cinder Project, All rights reserved. + + This code is intended for use with the Cinder C++ library: http://libcinder.org + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that + the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Toshiro Yamada + Portions Copyright (c) 2011 + Distributed under the BSD-License. + https://github.com/toshiroyamada/tnyosc + */ + +#pragma once +#if ! defined( ASIO_STANDALONE ) +#define ASIO_STANDALONE 1 +#endif +#include "asio/asio.hpp" + +#include +#include + +#include "cinder/Buffer.h" +#include "cinder/app/App.h" + +#if ! defined( _MSC_VER ) || _MSC_VER >= 1900 +#define NOEXCEPT noexcept +#else +#define NOEXCEPT +#endif + +namespace cinder { +namespace osc { + +//! Argument types suported by the Message class +enum class ArgType : char { INTEGER_32 = 'i', FLOAT = 'f', DOUBLE = 'd', STRING = 's', BLOB = 'b', MIDI = 'm', TIME_TAG = 't', INTEGER_64 = 'h', BOOL_T = 'T', BOOL_F = 'F', CHAR = 'c', NULL_T = 'N', IMPULSE = 'I', NONE = NULL_T }; + +// Forward declarations +using UdpSocketRef = std::shared_ptr; +using TcpSocketRef = std::shared_ptr; +using AcceptorRef = std::shared_ptr; + +template +using ByteArray = std::array; +using ByteBuffer = std::vector; +using ByteBufferRef = std::shared_ptr; + +/// This class represents an Open Sound Control message. It supports Open Sound +/// Control 1.0 and 1.1 specifications and extra non-standard arguments listed +/// in http://opensoundcontrol.org/spec-1_0. +class Message { + public: + + Message(); + //! Create an OSC message. + explicit Message( const std::string& address ); + Message( const Message & ); + Message& operator=( const Message & ); + Message( Message && ) NOEXCEPT; + Message& operator=( Message && ) NOEXCEPT; + ~Message() = default; + + // Functions for appending OSC 1.0 types + + //! Appends an int32 to the back of the message. + void append( int32_t v ); + //! Appends a float to the back of the message. + void append( float v ); + //! Appends a string to the back of the message. + void append( const std::string& v ); + //! Appends a null-terminated c-string to the back of message. + void append( const char v[] ); + //! Appends an osc blob to the back of the message. + void appendBlob( void* blob, uint32_t size ); + //! Appends an osc blob to the back of the message. + void append( const ci::Buffer &buffer ); + + // Functions for appending OSC 1.1 types + + //! Appends an OSC-timetag (NTP format) to the back of the message. + void appendTimeTag( uint64_t v ); + //! Appends the current UTP timestamp to the back of the message. + void appendCurrentTime(); + //! Appends a 'T'(True) or 'F'(False) to the back of the message. + void append( bool v ); + //! Appends a Null (or nil) to the back of the message. + void appendNull() { mIsCached = false; mDataViews.emplace_back( this, ArgType::NULL_T, -1, 0 ); } + //! Appends an Impulse (or IMPULSE) to the back of the message + void appendImpulse() { mIsCached = false; mDataViews.emplace_back( this, ArgType::IMPULSE, -1, 0 ); } + + // Functions for appending nonstandard types + + //! Appends an int64_t to the back of the message. + void append( int64_t v ); + //! Appends a float64 (or double) to the back of the message. + void append( double v ); + //! Appends an ascii character to the back of the message. + void append( char v ); + //! Appends a midi value to the back of the message. + void appendMidi( uint8_t port, uint8_t status, uint8_t data1, uint8_t data2 ); + + //! Appends \a arg to the back of the message. Static asserts if Message doesn't know how to + //! convert the type. + template + Message& operator<<( T&& arg ) + { + appendArg( std::forward(arg) ); + return *this; + } + + //! Returns the type \a T located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + template + T getArg( uint32_t index ); + //! Returns the int32_t located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + int32_t getArgInt32( uint32_t index ) const; + //! Returns the float located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + float getArgFloat( uint32_t index ) const; + //! Returns the string located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + std::string getArgString( uint32_t index ) const; + //! Supplies the string data located at \a index to the \a dataPtr and \a size. Note: Doesn't copy. + //! If index is out of bounds, throws ExcIndexOutOfBounds. If argument isn't convertible to this type, + //! throws ExcNonConvertible + void getArgStringData( uint32_t index, const char **dataPtr, uint32_t *size ) const; + //! Returns the time_tag located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + int64_t getArgTime( uint32_t index ) const; + //! Returns the time_tag located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + int64_t getArgInt64( uint32_t index ) const; + //! Returns the double located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + double getArgDouble( uint32_t index ) const; + //! Returns the bool located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + bool getArgBool( uint32_t index ) const; + //! Returns the char located at \a index. If index is out of bounds, throws ExcIndexOutOfBounds. + //! If argument isn't convertible to this type, throws ExcNonConvertible + char getArgChar( uint32_t index ) const; + //! Supplies values for the four arguments in the midi format located at \a index. If index is out + //! of bounds, throws ExcIndexOutOfBounds. If argument isn't convertible to this type, throws + //! ExcNonConvertible + void getArgMidi( uint32_t index, uint8_t *port, uint8_t *status, uint8_t *data1, uint8_t *data2 ) const; + //! Returns the blob, as a ci::Buffer, located at \a index. If index is out of bounds, throws + //! ExcIndexOutOfBounds. If argument isn't convertible to this type, throws ExcNonConvertible + ci::Buffer getArgBlob( uint32_t index ) const; + //! Supplies the blob data located at \a index to the \a dataPtr and \a size. Note: Doesn't copy. + //! If index is out of bounds, throws ExcIndexOutOfBounds. If argument isn't convertible to this type, + //! throws ExcNonConvertible + void getArgBlobData( uint32_t index, const void **dataPtr, size_t *size ) const; + //! Returns the argument type located at \a index. + ArgType getArgType( uint32_t index ) const; + //! Returns the number of arguments contained. + size_t getNumArgs() const { return mDataViews.size(); } + //! Returns a string in spec format of the contained arguments for validation purposes. + std::string getTypeTagString() const; + + //! Sets the OSC address of this message. + void setAddress( const std::string& address ); + //! Returns the OSC address of this message. + const std::string& getAddress() const { return mAddress; } + + //! Returns the size of this OSC message as a complete packet. + //! Performs a complete cache operation. + size_t getPacketSize() const; + /// Clears the message, specifically any cache, dataViews, and address. + void clear(); + + class Argument { + public: + Argument(); + Argument( Message *owner, ArgType type, int32_t offset, uint32_t size, bool needsSwap = false ); + Argument( const Argument &arg ); + Argument& operator=( const Argument &arg ); + Argument( Argument &&arg ) NOEXCEPT; + Argument& operator=( Argument &&arg ) NOEXCEPT; + + ~Argument() = default; + + //! Returns the arguments type as an ArgType + ArgType getType() const { return mType; } + //! Returns the arguments size, without trailing zeros or size int's taken into account. + uint32_t getSize() const { return mSize; } + //! Returns the offset into the dataBuffer, where this Argument starts. + int32_t getOffset() const { return mOffset; } + + //! returns the underlying argument as an int32. If argument isn't convertible to this type, + //! throws ExcNonConvertible + int32_t int32() const; + //! returns the underlying argument as an int64. If argument isn't convertible to this type, + //! throws ExcNonConvertible + int64_t int64() const; + //! returns the underlying argument as a float. If argument isn't convertible to this type, + //! throws ExcNonConvertible + float flt() const; + //! returns the underlying argument as a double. If argument isn't convertible to this type, + //! throws ExcNonConvertible + double dbl() const; + //! returns the underlying argument as a boolean. If argument isn't convertible to this type, + //! throws ExcNonConvertible + bool boolean() const; + //! Supplies values for the four arguments in the midi format located at \a index. If argument + //! isn't convertible to this type, throws ExcNonConvertible. + //! ExcNonConvertible + void midi( uint8_t *port, uint8_t *status, uint8_t *data1, uint8_t *data2 ) const; + //! Returns the underlying argument as a "deep-copied" ci::Buffer. If argument isn't convertible + //! to this type, throws ExcNonConvertible + ci::Buffer blob() const; + //! Supplies the blob data to the \a dataPtr and \a size. Note: Doesn't copy. + //! If argument isn't convertible to this type, throws ExcNonConvertible + void blobData( const void **dataPtr, size_t *size ) const; + //! Returns the underlying argument as a char. If argument isn't convertible to this type, + //! throws ExcNonConvertible + char character() const; + //! Returns the underlying argument as a string. If argument isn't convertible to this type, + //! throws ExcNonConvertible + std::string string() const; + //! Supplies the string data to the \a dataPtr and \a size. Note: Doesn't copy. + //! If argument isn't convertible to this type, throws ExcNonConvertible + void stringData( const char **dataPtr, uint32_t *size ) const; + + //! Evaluates the equality of this with \a other + bool operator==( const Argument &other ) const; + + //! Converts ArgType \a type to c-string for debug purposes. + static const char* argTypeToString( ArgType type ); + //! Helper function to translate from ArgType to the spec's representation of that type. + static char translateArgTypeToCharType( ArgType type ); + //! Helper function to translate from the spec's character representation to the ArgType. + static ArgType translateCharTypeToArgType( char type ); + + private: + //! Simple helper to stream a message's contents to the console. + void outputValueToStream( std::ostream &ostream ) const; + //! Returns true, if before transporting this message, this argument needs a big endian swap + bool needsEndianSwapForTransmit() const { return mNeedsEndianSwapForTransmit; } + //! Implements the swap for all types needing a swap. + void swapEndianForTransmit( uint8_t* buffer ) const; + //! Helper to check if the underlying type is able to be converted to the provided template + //! type \a T. + template + bool convertible() const; + + Message* mOwner; + ArgType mType; + int32_t mOffset; + uint32_t mSize; + bool mNeedsEndianSwapForTransmit; + + friend class Message; + friend std::ostream& operator<<( std::ostream &os, const Message &rhs ); + friend std::ostream& operator<<( std::ostream &os, const Message::Argument &rhs ); + }; + //! Subscript operator returns a const Argument& based on \a index. If index is out of + //! bounds, throws ExcIndexOutOfBounds. + const Argument& operator[]( uint32_t index ) const; + //! Evaluates the equality of this with \a other + bool operator==( const Message &other ) const; + //! Evaluates the inequality of this with \a other + bool operator!=( const Message &other ) const; + //! Returns a const reference of the Sender's (originator) Ip Address. Note: Will only + //! be set by the receiver when the message is received. + const asio::ip::address& getSenderIpAddress() const { return mSenderIpAddress; } + //! Returns the Sender's (originator) Port. Note: Will only + //! be set by the receiver when the message is received. + uint16_t getSenderPort() const { return mSenderPort; } + + private: + //! Helper to calculate how many zeros to buffer to create a 4 byte + static uint8_t getTrailingZeros( size_t bufferSize ) { return 4 - ( bufferSize % 4 ); } + //! Helper to get current offset into the buffer. + int32_t getCurrentOffset() { + CI_ASSERT_MSG( mDataBuffer.size() <= std::numeric_limits::max(), + "Argument offset must fit in int32_t" ); + return static_cast( mDataBuffer.size() ); + } + //! Helper to retrieve the data view of an Argument. Checks the type provided and + //! throws ExcNonConvertible if data view cannot convert the type. + template + const Argument& getDataView( uint32_t index ) const; + //! Helper type trait definition of a c-string. + template + struct is_c_str + : std::integral_constant< + bool, + std::is_same::type>::value || + std::is_same::type>::value || + std::is_same::type>::value + > {}; + //! Appends \a t to the back of a message. Checks the type against acceptable types + //! and static_asserts if the type is acceptable. + template + void appendArg(T&& t) + { + static_assert(std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + is_c_str::value, + "Unsupported Type in operator<< statement" ); + append(std::forward(t)); + } + + //! Helper to to insert data starting at \a begin for \a with resize/fill in the amount + //! of \a trailingZeros + void appendDataBuffer( const void *begin, size_t size, uint32_t trailingZeros = 0 ); + + //! Returns a complete byte array of this OSC message as a ByteBufferRef type. + //! The byte buffer is constructed lazily and is cached until the cache is + //! obsolete. Call to |data| and |size| perform the same caching. + ByteBufferRef getSharedBuffer() const; + + std::string mAddress; + ByteBuffer mDataBuffer; + std::vector mDataViews; + mutable bool mIsCached; + mutable ByteBufferRef mCache; + asio::ip::address mSenderIpAddress; + uint16_t mSenderPort; + + //! Create the OSC message and store it in cache. + void createCache() const; + //! Used by receiver to create the inner message. + bool bufferCache( uint8_t *data, size_t size ); + + friend class Bundle; + friend class SenderBase; + friend class SenderUdp; + friend class ReceiverBase; + friend std::ostream& operator<<( std::ostream &os, const Message &rhs ); + + public: + class ExcIndexOutOfBounds : public ci::Exception { + public: + ExcIndexOutOfBounds( const std::string &address, uint32_t index ) + : Exception( std::string( std::to_string( index ) + " out of bounds from address, " + address ) ) + {} + }; + + class ExcNonConvertible : public ci::Exception { + public: + ExcNonConvertible( const std::string &address, ArgType actualType, ArgType convertToType ) + : Exception( address + ": expected type: " + + Argument::argTypeToString( convertToType ) + + ", actual type: " + + Argument::argTypeToString( actualType ) ) + {} + }; +}; + +template<> inline int32_t Message::getArg( uint32_t index ) { return getArgInt32( index ); } +template<> inline float Message::getArg( uint32_t index ) { return getArgFloat( index ); } +template<> inline std::string Message::getArg( uint32_t index ) { return getArgString( index ); } +template<> inline ci::Buffer Message::getArg( uint32_t index ) { return getArgBlob( index ); } +template<> inline int64_t Message::getArg( uint32_t index ) { return getArgInt64( index ); } +template<> inline double Message::getArg( uint32_t index ) { return getArgDouble( index ); } +template<> inline char Message::getArg( uint32_t index ) { return getArgChar( index ); } +template<> inline bool Message::getArg( uint32_t index ) { return getArgBool( index ); } + +//! Convenient stream operator for Message +std::ostream& operator<<( std::ostream &os, const Message &rhs ); +std::ostream& operator<<( std::ostream &os, const Message::Argument &rhs ); + +//! Represents an Open Sound Control bundle message. A bundle can contains any number +//! of Messages and Bundles. +class Bundle { + public: + + //! Creates an OSC bundle with timestamp set to immediate. Call set_timetag to + //! set a custom timestamp. Note: The current Receiver's below disregard timestamp + //! and dispatch the contents immediately. + Bundle(); + ~Bundle() = default; + + //! Appends an OSC message to this bundle. The message's byte buffer is immediately + //! copied into this bundle and any changes to the message after the call to this + //! function does not affect this bundle. + void append( const Message &message ) { appendData( message.getSharedBuffer() ); } + //! Appends an OSC bundle to this bundle. The bundle's contents are immediately copied + //! into this bundle and any changes to the message after the call to this + //! function does not affect this bundle. + void append( const Bundle &bundle ) { appendData( bundle.getSharedBuffer() ); } + + /// Sets timestamp of the bundle. + void setTimetag( uint64_t ntp_time ); + + //! Returns the size of this OSC bundle as a complete packet. + size_t getPacketSize() const { return mDataBuffer->size(); } + + //! Clears the bundle. + void clear() { initializeBuffer(); } + + private: + ByteBufferRef mDataBuffer; + + /// Returns a pointer to the byte array of this OSC bundle. This call is + /// convenient for actually sending this OSC bundle. + ByteBufferRef getSharedBuffer() const; + + void initializeBuffer(); + //! Appends /a data to the internal byte buffer + void appendData( const ByteBufferRef& data ); + + friend class SenderBase; + friend class SenderUdp; +}; + +using PacketFramingRef = std::shared_ptr; + +class PacketFraming { + public: + virtual ~PacketFraming() = default; + //! Abstract signature to implement the encode process. + virtual ByteBufferRef encode( ByteBufferRef bufferToEncode ) = 0; + //! Abstract signature to implement the decode process. + virtual ByteBufferRef decode( ByteBufferRef bufferToDecode ) = 0; + //! Alias representing the iterator type passed the message complete function. + using iterator = asio::buffers_iterator; + //! Abstract signature used to implement the read_until message match_condition. For more info on + //! the use of this function read about match_condition here... + //! http://think-async.com/Asio/asio-1.10.6/doc/asio/reference/async_read_until/overload4.html + virtual std::pair messageComplete( iterator begin, iterator end ) = 0; + + protected: + PacketFraming() = default; +}; + +//! Represents an OSC Sender (called a \a server in the OSC spec) and implements a unified +//! interface without implementing any of the networking layer. +class SenderBase { + public: + virtual ~SenderBase() = default; + + //! Alias for the send OnErrorFn. + using OnErrorFn = std::function; + //! Alias for the send OnCompleteFn. + using OnCompleteFn = std::function; + + //! Binds the underlying network socket. Should be called before trying any communication operations. + //! If an error occurs with either the underlying open or bind operations on the socket, throws + //! osc::Exception with asio::error_code information. + void bind() { bindImpl(); } + //! Sends \a message to the destination endpoint. Takes optional /a onErrorFn and /a onCompleteFn. + //! If error occurs, and an error callback is provided, it will be called with error_code information. + //! If send operation completes and /a onCompleteFn included, it will be called. + void send( const Message &message, OnErrorFn onErrorFn = nullptr, OnCompleteFn onCompleteFn = nullptr ); + //! Sends \a bundle to the destination endpoint. Takes optional /a onErrorFn and /a onCompleteFn. + //! If error occurs, and an error callback is provided, it will be called with error_code information. + //! If send operation completes and /a onCompleteFn included, it will be called. + void send( const Bundle &bundle, OnErrorFn onErrorFn = nullptr, OnCompleteFn onCompleteFn = nullptr ); + //! Closes the underlying connection to the socket. If an error occurs with either the underlying + //! close operations on the socket, throws osc::Exception with asio::error_code information. + void close() { closeImpl(); } + + protected: + SenderBase() = default; + + SenderBase( const SenderBase &other ) = delete; + SenderBase& operator=( const SenderBase &other ) = delete; + SenderBase( SenderBase &&other ) = delete; + SenderBase& operator=( SenderBase &&other ) = delete; + + //! Abstract send function implemented by the network layer. + virtual void sendImpl( const ByteBufferRef &byteBuffer, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) = 0; + //! Helper function to extract the address of an osc message if present. + static std::string extractOscAddress( const ByteBufferRef &transportData ); + //! Abstract bind function implemented by the network layer + virtual void bindImpl() = 0; + //! Abstract close function implemented by the network layer + virtual void closeImpl() = 0; +}; + +//! Represents an OSC Sender (called a \a server in the OSC spec) and implements the UDP +//! transport networking layer. +class SenderUdp : public SenderBase { + public: + //! Alias protocol for cleaner interfaces + using protocol = asio::ip::udp; + //! Constructs a Sender (called a \a server in the OSC spec) using UDP as transport, whose local endpoint is + //! defined by \a localPort and \a protocol, which defaults to v4, and remote endpoint is defined by + //! \a destinationHost and \a destinationPort. Takes an optional io_context, used to construct the socket from. + SenderUdp( uint16_t localPort, + const std::string &destinationHost, + uint16_t destinationPort, + const protocol &protocol = protocol::v4(), + asio::io_context &service = ci::app::App::get()->io_context() ); + //! Constructs a Sender (called a \a server in the OSC spec) using UDP as transport, whose local endpoint is + //! defined by \a localPort and \a protocol, which defaults to v4, and remote endpoint is defined by \a + //! destination. Takes an optional io_context, used to construct the socket from. + SenderUdp( uint16_t localPort, + const protocol::endpoint &destination, + const protocol &protocol = protocol::v4(), + asio::io_context &service = ci::app::App::get()->io_context() ); + //! Constructs a Sender (called a \a server in the OSC spec) using UDP for transport, with an already created + //! udp::socket shared_ptr \a socket and remote endpoint \a destination. This constructor is good for using + //! already constructed sockets for more indepth configuration. Expects the local endpoint to be constructed. + SenderUdp( const UdpSocketRef &socket, const protocol::endpoint &destination ); + //! Default virtual constructor + virtual ~SenderUdp() = default; + + //! Returns the local address of the endpoint associated with this socket. + protocol::endpoint getLocalAddress() const { return mSocket->local_endpoint(); } + //! Returns the remote address of the endpoint associated with this transport. + const protocol::endpoint& getRemoteAddress() const { return mRemoteEndpoint; } + + protected: + //! Opens and Binds the underlying UDP socket to the protocol and localEndpoint respectively. If an + //! error occurs, throws osc::Exception. + void bindImpl() override; + //! Sends the byte buffer /a data to the remote endpoint using the UDP socket, asynchronously. Takes + //! optional /a options. If error occurs, and SenderOptions provides an error callback, it will be + //! called with information. If /a options includes a completeFn, it will be called. + void sendImpl( const ByteBufferRef &data, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) override; + //! Closes the underlying UDP socket. If an error occurs, If an error occurs, throws osc::Exception. + void closeImpl() override; + + UdpSocketRef mSocket; + protocol::endpoint mLocalEndpoint, mRemoteEndpoint; + + public: + //! Non-copyable. + SenderUdp( const SenderUdp &other ) = delete; + //! Non-copyable. + SenderUdp& operator=( const SenderUdp &other ) = delete; + //! Non-Moveable. + SenderUdp( SenderUdp &&other ) = delete; + //! Non-Moveable. + SenderUdp& operator=( SenderUdp &&other ) = delete; +}; + +//! Represents an OSC Sender (called a \a server in the OSC spec) and implements the TCP +//! transport networking layer. Implements an optional PacketFraming interface used at +//! construction to define the framing of messages to the endpoint. See PacketFraming above. +class SenderTcp : public SenderBase { + public: + //! Alias protocol for cleaner interfaces + using protocol = asio::ip::tcp; + //! Alias function representing an on connect callback, receiving an error_code and the bound and connected + //! underlying shared_ptr tcp::socket. + using OnConnectFn = std::function; + //! Constructs a Sender (called a \a server in the OSC spec) using TCP as transport, whose local endpoint is + //! defined by \a localPort and \a protocol, which defaults to v4, and remote endpoint is defined by \a + //! destinationHost and \a destinationPort and PacketFraming shared_ptr \a packetFraming, which defaults to + //! null. Takes an optional io_context to construct the socket from. + SenderTcp( uint16_t localPort, + const std::string &destinationHost, + uint16_t destinationPort, + const protocol &protocol = protocol::v4(), + asio::io_context &service = ci::app::App::get()->io_context(), + PacketFramingRef packetFraming = nullptr ); + //! Constructs a Sender (called a \a server in the OSC spec) using TCP as transport, whose local endpoint is + //! defined by \a localPort and \a protocol, which defaults to v4, and remote endpoint is defined by \a + //! destination and PacketFraming shared_ptr \a packetFraming, which defaults to null. Takes an optional + //! io_context to construct the socket from. + SenderTcp( uint16_t localPort, + const protocol::endpoint &destination, + const protocol &protocol = protocol::v4(), + asio::io_context &service = ci::app::App::get()->io_context(), + PacketFramingRef packetFraming = nullptr ); + //! Constructs a Sender (called a \a server in the OSC spec) using TCP as transport, with an already created + //! tcp::socket shared_ptr \a socket, and remote endpoint is defined by \a destination and PacketFraming + //! shared_ptr \a packetFraming, which defaults to null. This constructor is good for using already constructed + //! sockets for more indepth configuration. Expects the local endpoint is already constructed. + SenderTcp( const TcpSocketRef &socket, + const protocol::endpoint &destination, + PacketFramingRef packetFraming = nullptr ); + virtual ~SenderTcp(); + + //! Connects to the remote endpoint using the underlying socket. Has to be called before attempting to + //! send anything. /a onConnectFn called after asynchronous operation finishes, with any errors in the + //! error_code argument + void connect( OnConnectFn onConnectFn ); + //! Shuts down the underlying socket. Use this function prior to close to clean up the connection and + //! end socket linger. If an error occurs with the underlying operation on the socket, throws + //! osc::Exception with asio::error_code information. + void shutdown( asio::socket_base::shutdown_type shutdownType = asio::socket_base::shutdown_both ); + + //! Returns the local address of the endpoint associated with this socket. + protocol::endpoint getLocalEndpoint() const { return mSocket->local_endpoint(); } + //! Returns the remote address of the endpoint associated with this transport. + const protocol::endpoint& getRemoteEndpoint() const { return mRemoteEndpoint; } + + protected: + //! Opens and Binds the underlying TCP socket to the protocol and localEndpoint respectively. If an + //! error occurs, throws osc::Exception. + void bindImpl() override; + //! Sends the byte buffer /a data to the remote endpoint using the TCP socket, asynchronously. Takes + //! optional /a options. If error occurs, and SenderOptions provides an error callback, it will be + //! called with information. If /a options includes a completeFn, it will be called. + void sendImpl( const ByteBufferRef &data, OnErrorFn onErrorFn, OnCompleteFn onCompleteFn ) override; + //! Closes the underlying TCP socket. If error occurs, throws osc::Exception. + void closeImpl() override; + + TcpSocketRef mSocket; + PacketFramingRef mPacketFraming; + asio::ip::tcp::endpoint mLocalEndpoint, mRemoteEndpoint; + + public: + //! Non-copyable. + SenderTcp( const SenderTcp &other ) = delete; + //! Non-copyable. + SenderTcp& operator=( const SenderTcp &other ) = delete; + //! Non-Moveable. + SenderTcp( SenderTcp &&other ) = delete; + //! Non-Moveable. + SenderTcp& operator=( SenderTcp &&other ) = delete; +}; + +//! Represents an OSC Receiver(called a \a client in the OSC spec) and implements a unified +//! interface without implementing any of the networking layer. +class ReceiverBase { +public: + virtual ~ReceiverBase() = default; + //! Alias function representing a message callback. + using ListenerFn = std::function; + //! Alias container for callbacks. + using Listeners = std::vector>; + + //! Binds the underlying network socket. Should be called before executing communication operations. + void bind() { bindImpl(); } + //! Closes the underlying network socket. Should be called on most errors to reset the socket. + void close() { closeImpl(); } + + //! Sets a callback, \a listener, to be called when receiving a message with \a address. If a ListenerFn + //! does not exist for a specific address, any messages with that address will be disregarded. If a ListenerFn + //! already exists for this address, \a listener will replace it. + void setListener( const std::string &address, ListenerFn listener ); + //! Removes the listener associated with \a address. + void removeListener( const std::string &address ); + + protected: + ReceiverBase() = default; + //! Non-copyable. + ReceiverBase( const ReceiverBase &other ) = delete; + //! Non-copyable. + ReceiverBase& operator=( const ReceiverBase &other ) = delete; + //! Non-Moveable. + ReceiverBase( ReceiverBase &&other ) = delete; + //! Non-Moveable. + ReceiverBase& operator=( ReceiverBase &&other ) = delete; + + //! Decodes and routes messages from the networking layer stream. Dispatches all messages with + //! an address that has an associated listener. + void dispatchMethods( uint8_t *data, uint32_t size, const asio::ip::address &senderIpAddress, uint16_t senderPort ); + //! Decodes a complete OSC Packet into it's individual parts. \a timetag is ignored within the + //! below implementations. + bool decodeData( uint8_t *data, uint32_t size, std::vector &messages, uint64_t timetag = 0 ) const; + //! Decodes an individual message. \a timetag is ignored within the below implementations. + bool decodeMessage( uint8_t *data, uint32_t size, std::vector &messages, uint64_t timetag = 0 ) const; + //! Matches the addresses of messages based on the OSC spec. + bool patternMatch( const std::string &lhs, const std::string &rhs ) const; + + //! Abstract bind implementation function. + virtual void bindImpl() = 0; + //! Abstract close implementation function. + virtual void closeImpl() = 0; + + Listeners mListeners; + std::mutex mListenerMutex; + std::set mDisregardedAddresses; +}; + +//! Represents an OSC Receiver(called a \a client in the OSC spec) and implements the UDP transport +//! networking layer. +class ReceiverUdp : public ReceiverBase { + public: + //! Alias protocol for cleaner interfaces + using protocol = asio::ip::udp; + //! Alias function that represents a general error callback for the socket, with an error_code and + //! orginating endpoint if present. To see more about asio's error_codes, look at "asio/error.hpp". + using OnSocketErrorFn = std::function; + //! Constructs a Receiver (called a \a client in the OSC spec) using UDP for transport, whose local endpoint + //! is defined by \a localPort and \a protocol, which defaults to v4. Takes an optional io_context to + //! construct the socket from. + ReceiverUdp( uint16_t port, + const protocol &protocol = protocol::v4(), + asio::io_context &io = ci::app::App::get()->io_context() ); + //! Constructs a Receiver (called a \a client in the OSC spec) using UDP for transport, whose local endpoint + //! is defined by \a localEndpoint. Takes an optional io_context to construct the socket from. + ReceiverUdp( const protocol::endpoint &localEndpoint, + asio::io_context &io = ci::app::App::get()->io_context() ); + //! Constructs a Receiver (called a \a client in the OSC spec) using UDP for transport, from the already + //! constructed udp::socket shared_ptr \a socket. Use this for extra configuration and or sharing sockets + //! between sender and receiver. + ReceiverUdp( UdpSocketRef socket ); + virtual ~ReceiverUdp() = default; + + //! Commits the socket to asynchronously listen and begin to receive from outside connections. + //! /a onSocketErrorFn will be called in the case that any socket error is propagated. + void listen( OnSocketErrorFn onSocketErrorFn ); + //! Sets the amount of bytes to reserve for the datagrams being received. + void setAmountToReceive( uint32_t amountToReceive ); + //! Returns the local udp::endpoint of the underlying socket. + asio::ip::udp::endpoint getLocalEndpoint() { return mSocket->local_endpoint(); } + + protected: + //! Opens and Binds the underlying UDP socket to the protocol and localEndpoint respectively. If an error occurs, + //! the SocketTranportErrorFn will be called with a default constructed endpoint. + void bindImpl() override; + //! Closes the underlying UDP socket. If an error occurs, the SocketTranportErrorFn will be called with a + //! default constructed endpoint. + void closeImpl() override; + + UdpSocketRef mSocket; + asio::ip::udp::endpoint mLocalEndpoint; + asio::streambuf mBuffer; + + std::atomic mAmountToReceive; + + public: + //! Non-copyable. + ReceiverUdp( const ReceiverUdp &other ) = delete; + //! Non-copyable. + ReceiverUdp& operator=( const ReceiverUdp &other ) = delete; + //! Non-Moveable. + ReceiverUdp( ReceiverUdp &&other ) = delete; + //! Non-Moveable. + ReceiverUdp& operator=( ReceiverUdp &&other ) = delete; +}; + +//! Represents an OSC Receiver(called a \a client in the OSC spec) and implements the TCP +//! transport networking layer. +class ReceiverTcp : public ReceiverBase { + public: + //! Alias protocol for cleaner interfaces + using protocol = asio::ip::tcp; + //! Alias function that represents a general error callback for the socket, arguments include + //! the error_code and the connectionIdentifier. To see more about asio's error_codes, look + //! at "asio/error.hpp". The connectionIdentifier can be used to close the underlying socket + //! and notify the rest of your system. + using ConnectionErrorFn = std::function; + //! Alias function that represents an on accept callback with the constructed tcp::socket and the + //! connectionIdentifier. The connectionIdentifier is useful to target closing the underlying + //! socket at a later time. Function should return whether the Receiver should or should not cache + //! the connection. + using OnAcceptFn = std::function; + //! Alias function that represents a general error callback for the underlying tcp::acceptor, + //! arguments include the error_code and possible remote endpoint. Function should return whether or + //! not the acceptor should continue to accept. To see more about asio's error_codes, + //! look at "asio/error.hpp" + using OnAcceptErrorFn = std::function; + //! Constructs a Receiver (called a \a client in the OSC spec) using TCP for transport, whose + //! local endpoint is defined by \a localPort and \a protocol, which defaults to v4, and + //! PacketFraming shared_ptr \a packetFraming, which defaults to null. Takes an optional io_context + //! to construct the socket from. + ReceiverTcp( uint16_t port, + const protocol &protocol = protocol::v4(), + asio::io_context &service = ci::app::App::get()->io_context(), + PacketFramingRef packetFraming = nullptr ); + //! Constructs a Receiver (called a \a client in the OSC spec) using TCP for transport, whose local endpoint + //! is defined by \a localEndpoint and PacketFraming shared_ptr \a packetFraming, which defaults to null. Takes + //! an optional io_context to construct the socket from. + ReceiverTcp( const protocol::endpoint &localEndpoint, + asio::io_context &service = ci::app::App::get()->io_context(), + PacketFramingRef packetFraming = nullptr ); + //! Constructs a Receiver (called a \a client in the OSC spec) using TCP for transport, from the already + //! constructed tcp::acceptor shared_ptr \a acceptor and PacketFraming shared_ptr \a packetFraming, which + //! defaults to null. Use this for extra configuration. + ReceiverTcp( AcceptorRef acceptor, + PacketFramingRef packetFraming = nullptr ); + //! Constructs a Receiver (called a \a client in the OSC spec) using TCP for transport, from the already + //! constructed tcp::socket shared_ptr \a connection and PacketFraming shared_ptr \a packetFraming, which + //! defaults to null. Advanced use only. Does not instantiate an acceptor. + ReceiverTcp( TcpSocketRef connection, + PacketFramingRef packetFraming = nullptr ); + virtual ~ReceiverTcp(); + + //! Launches acceptor to asynchronously accept connections. If an error occurs, the /a onAcceptErrorFn + //! will be called. When a connection is accepted, /a onAcceptFn will be called. + void accept( OnAcceptErrorFn onAcceptErrorFn, OnAcceptFn onAcceptFn ); + //! Sets the underlying SocketTransportErrorFn, called on any errors happening on the underlying sockets. + void setConnectionErrorFn( ConnectionErrorFn errorFn ); + //! Closes the underlying acceptor. Must rebind to listen again after calling this function. + void closeAcceptor(); + //! Closes the Connection associated with the connectionIdentifier. \a connectionIdentifier is the handle + //! to the socket, received in the OnAccept method. \a shutdownType sets the shutdown method for the underlying + //! socket before closing it. See OnAcceptFn and SocketTransportErrorFn for more. + asio::error_code closeConnection( uint64_t connectionIdentifier, asio::socket_base::shutdown_type shutdownType = asio::socket_base::shutdown_both ); + + protected: + //! Handles reading from the socket. + struct Connection { + //! Constructs a Connection with \a socket,\a transport, and \a identifier. + Connection( TcpSocketRef socket, ReceiverTcp* transport, uint64_t identifier ); + ~Connection(); + + //! Implements asynchronous read on the underlying socket. Handles the async receive completion operations. + void read(); + asio::error_code shutdown( asio::socket_base::shutdown_type shutdownType ); + //! Simple alias for asio buffer iterator type. + using iterator = asio::buffers_iterator; + //! Static method which is used to read the stream as it's coming in and notate each packet. Implementation + //! based on the OSC 1.0 spec. + static std::pair readMatchCondition( iterator begin, iterator end ); + + TcpSocketRef mSocket; + ReceiverTcp* mReceiver; + asio::streambuf mBuffer; + std::atomic mIsConnected; + + const uint64_t mIdentifier; + + //! Non-copyable. + Connection( const Connection &other ) = delete; + //! Non-copyable. + Connection& operator=( const Connection &other ) = delete; + //! Non-moveable. + Connection( Connection &&other ) = delete; + //! Non-moveable. + Connection& operator=( Connection &&other ) = delete; + + friend class ReceiverTcp; + }; + + //! Opens and Binds the underlying TCP acceptor to the protocol and localEndpoint respectively. If an error occurs, + //! the AccpetorErrorFn will be called. + void bindImpl() override; + //! Implements the close operation for the underlying sockets and acceptor. For the underlying socket, shutdown is + //! called on the prior to close. If an error occurs, the AcceptorErrorFn or the SocketErrorFn will be called + //! respectively. + void closeImpl() override; + + AcceptorRef mAcceptor; + PacketFramingRef mPacketFraming; + protocol::endpoint mLocalEndpoint; + + ConnectionErrorFn mConnectionErrorFn; + + std::mutex mConnectionMutex, mConnectionErrorFnMutex; + + //! Alias representing each connection. + using UniqueConnection = std::unique_ptr; + std::vector mConnections; + uint64_t mConnectionIdentifiers; + std::atomic mIsShuttingDown; + + friend struct Connection; + public: + //! Non-copyable. + ReceiverTcp( const ReceiverTcp &other ) = delete; + //! Non-copyable. + ReceiverTcp& operator=( const ReceiverTcp &other ) = delete; + //! Non-Moveable. + ReceiverTcp( ReceiverTcp &&other ) = delete; + //! Non-Moveable. + ReceiverTcp& operator=( ReceiverTcp &&other ) = delete; +}; + +//! Implements the SLIP encode and decode process for Stream Packet Framing. This is the recommended +//! standard for the OSC 1.1 specification. Code contribution from https://github.com/pizthewiz/Cinder-Encoding. +class SLIPPacketFraming : public PacketFraming { + public: + SLIPPacketFraming() = default; + virtual ~SLIPPacketFraming() = default; + //! SLIP encodes \a bufferToEncode returning the encoded ByteBufferRef. + ByteBufferRef encode( ByteBufferRef bufferToEncode ) override; + //! SLIP decodes \a bufferToDecode returning the decoded ByteBufferRef. + ByteBufferRef decode( ByteBufferRef bufferToDecode ) override; + //! Message Match condition for SLIP encoding. + std::pair messageComplete( iterator begin, iterator end ) override; + + //! Const values used in the SLIP encoding/decoding process. + static const uint8_t SLIP_END = 0xC0; + static const uint8_t SLIP_ESC = 0xDB; + static const uint8_t SLIP_ESC_END = 0xDC; + static const uint8_t SLIP_ESC_ESC = 0xDD; + + protected: + //! Implements the encoding process. + size_t encode( const uint8_t* data, size_t size, uint8_t* encodedData ); + //! Implements the decoding process. + size_t decode( const uint8_t* data, size_t size, uint8_t* decodedData ); +}; + +class Exception : public ci::Exception { +public: + Exception( asio::error_code error ) : ci::Exception( error.message() ), mError( error ) {} + //! Returns system level value of the error for reference online. Very helpful information + //! can be found using this number. + int32_t value() const { return mError.value(); } +private: + asio::error_code mError; +}; + +namespace time { + using milliseconds = std::chrono::milliseconds; + //! Returns system clock time. + uint64_t get_current_ntp_time( milliseconds offsetMillis = milliseconds( 0 ) ); + //! Returns the current presentation time as NTP time, which may include an offset to the system clock. + uint64_t getFutureClockWithOffset( milliseconds offsetFuture = milliseconds( 0 ), int64_t localOffsetSecs = 0, int64_t localOffsetUSecs = 0 ); + //! Returns the current presentation time as a string. + std::string getClockString( uint64_t ntpTime, bool includeDate = false ); + //! Sets the current presentation time as NTP time, from which an offset to the system clock is calculated. + void calcOffsetFromSystem( uint64_t ntpTime, int64_t *localOffsetSecs, int64_t *localOffsetUSecs ); +} // namespace time + +} // namespace osc + +} // namespace cinder diff --git a/include/Resources.h b/include/Resources.h new file mode 100644 index 0000000..09394a7 --- /dev/null +++ b/include/Resources.h @@ -0,0 +1,8 @@ +#pragma once +#include "cinder/CinderResources.h" + +//#define RES_MY_RES CINDER_RESOURCE( ../resources/, image_name.png, 128, IMAGE ) + + + + diff --git a/resources/CinderApp.icns b/resources/CinderApp.icns new file mode 100644 index 0000000..e3f05c8 Binary files /dev/null and b/resources/CinderApp.icns differ diff --git a/src/voicemachine_configApp.cpp b/src/voicemachine_configApp.cpp new file mode 100644 index 0000000..8c20d91 --- /dev/null +++ b/src/voicemachine_configApp.cpp @@ -0,0 +1,34 @@ +#include "cinder/app/App.h" +#include "cinder/app/RendererGl.h" +#include "cinder/gl/gl.h" + +using namespace ci; +using namespace ci::app; +using namespace std; + +class voicemachine_configApp : public App { + public: + void setup() override; + void mouseDown( MouseEvent event ) override; + void update() override; + void draw() override; +}; + +void voicemachine_configApp::setup() +{ +} + +void voicemachine_configApp::mouseDown( MouseEvent event ) +{ +} + +void voicemachine_configApp::update() +{ +} + +void voicemachine_configApp::draw() +{ + gl::clear( Color( 0, 0, 0 ) ); +} + +CINDER_APP( voicemachine_configApp, RendererGl ) diff --git a/xcode/Info.plist b/xcode/Info.plist new file mode 100644 index 0000000..838dc0d --- /dev/null +++ b/xcode/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + CinderApp.icns + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2015 __MyCompanyName__. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/xcode/voicemachine_config.xcodeproj/project.pbxproj b/xcode/voicemachine_config.xcodeproj/project.pbxproj new file mode 100644 index 0000000..611b2a0 --- /dev/null +++ b/xcode/voicemachine_config.xcodeproj/project.pbxproj @@ -0,0 +1,386 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 006D720419952D00008149E2 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 006D720219952D00008149E2 /* AVFoundation.framework */; }; + 006D720519952D00008149E2 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 006D720319952D00008149E2 /* CoreMedia.framework */; }; + 0091D8F90E81B9330029341E /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0091D8F80E81B9330029341E /* OpenGL.framework */; }; + 00B784B30FF439BC000DE1D7 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784AF0FF439BC000DE1D7 /* Accelerate.framework */; }; + 00B784B40FF439BC000DE1D7 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784B00FF439BC000DE1D7 /* AudioToolbox.framework */; }; + 00B784B50FF439BC000DE1D7 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784B10FF439BC000DE1D7 /* AudioUnit.framework */; }; + 00B784B60FF439BC000DE1D7 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B784B20FF439BC000DE1D7 /* CoreAudio.framework */; }; + 00B9955A1B128DF400A5C623 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B995581B128DF400A5C623 /* IOKit.framework */; }; + 00B9955B1B128DF400A5C623 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B995591B128DF400A5C623 /* IOSurface.framework */; }; + 5323E6B20EAFCA74003A9687 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5323E6B10EAFCA74003A9687 /* CoreVideo.framework */; }; + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; + C53DA3F700C24E33AB6710F3 /* Osc.h in Headers */ = {isa = PBXBuildFile; fileRef = 7088759C1E2C41AE8EF7EBDC /* Osc.h */; }; + 50F2065501884448A2FACB12 /* Osc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C8900433FD9D45DC91294DF4 /* Osc.cpp */; }; + 9D815DE33C6D46F7927AB495 /* voicemachine_config_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 351F2451C9BC4E5FA96011BC /* voicemachine_config_Prefix.pch */; }; + 0948BE34441A47E49CD52A12 /* CinderApp.icns in Resources */ = {isa = PBXBuildFile; fileRef = 6465830879024FB5AA56E8A6 /* CinderApp.icns */; }; + 02197BA1584E4F08846C5AE3 /* Resources.h in Headers */ = {isa = PBXBuildFile; fileRef = 89A6DB43590A4AB3934CCFC6 /* Resources.h */; }; + FA813E13F95847E286C748C2 /* voicemachine_configApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EAC0EA4C76E249AE96D209FC /* voicemachine_configApp.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 006D720219952D00008149E2 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + 006D720319952D00008149E2 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + 0091D8F80E81B9330029341E /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = /System/Library/Frameworks/OpenGL.framework; sourceTree = ""; }; + 00B784AF0FF439BC000DE1D7 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; + 00B784B00FF439BC000DE1D7 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 00B784B10FF439BC000DE1D7 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; + 00B784B20FF439BC000DE1D7 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; + 00B995581B128DF400A5C623 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; + 00B995591B128DF400A5C623 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; }; + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; + 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 5323E6B10EAFCA74003A9687 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = /System/Library/Frameworks/CoreVideo.framework; sourceTree = ""; }; + 8D1107320486CEB800E47090 /* voicemachine_config.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = voicemachine_config.app; sourceTree = BUILT_PRODUCTS_DIR; }; + EAC0EA4C76E249AE96D209FC /* voicemachine_configApp.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.cpp; path = ../src/voicemachine_configApp.cpp; sourceTree = ""; name = voicemachine_configApp.cpp; }; + 89A6DB43590A4AB3934CCFC6 /* Resources.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ../include/Resources.h; sourceTree = ""; name = Resources.h; }; + 6465830879024FB5AA56E8A6 /* CinderApp.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = ../resources/CinderApp.icns; sourceTree = ""; name = CinderApp.icns; }; + 62633A4314FA45FBA3C6FE21 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; name = Info.plist; }; + 351F2451C9BC4E5FA96011BC /* voicemachine_config_Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = "\"\""; path = voicemachine_config_Prefix.pch; sourceTree = ""; name = voicemachine_config_Prefix.pch; }; + C8900433FD9D45DC91294DF4 /* Osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.cpp; path = ../blocks/OSC/src/cinder/osc/Osc.cpp; sourceTree = ""; name = Osc.cpp; }; + 7088759C1E2C41AE8EF7EBDC /* Osc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ../blocks/OSC/src/cinder/osc/Osc.h; sourceTree = ""; name = Osc.h; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8D11072E0486CEB800E47090 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 006D720419952D00008149E2 /* AVFoundation.framework in Frameworks */, + 006D720519952D00008149E2 /* CoreMedia.framework in Frameworks */, + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, + 0091D8F90E81B9330029341E /* OpenGL.framework in Frameworks */, + 5323E6B20EAFCA74003A9687 /* CoreVideo.framework in Frameworks */, + 00B784B30FF439BC000DE1D7 /* Accelerate.framework in Frameworks */, + 00B784B40FF439BC000DE1D7 /* AudioToolbox.framework in Frameworks */, + 00B784B50FF439BC000DE1D7 /* AudioUnit.framework in Frameworks */, + 00B784B60FF439BC000DE1D7 /* CoreAudio.framework in Frameworks */, + 00B9955A1B128DF400A5C623 /* IOKit.framework in Frameworks */, + 00B9955B1B128DF400A5C623 /* IOSurface.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Source */ = { + isa = PBXGroup; + children = ( + EAC0EA4C76E249AE96D209FC /* voicemachine_configApp.cpp */, + ); + name = Source; + sourceTree = ""; + }; + 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { + isa = PBXGroup; + children = ( + 006D720219952D00008149E2 /* AVFoundation.framework */, + 006D720319952D00008149E2 /* CoreMedia.framework */, + 00B784AF0FF439BC000DE1D7 /* Accelerate.framework */, + 00B784B00FF439BC000DE1D7 /* AudioToolbox.framework */, + 00B784B10FF439BC000DE1D7 /* AudioUnit.framework */, + 00B784B20FF439BC000DE1D7 /* CoreAudio.framework */, + 5323E6B10EAFCA74003A9687 /* CoreVideo.framework */, + 0091D8F80E81B9330029341E /* OpenGL.framework */, + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, + 00B995581B128DF400A5C623 /* IOKit.framework */, + 00B995591B128DF400A5C623 /* IOSurface.framework */, + ); + name = "Linked Frameworks"; + sourceTree = ""; + }; + 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 29B97324FDCFA39411CA2CEA /* AppKit.framework */, + 29B97325FDCFA39411CA2CEA /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8D1107320486CEB800E47090 /* voicemachine_config.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* voicemachine_config */ = { + isa = PBXGroup; + children = ( + 01B97315FEAEA392516A2CEA /* Blocks */, + 29B97315FDCFA39411CA2CEA /* Headers */, + 080E96DDFE201D6D7F000001 /* Source */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = voicemachine_config; + sourceTree = ""; + }; + 39959C6DF6CE4DBB850BB996 /* osc */ = { + isa = PBXGroup; + children = ( + C8900433FD9D45DC91294DF4 /* Osc.cpp */, + 7088759C1E2C41AE8EF7EBDC /* Osc.h */, + ); + name = osc; + sourceTree = ""; + }; + 5DE47F8D262846D4972AC090 /* cinder */ = { + isa = PBXGroup; + children = ( + 39959C6DF6CE4DBB850BB996 /* osc */, + ); + name = cinder; + sourceTree = ""; + }; + 959A2B1B2CD04BBCB24180A7 /* src */ = { + isa = PBXGroup; + children = ( + 5DE47F8D262846D4972AC090 /* cinder */, + ); + name = src; + sourceTree = ""; + }; + BAF8E12BD2A5498FB3827280 /* OSC */ = { + isa = PBXGroup; + children = ( + 959A2B1B2CD04BBCB24180A7 /* src */, + ); + name = OSC; + sourceTree = ""; + }; + 01B97315FEAEA392516A2CEA /* Blocks */ = { + isa = PBXGroup; + children = ( + BAF8E12BD2A5498FB3827280 /* OSC */, + ); + name = Blocks; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Headers */ = { + isa = PBXGroup; + children = ( + 89A6DB43590A4AB3934CCFC6 /* Resources.h */, + 351F2451C9BC4E5FA96011BC /* voicemachine_config_Prefix.pch */, + ); + name = Headers; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 6465830879024FB5AA56E8A6 /* CinderApp.icns */, + 62633A4314FA45FBA3C6FE21 /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, + 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8D1107260486CEB800E47090 /* voicemachine_config */ = { + isa = PBXNativeTarget; + buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "voicemachine_config" */; + buildPhases = ( + 8D1107290486CEB800E47090 /* Resources */, + 8D11072C0486CEB800E47090 /* Sources */, + 8D11072E0486CEB800E47090 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = voicemachine_config; + productInstallPath = "$(HOME)/Applications"; + productName = voicemachine_config; + productReference = 8D1107320486CEB800E47090 /* voicemachine_config.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "voicemachine_config" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 29B97314FDCFA39411CA2CEA /* voicemachine_config */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8D1107260486CEB800E47090 /* voicemachine_config */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8D1107290486CEB800E47090 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0948BE34441A47E49CD52A12 /* CinderApp.icns in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8D11072C0486CEB800E47090 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FA813E13F95847E286C748C2 /* voicemachine_configApp.cpp in Sources */, + 50F2065501884448A2FACB12 /* Osc.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C01FCF4B08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = voicemachine_config_Prefix.pch; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + OTHER_LDFLAGS = "\"$(CINDER_PATH)/lib/macosx/$(CONFIGURATION)/libcinder.a\""; + PRODUCT_BUNDLE_IDENTIFIER = "org.libcinder.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = voicemachine_config; + SYMROOT = ./build; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + C01FCF4C08A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_FAST_MATH = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_INLINES_ARE_PRIVATE_EXTERN = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + "NDEBUG=1", + "$(inherited)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = voicemachine_config_Prefix.pch; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + OTHER_LDFLAGS = "\"$(CINDER_PATH)/lib/macosx/$(CONFIGURATION)/libcinder.a\""; + PRODUCT_BUNDLE_IDENTIFIER = "org.libcinder.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = voicemachine_config; + STRIP_INSTALLED_PRODUCT = YES; + SYMROOT = ./build; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + C01FCF4F08A954540054247B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CINDER_PATH = "../../../../../../../../D/___systems/Cinder"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LIBRARY = "libc++"; + ENABLE_TESTABILITY = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\""; + MACOSX_DEPLOYMENT_TARGET = 10.13; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = ( + "\"$(CINDER_PATH)/include\" ../include", + ../blocks/OSC/src, + ); + }; + name = Debug; + }; + C01FCF5008A954540054247B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CINDER_PATH = "../../../../../../../../D/___systems/Cinder"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; + CLANG_CXX_LIBRARY = "libc++"; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "\"$(CINDER_PATH)/include\""; + MACOSX_DEPLOYMENT_TARGET = 10.13; + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = ( + "\"$(CINDER_PATH)/include\" ../include", + ../blocks/OSC/src, + ); + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "voicemachine_config" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4B08A954540054247B /* Debug */, + C01FCF4C08A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C01FCF4E08A954540054247B /* Build configuration list for PBXProject "voicemachine_config" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C01FCF4F08A954540054247B /* Debug */, + C01FCF5008A954540054247B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/xcode/voicemachine_config_Prefix.pch b/xcode/voicemachine_config_Prefix.pch new file mode 100644 index 0000000..226b4a6 --- /dev/null +++ b/xcode/voicemachine_config_Prefix.pch @@ -0,0 +1,12 @@ +#if defined( __cplusplus ) + #include "cinder/Cinder.h" + + #include "cinder/app/App.h" + + #include "cinder/gl/gl.h" + + #include "cinder/CinderMath.h" + #include "cinder/Matrix.h" + #include "cinder/Vector.h" + #include "cinder/Quaternion.h" +#endif