1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/modules/juce_osc/osc/juce_OSCSender.cpp

636 lines
23 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
namespace
{
//==============================================================================
/** Writes OSC data to an internal memory buffer, which grows as required.
The data that was written into the stream can then be accessed later as
a contiguous block of memory.
This class implements the Open Sound Control 1.0 Specification for
the format in which the OSC data will be written into the buffer.
*/
struct OSCOutputStream
{
OSCOutputStream() noexcept {}
/** Returns a pointer to the data that has been written to the stream. */
const void* getData() const noexcept { return output.getData(); }
/** Returns the number of bytes of data that have been written to the stream. */
size_t getDataSize() const noexcept { return output.getDataSize(); }
//==============================================================================
bool writeInt32 (int32 value)
{
return output.writeIntBigEndian (value);
}
bool writeUint64 (uint64 value)
{
return output.writeInt64BigEndian (int64 (value));
}
bool writeFloat32 (float value)
{
return output.writeFloatBigEndian (value);
}
bool writeString (const String& value)
{
if (! output.writeString (value))
return false;
const size_t numPaddingZeros = ~value.length() & 3;
return output.writeRepeatedByte ('\0', numPaddingZeros);
}
bool writeBlob (const MemoryBlock& blob)
{
if (! (output.writeIntBigEndian ((int) blob.getSize())
&& output.write (blob.getData(), blob.getSize())))
return false;
const size_t numPaddingZeros = ~(blob.getSize() - 1) & 3;
return output.writeRepeatedByte (0, numPaddingZeros);
}
bool writeTimeTag (OSCTimeTag timeTag)
{
return output.writeInt64BigEndian (int64 (timeTag.getRawTimeTag()));
}
bool writeAddress (const OSCAddress& address)
{
return writeString (address.toString());
}
bool writeAddressPattern (const OSCAddressPattern& ap)
{
return writeString (ap.toString());
}
bool writeTypeTagString (const OSCTypeList& typeList)
{
output.writeByte (',');
if (typeList.size() > 0)
output.write (typeList.begin(), (size_t) typeList.size());
output.writeByte ('\0');
size_t bytesWritten = (size_t) typeList.size() + 1;
size_t numPaddingZeros = ~bytesWritten & 0x03;
return output.writeRepeatedByte ('\0', numPaddingZeros);
}
bool writeArgument (const OSCArgument& arg)
{
switch (arg.getType())
{
case OSCTypes::int32: return writeInt32 (arg.getInt32());
case OSCTypes::float32: return writeFloat32 (arg.getFloat32());
case OSCTypes::string: return writeString (arg.getString());
case OSCTypes::blob: return writeBlob (arg.getBlob());
default:
// In this very unlikely case you supplied an invalid OSCType!
jassertfalse;
return false;
}
}
//==============================================================================
bool writeMessage (const OSCMessage& msg)
{
if (! writeAddressPattern (msg.getAddressPattern()))
return false;
OSCTypeList typeList;
for (OSCArgument* arg = msg.begin(); arg != msg.end(); ++arg)
typeList.add (arg->getType());
if (! writeTypeTagString (typeList))
return false;
for (OSCArgument* arg = msg.begin(); arg != msg.end(); ++arg)
if (! writeArgument (*arg))
return false;
return true;
}
bool writeBundle (const OSCBundle& bundle)
{
if (! writeString ("#bundle"))
return false;
if (! writeTimeTag (bundle.getTimeTag()))
return false;
for (OSCBundle::Element* element = bundle.begin(); element != bundle.end(); ++element)
if (! writeBundleElement (*element))
return false;
return true;
}
//==============================================================================
bool writeBundleElement (const OSCBundle::Element& element)
{
const int64 startPos = output.getPosition();
if (! writeInt32 (0)) // writing dummy value for element size
return false;
if (element.isBundle())
{
if (! writeBundle (element.getBundle()))
return false;
}
else
{
if (! writeMessage (element.getMessage()))
return false;
}
const int64 endPos = output.getPosition();
const int64 elementSize = endPos - (startPos + 4);
return output.setPosition (startPos)
&& writeInt32 ((int32) elementSize)
&& output.setPosition (endPos);
}
private:
MemoryOutputStream output;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSCOutputStream)
};
} // namespace
//==============================================================================
struct OSCSender::Pimpl
{
Pimpl() noexcept : targetPortNumber (0) {}
~Pimpl() noexcept { disconnect(); }
//==============================================================================
bool connect (const String& newTargetHost, int newTargetPort)
{
if (! disconnect())
return false;
socket = new DatagramSocket (true);
targetHostName = newTargetHost;
targetPortNumber = newTargetPort;
if (socket->bindToPort (0)) // 0 = use any local port assigned by the OS.
return true;
socket = nullptr;
return false;
}
bool disconnect()
{
socket = nullptr;
return true;
}
//==============================================================================
bool send (const OSCMessage& message, const String& hostName, int portNumber)
{
OSCOutputStream outStream;
outStream.writeMessage (message);
return sendOutputStream (outStream, hostName, portNumber);
}
bool send (const OSCBundle& bundle, const String& hostName, int portNumber)
{
OSCOutputStream outStream;
outStream.writeBundle (bundle);
return sendOutputStream (outStream, hostName, portNumber);
}
bool send (const OSCMessage& message) { return send (message, targetHostName, targetPortNumber); }
bool send (const OSCBundle& bundle) { return send (bundle, targetHostName, targetPortNumber); }
private:
//==============================================================================
bool sendOutputStream (OSCOutputStream& outStream, const String& hostName, int portNumber)
{
if (socket != nullptr)
{
const int streamSize = (int) outStream.getDataSize();
const int bytesWritten = socket->write (hostName, portNumber,
outStream.getData(), streamSize);
return bytesWritten == streamSize;
}
// if you hit this, you tried to send some OSC data without being
// connected to a port! You should call OSCSender::connect() first.
jassertfalse;
return false;
}
//==============================================================================
ScopedPointer<DatagramSocket> socket;
String targetHostName;
int targetPortNumber;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
//==============================================================================
OSCSender::OSCSender() : pimpl (new Pimpl())
{
}
OSCSender::~OSCSender()
{
pimpl->disconnect();
pimpl = nullptr;
}
//==============================================================================
bool OSCSender::connect (const String& targetHostName, int targetPortNumber)
{
return pimpl->connect (targetHostName, targetPortNumber);
}
bool OSCSender::disconnect()
{
return pimpl->disconnect();
}
//==============================================================================
bool OSCSender::send (const OSCMessage& message) { return pimpl->send (message); }
bool OSCSender::send (const OSCBundle& bundle) { return pimpl->send (bundle); }
bool OSCSender::sendToIPAddress (const String& host, int port, const OSCMessage& message) { return pimpl->send (message, host, port); }
bool OSCSender::sendToIPAddress (const String& host, int port, const OSCBundle& bundle) { return pimpl->send (bundle, host, port); }
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class OSCBinaryWriterTests : public UnitTest
{
public:
OSCBinaryWriterTests() : UnitTest ("OSCBinaryWriter class") {}
void runTest()
{
beginTest ("writing OSC addresses");
{
OSCOutputStream outStream;
const char check[16] = { '/', 't', 'e', 's', 't', '/', 'f', 'a', 'd', 'e', 'r', '7', '\0', '\0', '\0', '\0' };
OSCAddress address ("/test/fader7");
expect (outStream.writeAddress (address));
expect (outStream.getDataSize() == sizeof (check));
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
beginTest ("writing OSC address patterns");
{
OSCOutputStream outStream;
const char check[20] = { '/', '*', '/', '*', 'p', 'u', 't', '/', 'f', 'a', 'd', 'e', 'r', '[', '0', '-', '9', ']', '\0', '\0' };
OSCAddressPattern ap ("/*/*put/fader[0-9]");
expect (outStream.writeAddressPattern (ap));
expect (outStream.getDataSize() == sizeof (check));
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
beginTest ("writing OSC time tags");
{
OSCOutputStream outStream;
const char check[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
OSCTimeTag tag;
expect (outStream.writeTimeTag (tag));
expect (outStream.getDataSize() == 8);
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
beginTest ("writing OSC type tag strings");
{
{
OSCOutputStream outStream;
OSCTypeList list;
const char check[4] = { ',', '\0', '\0', '\0' };
expect (outStream.writeTypeTagString (list));
expect (outStream.getDataSize() == 4);
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
{
OSCOutputStream outStream;
OSCTypeList list;
list.add (OSCTypes::int32);
list.add (OSCTypes::float32);
const char check[4] = { ',', 'i', 'f', '\0' };
expect (outStream.writeTypeTagString (list));
expect (outStream.getDataSize() == sizeof (check));
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
{
OSCOutputStream outStream;
OSCTypeList list;
list.add (OSCTypes::blob);
list.add (OSCTypes::blob);
list.add (OSCTypes::string);
const char check[8] = { ',', 'b', 'b', 's', '\0', '\0', '\0', '\0' };
expect (outStream.writeTypeTagString (list));
expect (outStream.getDataSize() == sizeof (check));
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
}
beginTest ("writing OSC arguments");
{
// test data:
int testInt = -2015;
const uint8 testIntRepresentation[] = { 0xFF, 0xFF, 0xF8, 0x21 }; // big endian two's complement
float testFloat = 345.6125f;
const uint8 testFloatRepresentation[] = { 0x43, 0xAC, 0xCE, 0x66 }; // big endian IEEE 754
String testString = "Hello, World!";
const char testStringRepresentation[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0', '\0', '\0' }; // padded to size % 4 == 0
const uint8 testBlobData[] = { 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
const MemoryBlock testBlob (testBlobData, sizeof (testBlobData));
const uint8 testBlobRepresentation[] = { 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00 }; // padded to size % 4 == 0
// write:
{
// int32:
OSCArgument arg (testInt);
OSCOutputStream outStream;
expect (outStream.writeArgument (arg));
expect (outStream.getDataSize() == 4);
expect (std::memcmp (outStream.getData(), testIntRepresentation, sizeof (testIntRepresentation)) == 0);
}
{
// float32:
OSCArgument arg (testFloat);
OSCOutputStream outStream;
expect (outStream.writeArgument (arg));
expect (outStream.getDataSize() == 4);
expect (std::memcmp (outStream.getData(), testFloatRepresentation, sizeof (testFloatRepresentation)) == 0);
}
{
// string:
expect (testString.length() % 4 != 0); // check whether we actually cover padding
expect (sizeof (testStringRepresentation) % 4 == 0);
OSCArgument arg (testString);
OSCOutputStream outStream;
expect (outStream.writeArgument (arg));
expect (outStream.getDataSize() == sizeof (testStringRepresentation));
expect (std::memcmp (outStream.getData(), testStringRepresentation, sizeof (testStringRepresentation)) == 0);
}
{
// blob:
expect (testBlob.getSize() % 4 != 0); // check whether we actually cover padding
expect (sizeof (testBlobRepresentation) % 4 == 0);
OSCArgument arg (testBlob);
OSCOutputStream outStream;
expect (outStream.writeArgument (arg));
expect (outStream.getDataSize() == sizeof (testBlobRepresentation));
expect (std::memcmp (outStream.getData(), testBlobRepresentation, sizeof (testBlobRepresentation)) == 0);
}
}
beginTest ("Writing strings with correct padding");
{
// the only OSC-specific thing to check is the correct number of padding zeros
{
OSCArgument with15Chars ("123456789012345");
OSCOutputStream outStream;
expect (outStream.writeArgument (with15Chars));
expect (outStream.getDataSize() == 16);
}
{
OSCArgument with16Chars ("1234567890123456");
OSCOutputStream outStream;
expect (outStream.writeArgument (with16Chars));
expect (outStream.getDataSize() == 20);
}
{
OSCArgument with17Chars ("12345678901234567");
OSCOutputStream outStream;
expect (outStream.writeArgument (with17Chars));
expect (outStream.getDataSize() == 20);
}
{
OSCArgument with18Chars ("123456789012345678");
OSCOutputStream outStream;
expect (outStream.writeArgument (with18Chars));
expect (outStream.getDataSize() == 20);
}
{
OSCArgument with19Chars ("1234567890123456789");
OSCOutputStream outStream;
expect (outStream.writeArgument (with19Chars));
expect (outStream.getDataSize() == 20);
}
{
OSCArgument with20Chars ("12345678901234567890");
OSCOutputStream outStream;
expect (outStream.writeArgument (with20Chars));
expect (outStream.getDataSize() == 24);
}
}
beginTest ("Writing blobs with correct padding");
{
const char buffer[20] = {};
{
OSCArgument with15Bytes (MemoryBlock (buffer, 15));
OSCOutputStream outStream;
expect (outStream.writeArgument (with15Bytes));
expect (outStream.getDataSize() == 20);
}
{
OSCArgument with16Bytes (MemoryBlock (buffer, 16));
OSCOutputStream outStream;
expect (outStream.writeArgument (with16Bytes));
expect (outStream.getDataSize() == 20);
}
{
OSCArgument with17Bytes (MemoryBlock (buffer, 17));
OSCOutputStream outStream;
expect (outStream.writeArgument (with17Bytes));
expect (outStream.getDataSize() == 24);
}
{
OSCArgument with18Bytes (MemoryBlock (buffer, 18));
OSCOutputStream outStream;
expect (outStream.writeArgument (with18Bytes));
expect (outStream.getDataSize() == 24);
}
{
OSCArgument with19Bytes (MemoryBlock (buffer, 19));
OSCOutputStream outStream;
expect (outStream.writeArgument (with19Bytes));
expect (outStream.getDataSize() == 24);
}
{
OSCArgument with20Bytes (MemoryBlock (buffer, 20));
OSCOutputStream outStream;
expect (outStream.writeArgument (with20Bytes));
expect (outStream.getDataSize() == 24);
}
}
beginTest ("Writing OSC messages.");
{
{
int32 testInt = -2015;
float testFloat = 345.6125f;
String testString = "Hello, World!";
const uint8 testBlobData[] = { 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
const MemoryBlock testBlob (testBlobData, sizeof (testBlobData));
uint8 check[52] = { '/', 't', 'e', 's', 't', '\0', '\0', '\0',
',', 'i', 'f', 's', 'b', '\0', '\0', '\0',
0xFF, 0xFF, 0xF8, 0x21,
0x43, 0xAC, 0xCE, 0x66,
'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0', '\0', '\0',
0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00
};
OSCOutputStream outStream;
OSCMessage msg ("/test");
msg.addInt32 (testInt);
msg.addFloat32 (testFloat);
msg.addString (testString);
msg.addBlob (testBlob);
expect (outStream.writeMessage (msg));
expect (outStream.getDataSize() == sizeof (check));
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
}
beginTest ("Writing OSC bundle.");
{
{
int32 testInt = -2015;
float testFloat = 345.6125f;
String testString = "Hello, World!";
const uint8 testBlobData[] = { 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
const MemoryBlock testBlob (testBlobData, sizeof (testBlobData));
uint8 check[] = {
'#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x34,
'/', 't', 'e', 's', 't', '/', '1', '\0',
',', 'i', 'f', 's', 'b', '\0', '\0', '\0',
0xFF, 0xFF, 0xF8, 0x21,
0x43, 0xAC, 0xCE, 0x66,
'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0', '\0', '\0',
0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C,
'/', 't', 'e', 's', 't', '/', '2', '\0',
',', '\0', '\0', '\0',
0x00, 0x00, 0x00, 0x10,
'#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
OSCOutputStream outStream;
OSCBundle bundle;
OSCMessage msg1 ("/test/1");
msg1.addInt32 (testInt);
msg1.addFloat32 (testFloat);
msg1.addString (testString);
msg1.addBlob (testBlob);
bundle.addElement (msg1);
OSCMessage msg2 ("/test/2");
bundle.addElement (msg2);
OSCBundle subBundle;
bundle.addElement (subBundle);
expect (outStream.writeBundle (bundle));
expect (outStream.getDataSize() == sizeof (check));
expect (std::memcmp (outStream.getData(), check, sizeof (check)) == 0);
}
}
}
};
static OSCBinaryWriterTests OSCBinaryWriterUnitTests;
#endif // JUCE_UNIT_TESTS