init
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
PROJECT(openvibe-plugins-contrib-network-io)
|
||||
|
||||
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
|
||||
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
|
||||
|
||||
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl)
|
||||
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
|
||||
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
FOLDER ${PLUGINS_FOLDER}
|
||||
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
|
||||
|
||||
# ---------------------------------
|
||||
INCLUDE("FindOpenViBE")
|
||||
INCLUDE("FindOpenViBECommon")
|
||||
INCLUDE("FindOpenViBEToolkit")
|
||||
# INCLUDE("FindOpenViBEModuleEBML")
|
||||
INCLUDE("FindThirdPartyLSL")
|
||||
|
||||
# -----------------------------
|
||||
# Install files
|
||||
# -----------------------------
|
||||
INSTALL(TARGETS ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION ${DIST_BINDIR}
|
||||
LIBRARY DESTINATION ${DIST_LIBDIR}
|
||||
ARCHIVE DESTINATION ${DIST_LIBDIR})
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_OSCController OSC Controller
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Description|
|
||||
By this box, you can control OSC supporting devices, such as
|
||||
synthesizers and oscillators. It can be used to turn OpenViBE
|
||||
streams into sound, e.g. for brain music or auditory BCI.
|
||||
|
||||
The OSC Controller box simply sends the incoming data as UDP
|
||||
messages to the specified OSC Server. Each message is flagged with
|
||||
an OSC Address specifying the device that the message is intended to
|
||||
control.
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Inputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Inputs|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Input1|
|
||||
Data to send to the OSC Server. This input can be either a signal, a matrix, or a stimulation. The signal or matrix must have only 1 channel (row). Signals are sent as float and stimulations as size_t.
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Input1|
|
||||
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Settings|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Setting1|
|
||||
Server address (IP or DNS)
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Setting1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Setting2|
|
||||
Server port
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Setting2|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Setting3|
|
||||
The OSC Address specifying the device the messages are intended to, e.g. /oscillator/4/frequency
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Setting3|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Examples|
|
||||
Utilities such as OSC DataMonitor by Kasper Kamperman can be used to debug the messages sent out by the box.
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_OSCController_Miscellaneous|
|
||||
In the current implementation, the data is sent when received. OpenViBE internal timing is not passed to the OSC.
|
||||
The box supports only one input, but the input type can be modified. The input must have only one channel. For signals, you can extract a
|
||||
channel by using the Channel Selector box. Also, if input type is a signal, its sampling rate is ignored.
|
||||
However, several OSC Controller boxes can be used at the same time if multiple sources are needed to be
|
||||
sent to the OSC Server(s) or device(s). These limitations are to keep the code simple.
|
||||
|
||||
The box is not intended to transmit large chunks of numeric data. It may be meaningful to limit the
|
||||
amount of data on the OpenViBE side e.g. with boxes such as Signal Average, Downsampling or Signal Decimation, or Stimulation Filter.
|
||||
|
||||
For more information about the OSC protocol and applications that support it, please see http://www.opensoundcontrol.org
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_OSCController_Miscellaneous|
|
||||
*/
|
||||
+850
@@ -0,0 +1,850 @@
|
||||
/**
|
||||
OSCPKT : a minimalistic OSC ( http://opensoundcontrol.org ) c++ library
|
||||
|
||||
Before using this file please take the time to read the OSC spec, it
|
||||
is short and not complicated: http://opensoundcontrol.org/spec-1_0
|
||||
|
||||
Features:
|
||||
- handles basic OSC types: TFihfdsb
|
||||
- handles bundles
|
||||
- handles OSC pattern-matching rules (wildcards etc in message paths)
|
||||
- portable on win / macos / linux
|
||||
- robust wrt malformed packets
|
||||
- optional udp transport for packets
|
||||
- concise, all in a single .h file
|
||||
- does not throw exceptions
|
||||
|
||||
does not:
|
||||
- take into account timestamp values.
|
||||
- provide a cpu-scalable message dispatching.
|
||||
- not suitable for use inside a realtime thread as it allocates memory when
|
||||
building or reading messages.
|
||||
|
||||
|
||||
There are basically 3 classes of interest:
|
||||
- oscpkt::Message : read/write the content of an OSC message
|
||||
- oscpkt::PacketReader : read the bundles/messages embedded in an OSC packet
|
||||
- oscpkt::PacketWriter : write bundles/messages into an OSC packet
|
||||
|
||||
And optionaly:
|
||||
- oscpkt::UdpSocket : read/write OSC packets over UDP.
|
||||
|
||||
example: oscpkt_demo.cc
|
||||
example: oscpkt_test.cc
|
||||
*/
|
||||
|
||||
/* Copyright (C) 2010 Julien Pommier
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
(this is the zlib license)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <stdint.h>
|
||||
#else
|
||||
namespace oscpkt {
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
}
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
#if defined(OSCPKT_OSTREAM_OUTPUT) || defined(OSCPKT_TEST)
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
namespace oscpkt {
|
||||
|
||||
/**
|
||||
OSC timetag stuff, the highest 32-bit are seconds, the lowest are fraction of a second.
|
||||
*/
|
||||
class TimeTag
|
||||
{
|
||||
uint64_t v = 1;
|
||||
public:
|
||||
TimeTag() {}
|
||||
explicit TimeTag(const uint64_t w): v(w) {}
|
||||
operator uint64_t() const { return v; }
|
||||
static TimeTag immediate() { return TimeTag(1); }
|
||||
};
|
||||
|
||||
/* the various types that we handle (OSC 1.0 specifies that INT32/FLOAT/STRING/BLOB are the bare minimum) */
|
||||
enum
|
||||
{
|
||||
TYPE_TAG_TRUE = 'T',
|
||||
TYPE_TAG_FALSE = 'F',
|
||||
TYPE_TAG_INT32 = 'i',
|
||||
TYPE_TAG_INT64 = 'h',
|
||||
TYPE_TAG_FLOAT = 'f',
|
||||
TYPE_TAG_DOUBLE = 'd',
|
||||
TYPE_TAG_STRING = 's',
|
||||
TYPE_TAG_BLOB = 'b'
|
||||
};
|
||||
|
||||
/* a few utility functions follow.. */
|
||||
|
||||
// round to the next multiple of 4, works for size_t and pointer arguments
|
||||
template <typename Type>
|
||||
Type ceil4(Type p) { return Type((size_t(p) + 3) & (~size_t(3))); }
|
||||
|
||||
// check that a memory area is zero padded until the next address which is a multiple of 4
|
||||
inline bool isZeroPaddingCorrect(const char* p)
|
||||
{
|
||||
const char* q = ceil4(p);
|
||||
for (; p < q; ++p) { if (*p != 0) { return false; } }
|
||||
return true;
|
||||
}
|
||||
|
||||
// stuff for reading / writing POD ("Plain Old Data") variables to unaligned bytes.
|
||||
template <typename POD>
|
||||
union PodBytes
|
||||
{
|
||||
char bytes[sizeof(POD)];
|
||||
POD value;
|
||||
};
|
||||
|
||||
inline bool isBigEndian()
|
||||
{ // a compile-time constant would certainly improve performances..
|
||||
PodBytes<int32_t> p;
|
||||
p.value = 0x12345678;
|
||||
return p.bytes[0] == 0x12;
|
||||
}
|
||||
|
||||
/** read unaligned bytes into a POD type, assuming the bytes are a little endian representation */
|
||||
template <typename POD>
|
||||
POD bytes2pod(const char* bytes)
|
||||
{
|
||||
PodBytes<POD> p;
|
||||
for (size_t i = 0; i < sizeof(POD); ++i)
|
||||
{
|
||||
if (isBigEndian()) { p.bytes[i] = bytes[i]; }
|
||||
else { p.bytes[i] = bytes[sizeof(POD) - i - 1]; }
|
||||
}
|
||||
return p.value;
|
||||
}
|
||||
|
||||
/** stored a POD type into an unaligned bytes array, using little endian representation */
|
||||
template <typename POD>
|
||||
void pod2bytes(const POD value, char* bytes)
|
||||
{
|
||||
PodBytes<POD> p;
|
||||
p.value = value;
|
||||
for (size_t i = 0; i < sizeof(POD); ++i)
|
||||
{
|
||||
if (isBigEndian()) { bytes[i] = p.bytes[i]; }
|
||||
else { bytes[i] = p.bytes[sizeof(POD) - i - 1]; }
|
||||
}
|
||||
}
|
||||
|
||||
/** internal stuff, handles the dynamic storage with correct alignments to 4 bytes */
|
||||
struct Storage
|
||||
{
|
||||
std::vector<char> data;
|
||||
Storage() { data.reserve(200); }
|
||||
|
||||
char* getBytes(const size_t sz)
|
||||
{
|
||||
assert((data.size() & 3) == 0);
|
||||
if (data.size() + sz > data.capacity()) { data.reserve((data.size() + sz) * 2); }
|
||||
const size_t sz4 = ceil4(sz);
|
||||
const size_t pos = data.size();
|
||||
data.resize(pos + sz4); // resize will fill with zeros, so the zero padding is OK
|
||||
return &(data[pos]);
|
||||
}
|
||||
|
||||
char* begin() { return !data.empty() ? &data.front() : nullptr; }
|
||||
char* end() { return begin() + size(); }
|
||||
const char* begin() const { return !data.empty() ? &data.front() : nullptr; }
|
||||
const char* end() const { return begin() + size(); }
|
||||
size_t size() const { return data.size(); }
|
||||
void assign(const char* beg, const char* end) { data.assign(beg, end); }
|
||||
void clear() { data.resize(0); }
|
||||
};
|
||||
|
||||
/** check if the path matches the supplied path pattern , according to the OSC spec pattern rules ('*' and '//' wildcards, '{}' alternatives, brackets etc) */
|
||||
bool fullPatternMatch(const std::string& pattern, const std::string& path);
|
||||
/** check if the path matches the beginning of pattern */
|
||||
bool partialPatternMatch(const std::string& pattern, const std::string& path);
|
||||
|
||||
#if defined(OSCPKT_DEBUG)
|
||||
#define OSCPKT_SET_ERR(errcode) do { if (!err) { err = errcode; std::cerr << "set " #errcode << " at line " << __LINE__ << "\n"; } } while (0)
|
||||
#else
|
||||
#define OSCPKT_SET_ERR(errcode) do { if (!err) err = errcode; } while (0)
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
OK_NO_ERROR=0,
|
||||
// errors raised by the Message class:
|
||||
MALFORMED_ADDRESS_PATTERN,
|
||||
MALFORMED_TYPE_TAGS,
|
||||
MALFORMED_ARGUMENTS,
|
||||
UNHANDLED_TYPE_TAGS,
|
||||
// errors raised by ArgReader
|
||||
TYPE_MISMATCH,
|
||||
NOT_ENOUGH_ARG,
|
||||
PATTERN_MISMATCH,
|
||||
// errors raised by PacketReader/PacketWriter
|
||||
INVALID_BUNDLE,
|
||||
INVALID_PACKET_SIZE,
|
||||
BUNDLE_REQUIRED_FOR_MULTI_MESSAGES
|
||||
} ErrorCode;
|
||||
|
||||
/**
|
||||
struct used to hold an OSC message that will be written or read.
|
||||
|
||||
The list of arguments is exposed as a sort of queue. You "pop"
|
||||
arguments from the front of the queue when reading, you push
|
||||
arguments at the back of the queue when writing.
|
||||
|
||||
Many functions return *this, so they can be chained: init("/foo").pushInt32(2).pushStr("kllk")...
|
||||
|
||||
Example of use:
|
||||
|
||||
creation of a message:
|
||||
@code
|
||||
msg.init("/foo").pushInt32(4).pushStr("bar");
|
||||
@endcode
|
||||
reading a message, with error detection:
|
||||
@code
|
||||
if (msg.match("/foo/b*ar/plop")) {
|
||||
int i; std::string s; std::vector<char> b;
|
||||
if (msg.arg().popInt32(i).popStr(s).popBlob(b).isOkNoMoreArgs()) {
|
||||
process message...;
|
||||
} else arguments mismatch;
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
class Message
|
||||
{
|
||||
TimeTag time_tag;
|
||||
std::string address;
|
||||
std::string type_tags;
|
||||
std::vector<std::pair<size_t, size_t>> arguments; // array of pairs (pos,size), pos being an index into the 'storage' array.
|
||||
Storage storage; // the arguments data is stored here
|
||||
ErrorCode err;
|
||||
public:
|
||||
/** ArgReader is used for popping arguments from a Message, holds a
|
||||
pointer to the original Message, and maintains a local error code */
|
||||
class ArgReader
|
||||
{
|
||||
const Message* msg;
|
||||
ErrorCode err;
|
||||
size_t arg_idx; // arg index of the next arg that will be popped out.
|
||||
public:
|
||||
ArgReader(const Message& m, const ErrorCode e = OK_NO_ERROR) : msg(&m), err(msg->getErr()), arg_idx(0)
|
||||
{
|
||||
if (e != OK_NO_ERROR && err == OK_NO_ERROR) { err = e; }
|
||||
}
|
||||
|
||||
ArgReader(const ArgReader& other) : msg(other.msg), err(other.err), arg_idx(other.arg_idx) {}
|
||||
bool isBool() { return currentTypeTag() == TYPE_TAG_TRUE || currentTypeTag() == TYPE_TAG_FALSE; }
|
||||
bool isInt32() { return currentTypeTag() == TYPE_TAG_INT32; }
|
||||
bool isInt64() { return currentTypeTag() == TYPE_TAG_INT64; }
|
||||
bool isFloat() { return currentTypeTag() == TYPE_TAG_FLOAT; }
|
||||
bool isDouble() { return currentTypeTag() == TYPE_TAG_DOUBLE; }
|
||||
bool isStr() { return currentTypeTag() == TYPE_TAG_STRING; }
|
||||
bool isBlob() { return currentTypeTag() == TYPE_TAG_BLOB; }
|
||||
|
||||
size_t nbArgRemaining() const { return msg->arguments.size() - arg_idx; }
|
||||
bool isOk() const { return err == OK_NO_ERROR; }
|
||||
operator bool() const { return isOk(); } // implicit bool conversion is handy here
|
||||
/** call this at the end of the popXXX() chain to make sure everything is ok and
|
||||
all arguments have been popped */
|
||||
bool isOkNoMoreArgs() const { return err == OK_NO_ERROR && nbArgRemaining() == 0; }
|
||||
ErrorCode getErr() const { return err; }
|
||||
|
||||
/** retrieve an int32_t argument */
|
||||
ArgReader& popInt32(int32_t& i) { return popPod<int32_t>(TYPE_TAG_INT32, i); }
|
||||
/** retrieve an int64_t argument */
|
||||
ArgReader& popInt64(int64_t& i) { return popPod<int64_t>(TYPE_TAG_INT64, i); }
|
||||
/** retrieve a single precision floating point argument */
|
||||
ArgReader& popFloat(float& f) { return popPod<float>(TYPE_TAG_FLOAT, f); }
|
||||
/** retrieve a double precision floating point argument */
|
||||
ArgReader& popDouble(double& d) { return popPod<double>(TYPE_TAG_DOUBLE, d); }
|
||||
/** retrieve a string argument (no check performed on its content, so it may contain any byte value except 0) */
|
||||
ArgReader& popStr(std::string& s)
|
||||
{
|
||||
if (precheck(TYPE_TAG_STRING)) { s = argBeg(arg_idx++); }
|
||||
return *this;
|
||||
}
|
||||
/** retrieve a binary blob */
|
||||
ArgReader& popBlob(std::vector<char>& b)
|
||||
{
|
||||
if (precheck(TYPE_TAG_BLOB))
|
||||
{
|
||||
b.assign(argBeg(arg_idx) + 4, argEnd(arg_idx));
|
||||
++arg_idx;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
/** retrieve a bool argument */
|
||||
ArgReader& popBool(bool& b)
|
||||
{
|
||||
b = false;
|
||||
if (arg_idx >= msg->arguments.size()) { OSCPKT_SET_ERR(NOT_ENOUGH_ARG); }
|
||||
else if (currentTypeTag() == TYPE_TAG_TRUE) { b = true; }
|
||||
else if (currentTypeTag() == TYPE_TAG_FALSE) { b = false; }
|
||||
else { OSCPKT_SET_ERR(TYPE_MISMATCH); }
|
||||
++arg_idx;
|
||||
return *this;
|
||||
}
|
||||
/** skip whatever comes next */
|
||||
ArgReader& pop()
|
||||
{
|
||||
if (arg_idx >= msg->arguments.size()) { OSCPKT_SET_ERR(NOT_ENOUGH_ARG); }
|
||||
else { ++arg_idx; }
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* argBeg(const size_t idx) const
|
||||
{
|
||||
if (err || idx >= msg->arguments.size()) { return nullptr; }
|
||||
return msg->storage.begin() + msg->arguments[idx].first;
|
||||
}
|
||||
|
||||
const char* argEnd(const size_t idx) const
|
||||
{
|
||||
if (err || idx >= msg->arguments.size()) { return nullptr; }
|
||||
return msg->storage.begin() + msg->arguments[idx].first + msg->arguments[idx].second;
|
||||
}
|
||||
|
||||
int currentTypeTag()
|
||||
{
|
||||
if (!err && arg_idx < msg->type_tags.size()) { return msg->type_tags[arg_idx]; }
|
||||
OSCPKT_SET_ERR(NOT_ENOUGH_ARG);
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <typename POD>
|
||||
ArgReader& popPod(const int tag, POD& v)
|
||||
{
|
||||
if (precheck(tag))
|
||||
{
|
||||
v = bytes2pod<POD>(argBeg(arg_idx));
|
||||
++arg_idx;
|
||||
}
|
||||
else { v = POD(0); }
|
||||
return *this;
|
||||
}
|
||||
/* pre-check stuff before popping an argument from the message */
|
||||
bool precheck(const int tag)
|
||||
{
|
||||
if (arg_idx >= msg->arguments.size()) { OSCPKT_SET_ERR(NOT_ENOUGH_ARG); }
|
||||
else if (!err && currentTypeTag() != tag) { OSCPKT_SET_ERR(TYPE_MISMATCH); }
|
||||
return err == OK_NO_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
Message() { clear(); }
|
||||
Message(const std::string& s, const TimeTag tt = TimeTag::immediate()) : time_tag(tt), address(s), err(OK_NO_ERROR) {}
|
||||
|
||||
Message(const void* ptr, const size_t sz, const TimeTag tt = TimeTag::immediate())
|
||||
{
|
||||
buildFromRawData(ptr, sz);
|
||||
time_tag = tt;
|
||||
}
|
||||
|
||||
bool isOk() const { return err == OK_NO_ERROR; }
|
||||
ErrorCode getErr() const { return err; }
|
||||
|
||||
/** return the type_tags string, with its initial ',' stripped. */
|
||||
const std::string& typeTags() const { return type_tags; }
|
||||
/** retrieve the address pattern. If you want to follow to the whole OSC spec, you
|
||||
have to handle its matching rules for address specifications -- this file does
|
||||
not provide this functionality */
|
||||
const std::string& addressPattern() const { return address; }
|
||||
TimeTag timeTag() const { return time_tag; }
|
||||
/** clear the message and start a new message with the supplied address and time_tag. */
|
||||
Message& init(const std::string& addr, const TimeTag tt = TimeTag::immediate())
|
||||
{
|
||||
clear();
|
||||
address = addr;
|
||||
time_tag = tt;
|
||||
if (address.empty() || address[0] != '/') { OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** start a matching test. The typical use-case is to follow this by
|
||||
a sequence of calls to popXXX() and a final call to
|
||||
isOkNoMoreArgs() which will allow to check that everything went
|
||||
fine. For example:
|
||||
@code
|
||||
if (msg.match("/foo").popInt32(i).isOkNoMoreArgs()) { blah(i); }
|
||||
else if (msg.match("/bar").popStr(s).popInt32(i).isOkNoMoreArgs()) { plop(s,i); }
|
||||
else std::cerr << "unhandled message: " << msg << "\n";
|
||||
@endcode
|
||||
*/
|
||||
ArgReader match(const std::string& test) const { return ArgReader(*this, fullPatternMatch(address, test) ? OK_NO_ERROR : PATTERN_MISMATCH); }
|
||||
/** return true if the 'test' path matched by the first characters of addressPattern().
|
||||
For ex. ("/foo/bar").partialMatch("/foo/") is true */
|
||||
ArgReader partialMatch(const std::string& test) const { return ArgReader(*this, partialPatternMatch(address, test) ? OK_NO_ERROR : PATTERN_MISMATCH); }
|
||||
|
||||
ArgReader arg() const { return ArgReader(*this, OK_NO_ERROR); }
|
||||
|
||||
/** build the osc message for raw data (the message will keep a copy of that data) */
|
||||
void buildFromRawData(const void* ptr, const size_t sz)
|
||||
{
|
||||
clear();
|
||||
storage.assign((const char*)ptr, (const char*)ptr + sz);
|
||||
const char* address_beg = storage.begin();
|
||||
const char* address_end = (const char*)memchr(address_beg, 0, storage.end() - address_beg);
|
||||
if (!address_end || !isZeroPaddingCorrect(address_end + 1) || address_beg[0] != '/')
|
||||
{
|
||||
OSCPKT_SET_ERR(MALFORMED_ADDRESS_PATTERN);
|
||||
return;
|
||||
}
|
||||
address.assign(address_beg, address_end);
|
||||
|
||||
const char* type_tags_beg = ceil4(address_end + 1);
|
||||
const char* type_tags_end = (const char*)memchr(type_tags_beg, 0, storage.end() - type_tags_beg);
|
||||
if (!type_tags_end || !isZeroPaddingCorrect(type_tags_end + 1) || type_tags_beg[0] != ',')
|
||||
{
|
||||
OSCPKT_SET_ERR(MALFORMED_TYPE_TAGS);
|
||||
return;
|
||||
}
|
||||
type_tags.assign(type_tags_beg + 1, type_tags_end);
|
||||
// we do not copy the initial ','
|
||||
|
||||
const char* arg = ceil4(type_tags_end + 1);
|
||||
assert(arg <= storage.end());
|
||||
size_t iarg = 0;
|
||||
while (isOk() && iarg < type_tags.size())
|
||||
{
|
||||
assert(arg <= storage.end());
|
||||
size_t len = getArgSize(type_tags[iarg], arg);
|
||||
if (isOk()) { arguments.push_back(std::make_pair(arg - storage.begin(), len)); }
|
||||
arg += ceil4(len);
|
||||
++iarg;
|
||||
}
|
||||
if (iarg < type_tags.size() || arg != storage.end()) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); }
|
||||
}
|
||||
|
||||
/* below are all the functions that serve when *writing* a message */
|
||||
Message& pushBool(const bool b)
|
||||
{
|
||||
type_tags += (b ? TYPE_TAG_TRUE : TYPE_TAG_FALSE);
|
||||
arguments.push_back(std::make_pair(storage.size(), storage.size()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Message& pushInt32(const int32_t i) { return pushPod(TYPE_TAG_INT32, i); }
|
||||
Message& pushInt64(const int64_t h) { return pushPod(TYPE_TAG_INT64, h); }
|
||||
Message& pushFloat(const float f) { return pushPod(TYPE_TAG_FLOAT, f); }
|
||||
Message& pushDouble(const double d) { return pushPod(TYPE_TAG_DOUBLE, d); }
|
||||
|
||||
Message& pushStr(const std::string& s)
|
||||
{
|
||||
assert(s.size() < 2147483647); // insane values are not welcome
|
||||
type_tags += TYPE_TAG_STRING;
|
||||
arguments.push_back(std::make_pair(storage.size(), s.size() + 1));
|
||||
strcpy(storage.getBytes(s.size() + 1), s.c_str());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Message& pushBlob(void* ptr, const size_t size)
|
||||
{
|
||||
assert(size < 2147483647); // insane values are not welcome
|
||||
type_tags += TYPE_TAG_BLOB;
|
||||
arguments.push_back(std::make_pair(storage.size(), size + 4));
|
||||
pod2bytes<int32_t>(int32_t(size), storage.getBytes(4));
|
||||
if (size) { memcpy(storage.getBytes(size), ptr, size); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** reset the message to a clean state */
|
||||
void clear()
|
||||
{
|
||||
address.clear();
|
||||
type_tags.clear();
|
||||
storage.clear();
|
||||
arguments.clear();
|
||||
err = OK_NO_ERROR;
|
||||
time_tag = TimeTag::immediate();
|
||||
}
|
||||
|
||||
/** write the raw message data (used by PacketWriter) */
|
||||
void packMessage(Storage& s, const bool size) const
|
||||
{
|
||||
if (!isOk()) { return; }
|
||||
const size_t addr = address.size() + 1;
|
||||
const size_t type = type_tags.size() + 2;
|
||||
if (size) { pod2bytes<uint32_t>(uint32_t(ceil4(addr) + ceil4(type) + ceil4(storage.size())), s.getBytes(4)); }
|
||||
strcpy(s.getBytes(addr), address.c_str());
|
||||
strcpy(s.getBytes(type), ("," + type_tags).c_str());
|
||||
if (storage.size()) { memcpy(s.getBytes(storage.size()), const_cast<Storage&>(storage).begin(), storage.size()); }
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/* get the number of bytes occupied by the argument */
|
||||
size_t getArgSize(const int type, const char* p)
|
||||
{
|
||||
if (err) { return 0; }
|
||||
size_t sz = 0;
|
||||
assert(p >= storage.begin() && p <= storage.end());
|
||||
switch (type)
|
||||
{
|
||||
case TYPE_TAG_TRUE:
|
||||
case TYPE_TAG_FALSE: sz = 0;
|
||||
break;
|
||||
case TYPE_TAG_INT32:
|
||||
case TYPE_TAG_FLOAT: sz = 4;
|
||||
break;
|
||||
case TYPE_TAG_INT64:
|
||||
case TYPE_TAG_DOUBLE: sz = 8;
|
||||
break;
|
||||
case TYPE_TAG_STRING:
|
||||
{
|
||||
const char* q = (const char*)memchr(p, 0, storage.end() - p);
|
||||
if (!q) { OSCPKT_SET_ERR(MALFORMED_ARGUMENTS); }
|
||||
else { sz = (q - p) + 1; }
|
||||
}
|
||||
break;
|
||||
case TYPE_TAG_BLOB:
|
||||
{
|
||||
if (p == storage.end())
|
||||
{
|
||||
OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
|
||||
return 0;
|
||||
}
|
||||
sz = 4 + bytes2pod<uint32_t>(p);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
OSCPKT_SET_ERR(UNHANDLED_TYPE_TAGS);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (p + sz > storage.end() || /* string or blob too large.. */
|
||||
p + sz < p /* or even blob so large that it did overflow */)
|
||||
{
|
||||
OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
|
||||
return 0;
|
||||
}
|
||||
if (!isZeroPaddingCorrect(p + sz))
|
||||
{
|
||||
OSCPKT_SET_ERR(MALFORMED_ARGUMENTS);
|
||||
return 0;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
template <typename POD>
|
||||
Message& pushPod(const int tag, POD v)
|
||||
{
|
||||
type_tags += char(tag);
|
||||
arguments.push_back(std::make_pair(storage.size(), sizeof(POD)));
|
||||
pod2bytes(v, storage.getBytes(sizeof(POD)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef OSCPKT_OSTREAM_OUTPUT
|
||||
friend std::ostream &operator<<(std::ostream &os, const Message &msg) {
|
||||
os << "osc_address: '" << msg.address << "', types: '" << msg.type_tags << "', timetag=" << msg.time_tag << ", args=[";
|
||||
Message::ArgReader arg(msg);
|
||||
while (arg.nbArgRemaining() && arg.isOk()) {
|
||||
if (arg.isBool()) { bool b; arg.popBool(b); os << (b?"True":"False"); }
|
||||
else if (arg.isInt32()) { int32_t i; arg.popInt32(i); os << i; }
|
||||
else if (arg.isInt64()) { int64_t h; arg.popInt64(h); os << h << "ll"; }
|
||||
else if (arg.isFloat()) { float f; arg.popFloat(f); os << f << "f"; }
|
||||
else if (arg.isDouble()) { double d; arg.popDouble(d); os << d; }
|
||||
else if (arg.isStr()) { std::string s; arg.popStr(s); os << "'" << s << "'"; }
|
||||
else if (arg.isBlob()) { std::vector<char> b; arg.popBlob(b); os << "Blob " << b.size() << " bytes"; }
|
||||
else {
|
||||
assert(0); // I forgot a case..
|
||||
}
|
||||
if (arg.nbArgRemaining()) os << ", ";
|
||||
}
|
||||
if (!arg.isOk()) { os << " ERROR#" << arg.getErr(); }
|
||||
os << "]";
|
||||
return os;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
parse an OSC packet and extracts the embedded OSC messages.
|
||||
*/
|
||||
class PacketReader
|
||||
{
|
||||
public:
|
||||
PacketReader() { err = OK_NO_ERROR; }
|
||||
/** pointer and size of the osc packet to be parsed. */
|
||||
PacketReader(const void* ptr, size_t sz) { init(ptr, sz); }
|
||||
|
||||
void init(const void* ptr, const size_t sz)
|
||||
{
|
||||
err = OK_NO_ERROR;
|
||||
messages.clear();
|
||||
if ((sz % 4) == 0) { parse((const char*)ptr, (const char*)ptr + sz, TimeTag::immediate()); }
|
||||
else { OSCPKT_SET_ERR(INVALID_PACKET_SIZE); }
|
||||
it_messages = messages.begin();
|
||||
}
|
||||
|
||||
/** extract the next osc message from the packet. return 0 when all messages have been read, or in case of error. */
|
||||
Message* popMessage()
|
||||
{
|
||||
if (!err && !messages.empty() && it_messages != messages.end()) { return &*it_messages++; }
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool isOk() const { return err == OK_NO_ERROR; }
|
||||
ErrorCode getErr() const { return err; }
|
||||
|
||||
private:
|
||||
std::list<Message> messages;
|
||||
std::list<Message>::iterator it_messages;
|
||||
ErrorCode err;
|
||||
|
||||
void parse(const char* beg, const char* end, const TimeTag time_tag)
|
||||
{
|
||||
assert(beg <= end && !err);
|
||||
assert(((end-beg)%4)==0);
|
||||
|
||||
if (beg == end) { return; }
|
||||
if (*beg == '#')
|
||||
{
|
||||
/* it's a bundle */
|
||||
if (end - beg >= 20
|
||||
&& memcmp(beg, "#bundle\0", 8) == 0)
|
||||
{
|
||||
const TimeTag timeTag2(bytes2pod<uint64_t>(beg + 8));
|
||||
const char* pos = beg + 16;
|
||||
do
|
||||
{
|
||||
const uint32_t sz = bytes2pod<uint32_t>(pos);
|
||||
pos += 4;
|
||||
if ((sz & 3) != 0 || pos + sz > end || pos + sz < pos) { OSCPKT_SET_ERR(INVALID_BUNDLE); }
|
||||
else
|
||||
{
|
||||
parse(pos, pos + sz, timeTag2);
|
||||
pos += sz;
|
||||
}
|
||||
} while (!err && pos != end);
|
||||
}
|
||||
else { OSCPKT_SET_ERR(INVALID_BUNDLE); }
|
||||
}
|
||||
else
|
||||
{
|
||||
messages.push_back(Message(beg, end - beg, time_tag));
|
||||
if (!messages.back().isOk()) { OSCPKT_SET_ERR(messages.back().getErr()); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Assemble messages into an OSC packet. Example of use:
|
||||
@code
|
||||
PacketWriter pkt;
|
||||
Message msg;
|
||||
pkt.startBundle();
|
||||
pkt.addMessage(msg.init("/foo").pushBool(true).pushStr("plop").pushFloat(3.14f));
|
||||
pkt.addMessage(msg.init("/bar").pushBool(false));
|
||||
pkt.endBundle();
|
||||
if (pkt.isOk()) {
|
||||
send(pkt.data(), pkt.size());
|
||||
}
|
||||
@endcode
|
||||
*/
|
||||
class PacketWriter
|
||||
{
|
||||
public:
|
||||
PacketWriter() { init(); }
|
||||
|
||||
PacketWriter& init()
|
||||
{
|
||||
err = OK_NO_ERROR;
|
||||
storage.clear();
|
||||
bundles.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** begin a new bundle. If you plan to pack more than one message in the Osc packet, you have to
|
||||
put them in a bundle. Nested bundles inside bundles are also allowed. */
|
||||
PacketWriter& startBundle(const TimeTag ts = TimeTag::immediate())
|
||||
{
|
||||
char* p;
|
||||
if (!bundles.empty()) { p = storage.getBytes(4); } // hold the bundle size
|
||||
p = storage.getBytes(8);
|
||||
strcpy(p, "#bundle");
|
||||
bundles.push_back(p - storage.begin());
|
||||
p = storage.getBytes(8);
|
||||
pod2bytes<uint64_t>(ts, p);
|
||||
return *this;
|
||||
}
|
||||
/** close the current bundle. */
|
||||
PacketWriter& endBundle()
|
||||
{
|
||||
if (!bundles.empty())
|
||||
{
|
||||
if (storage.size() - bundles.back() == 16) { pod2bytes<uint32_t>(0, storage.getBytes(4)); } // the 'empty bundle' case, not very elegant
|
||||
if (bundles.size() > 1) // no size stored for the top-level bundle
|
||||
{
|
||||
pod2bytes<uint32_t>(uint32_t(storage.size() - bundles.back()), storage.begin() + bundles.back() - 4);
|
||||
}
|
||||
bundles.pop_back();
|
||||
}
|
||||
else { OSCPKT_SET_ERR(INVALID_BUNDLE); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** insert an Osc message into the current bundle / packet.
|
||||
*/
|
||||
PacketWriter& addMessage(const Message& msg)
|
||||
{
|
||||
if (storage.size() != 0 && bundles.empty()) { OSCPKT_SET_ERR(BUNDLE_REQUIRED_FOR_MULTI_MESSAGES); }
|
||||
else { msg.packMessage(storage, !bundles.empty()); }
|
||||
if (!msg.isOk()) { OSCPKT_SET_ERR(msg.getErr()); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** the error flag will be raised if an opened bundle is not closed, or if more than one message is
|
||||
inserted in the packet without a bundle */
|
||||
bool isOk() const { return err == OK_NO_ERROR; }
|
||||
ErrorCode getErr() const { return err; }
|
||||
|
||||
/** return the number of bytes of the osc packet -- will always be a
|
||||
multiple of 4 -- returns 0 if the construction of the packet has
|
||||
failed. */
|
||||
size_t packetSize() const { return err ? 0 : storage.size(); }
|
||||
|
||||
/** return the bytes of the osc packet (NULL if the construction of the packet has failed) */
|
||||
char* packetData() { return err ? nullptr : storage.begin(); }
|
||||
private:
|
||||
std::vector<size_t> bundles; // hold the position in the storage array of the beginning marker of each bundle
|
||||
Storage storage;
|
||||
ErrorCode err;
|
||||
};
|
||||
|
||||
// see the OSC spec for the precise pattern matching rules
|
||||
inline const char* internalPatternMatch(const char* pattern, const char* path)
|
||||
{
|
||||
while (*pattern)
|
||||
{
|
||||
const char* p = pattern;
|
||||
if (*p == '?' && *path)
|
||||
{
|
||||
++p;
|
||||
++path;
|
||||
}
|
||||
else if (*p == '[' && *path)
|
||||
{ // bracketted range, e.g. [a-zABC]
|
||||
++p;
|
||||
bool reverse = false;
|
||||
if (*p == '!')
|
||||
{
|
||||
reverse = true;
|
||||
++p;
|
||||
}
|
||||
bool match = reverse;
|
||||
for (; *p && *p != ']'; ++p)
|
||||
{
|
||||
const char c0 = *p;
|
||||
char c1 = c0;
|
||||
if (p[1] == '-' && p[2])
|
||||
{
|
||||
p += 2;
|
||||
c1 = *p;
|
||||
}
|
||||
if (*path >= c0 && *path <= c1) { match = !reverse; }
|
||||
}
|
||||
if (!match || *p != ']') { return pattern; }
|
||||
++p;
|
||||
++path;
|
||||
}
|
||||
else if (*p == '*')
|
||||
{ // wildcard '*'
|
||||
while (*p == '*') { ++p; }
|
||||
const char* best = nullptr;
|
||||
while (true)
|
||||
{
|
||||
const char* ret = internalPatternMatch(p, path);
|
||||
if (ret && ret > best) { best = ret; }
|
||||
if (*path == 0 || *path == '/') { break; }
|
||||
++path;
|
||||
}
|
||||
return best;
|
||||
}
|
||||
else if (*p == '/' && *(p + 1) == '/')
|
||||
{ // the super-wildcard '//'
|
||||
while (*(p + 1) == '/') { ++p; }
|
||||
const char* best = nullptr;
|
||||
while (true)
|
||||
{
|
||||
const char* ret = internalPatternMatch(p, path);
|
||||
if (ret && ret > best) { best = ret; }
|
||||
if (*path == 0) { break; }
|
||||
if (*path == 0 || (path = strchr(path + 1, '/')) == nullptr) { break; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
else if (*p == '{')
|
||||
{ // braced list {foo,bar,baz}
|
||||
const char *end = strchr(p, '}'), *q;
|
||||
if (!end) { return nullptr; } // syntax error in brace list..
|
||||
bool match = false;
|
||||
do
|
||||
{
|
||||
++p;
|
||||
q = strchr(p, ',');
|
||||
if (q == nullptr || q > end) { q = end; }
|
||||
if (strncmp(p, path, q - p) == 0)
|
||||
{
|
||||
path += (q - p);
|
||||
p = end + 1;
|
||||
match = true;
|
||||
}
|
||||
else { p = q; }
|
||||
} while (q != end && !match);
|
||||
if (!match) { return pattern; }
|
||||
}
|
||||
else if (*p == *path)
|
||||
{
|
||||
++p;
|
||||
++path;
|
||||
} // any other character
|
||||
else { break; }
|
||||
pattern = p;
|
||||
}
|
||||
return (*path == 0 ? pattern : nullptr);
|
||||
}
|
||||
|
||||
inline bool partialPatternMatch(const std::string& pattern, const std::string& path)
|
||||
{
|
||||
const char* q = internalPatternMatch(pattern.c_str(), path.c_str());
|
||||
return q != nullptr;
|
||||
}
|
||||
|
||||
inline bool fullPatternMatch(const std::string& pattern, const std::string& path)
|
||||
{
|
||||
const char* q = internalPatternMatch(pattern.c_str(), path.c_str());
|
||||
return q && *q == 0;
|
||||
}
|
||||
} // namespace oscpkt
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
This file provides a dumb c++ wrapper for sending OSC packets over UDP.
|
||||
*/
|
||||
|
||||
/* Copyright (C) 2010 Julien Pommier
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
(this is the zlib license)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER) || defined(WIN32)
|
||||
/*
|
||||
if windows.h has been already included, be prepared for tons of
|
||||
compile errors. winsock2 must be included BEFORE windows.h . -- OR
|
||||
define WIN32_LEAN_AND_MEAN before the first #include <windows.h> to
|
||||
prevent it from including tons of crap (winsock.h etc)
|
||||
*/
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
#if defined(_MSC_VER)
|
||||
# pragma comment(lib, "ws2_32.lib")
|
||||
#endif
|
||||
#else
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace oscpkt {
|
||||
|
||||
/** a wrapper class for holding an ip address, mostly used internnally */
|
||||
class SockAddr
|
||||
{
|
||||
union
|
||||
{
|
||||
sockaddr_storage ss; // hold an IPv4 or IPv6 address
|
||||
struct sockaddr sa;
|
||||
} addr_;
|
||||
|
||||
public:
|
||||
|
||||
struct sockaddr& addr() { return addr_.sa; }
|
||||
const struct sockaddr& addr() const { return addr_.sa; }
|
||||
size_t maxLen() const { return sizeof addr_; }
|
||||
|
||||
size_t actualLen() const
|
||||
{
|
||||
if (addr().sa_family == AF_UNSPEC) { return 0; }
|
||||
if (addr().sa_family == AF_INET) { return sizeof(struct sockaddr_in); }
|
||||
if (addr().sa_family == AF_INET6) { return sizeof(struct sockaddr_in6); }
|
||||
return sizeof addr_;
|
||||
}
|
||||
|
||||
SockAddr() { memset(&addr_, 0, sizeof addr_); }
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return addr().sa_family == AF_UNSPEC; /* this is the 0 value */
|
||||
}
|
||||
/** retrieve the current port number, -1 in case of error */
|
||||
int getPort() const
|
||||
{
|
||||
char servname[512];
|
||||
const int err = getnameinfo(&addr_.sa, sizeof addr_, nullptr, 0, servname, sizeof servname, NI_NUMERICSERV);
|
||||
return (err == 0 ? atoi(servname) : -1);
|
||||
}
|
||||
/* convert to a string representation (ip:port) */
|
||||
std::string asString() const
|
||||
{
|
||||
std::string s;
|
||||
if (addr().sa_family)
|
||||
{
|
||||
char hostname[512], servname[512];
|
||||
const int err = getnameinfo(&addr_.sa, sizeof addr_, hostname, sizeof hostname, servname, sizeof servname, NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
if (err == 0)
|
||||
{
|
||||
s = hostname;
|
||||
s += ":";
|
||||
s += servname;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* NOTE: This breaks build with OpenViBE
|
||||
friend std::ostream &operator<<(std::ostream &os, const SockAddr &ip) {
|
||||
os << "[";
|
||||
switch (ip.addr().sa_family) {
|
||||
case AF_UNSPEC: os << "AF_UNSPEC"; break;
|
||||
case AF_INET: os << "IPv4"; break;
|
||||
case AF_INET6: os << "IPv6"; break;
|
||||
default: os << "unknown family '" << ip.addr().sa_family << "'"; break;
|
||||
}
|
||||
os << " " << ip.asString() << "]";
|
||||
return os;
|
||||
}*/
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
just a wrapper over the classical socket stuff
|
||||
|
||||
should be robust, simple to use, IPv6 ready (avoids all deprecated
|
||||
stuff such as gethostbyname etc), and portable (mac/linux/windows)
|
||||
|
||||
Try to avoid sending packets larger than 8192 because some other
|
||||
implementation may truncate them (python's DatagramRequestHandler
|
||||
of OSC.py for example).
|
||||
*/
|
||||
struct UdpSocket
|
||||
{
|
||||
std::string error_message;
|
||||
int handle; /* the file descriptor for the socket */
|
||||
SockAddr local_addr /* initialised only for bound sockets */;
|
||||
SockAddr remote_addr; /* initialised for connected sockets. Also updated for bound sockets after each datagram received */
|
||||
|
||||
std::vector<char> buffer;
|
||||
|
||||
UdpSocket() : handle(-1)
|
||||
{
|
||||
#ifdef WIN32
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { setErr("winsock failed to initialise"); }
|
||||
#endif
|
||||
}
|
||||
|
||||
~UdpSocket()
|
||||
{
|
||||
close();
|
||||
#ifdef WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (handle != -1)
|
||||
{
|
||||
#ifdef WIN32
|
||||
closesocket(handle);
|
||||
#else
|
||||
::close(handle);
|
||||
#endif
|
||||
handle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool isOk() const { return error_message.empty(); }
|
||||
const std::string& errorMessage() const { return error_message; }
|
||||
|
||||
bool isBound() const { return !local_addr.empty(); }
|
||||
int boundPort() const { return local_addr.getPort(); }
|
||||
|
||||
std::string boundPortAsString() const
|
||||
{
|
||||
char s[512];
|
||||
#ifndef _MSC_VER
|
||||
snprintf(s, 512, "%d", boundPort());
|
||||
#else
|
||||
_snprintf_s(s, 512, 512, "%d", boundPort());
|
||||
#endif
|
||||
return s;
|
||||
}
|
||||
|
||||
int socketHandle() const { return handle; }
|
||||
|
||||
static std::string localHostName()
|
||||
{
|
||||
/* this stuff is not very nice but this is what liblo does in order to
|
||||
find out a sensible name for the local host */
|
||||
char hostname_buf[512];
|
||||
if (gethostname(hostname_buf, sizeof hostname_buf) != 0) { hostname_buf[0] = 0; }
|
||||
hostname_buf[sizeof hostname_buf - 1] = 0;
|
||||
struct hostent* host = gethostbyname(hostname_buf);
|
||||
if (host) { return host->h_name; }
|
||||
return hostname_buf[0] ? hostname_buf : "localhost";
|
||||
}
|
||||
|
||||
std::string localHostNameWithPort() const { return (localHostName() + ":") + boundPortAsString(); }
|
||||
|
||||
enum
|
||||
{
|
||||
OPTION_UNSPEC = 0,
|
||||
OPTION_FORCE_IPV4 = 1,
|
||||
OPTION_FORCE_IPV6 = 2,
|
||||
OPTION_DEFAULT = OPTION_FORCE_IPV4 // according to liblo's README, using ipv6 sockets causes issues with other non-ipv6 enabled osc software
|
||||
};
|
||||
|
||||
/** open the socket, and prepare for sending datagrams to the specified host:port */
|
||||
bool connectTo(const std::string& host, const std::string& port, const int options = OPTION_DEFAULT) { return openSocket(host, port, options); }
|
||||
|
||||
bool connectTo(const std::string& host, const int port, const int options = OPTION_DEFAULT) { return openSocket(host, port, options); }
|
||||
|
||||
void setErr(const std::string& msg) { if (error_message.empty()) { error_message = msg; } }
|
||||
|
||||
void* packetData() { return buffer.empty() ? nullptr : &buffer[0]; }
|
||||
size_t packetSize() const { return buffer.size(); }
|
||||
SockAddr& packetOrigin() { return remote_addr; }
|
||||
|
||||
bool sendPacket(const void* ptr, const size_t sz) { return sendPacketTo(ptr, sz, remote_addr); }
|
||||
|
||||
bool sendPacketTo(const void* ptr, const size_t sz, SockAddr& addr)
|
||||
{
|
||||
if (!isOk() || handle == -1)
|
||||
{
|
||||
setErr("not opened..");
|
||||
return false;
|
||||
}
|
||||
if (!ptr || sz == 0) { return false; }
|
||||
|
||||
int sent = 0;
|
||||
do
|
||||
{
|
||||
int res;
|
||||
errno = 0;
|
||||
if (isBound()) { res = sendto(handle, (const char*)ptr, int(sz), 0, &addr.addr(), int(addr.actualLen())); }
|
||||
else
|
||||
{
|
||||
res = send(handle, (const char*)ptr, int(sz), 0);
|
||||
// res = write(handle, ptr, sz);
|
||||
}
|
||||
#ifdef WIN32
|
||||
if (res == -1 && WSAGetLastError() == WSAEINTR) { continue; }
|
||||
sent = res;
|
||||
#else
|
||||
//if (res == -1) std::cerr << "sendto handle=" << handle << ", res:" << res << ", sz=" << sz << ", errno=" << errno << " " << strerror(errno) << "\n";
|
||||
if (res == -1 && errno == EINTR) continue;
|
||||
else sent = res;
|
||||
#endif
|
||||
} while (false);
|
||||
|
||||
return size_t(sent) == sz;
|
||||
}
|
||||
|
||||
private:
|
||||
bool openSocket(const std::string& hostname, const int port, const int options)
|
||||
{
|
||||
char port_string[64];
|
||||
#ifdef _MSC_VER
|
||||
_snprintf_s(port_string, 64, 64, "%d", port);
|
||||
#else
|
||||
snprintf(port_string, 64, "%d", port);
|
||||
#endif
|
||||
return openSocket(hostname, port_string, options);
|
||||
}
|
||||
|
||||
bool openSocket(const std::string& hostname, const std::string& port, const int options)
|
||||
{
|
||||
const bool binding = hostname.empty();
|
||||
close();
|
||||
error_message.clear();
|
||||
|
||||
struct addrinfo hints;
|
||||
struct addrinfo* result = nullptr;
|
||||
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
if (options == OPTION_FORCE_IPV4) { hints.ai_family = AF_INET; }
|
||||
else if (options == OPTION_FORCE_IPV6) { hints.ai_family = AF_INET6; }
|
||||
else { hints.ai_family = AF_UNSPEC; } // Allow IPv4 or IPv6 -- in case of problem, try with AF_INET ...
|
||||
hints.ai_socktype = SOCK_DGRAM; // Datagram socket
|
||||
hints.ai_flags = (binding ? AI_PASSIVE : 0); // AI_PASSIVE means socket address is intended for bind
|
||||
|
||||
const int err = getaddrinfo(binding ? nullptr : hostname.c_str(), port.empty() ? nullptr : port.c_str(), &hints, &result);
|
||||
if (err != 0)
|
||||
{
|
||||
setErr(gai_strerror(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct addrinfo* rp = result;
|
||||
for (; rp && handle == -1; rp = rp->ai_next)
|
||||
{
|
||||
handle = socket(rp->ai_family, rp->ai_socktype,
|
||||
rp->ai_protocol);
|
||||
if (handle == -1) { continue; }
|
||||
|
||||
if (binding)
|
||||
{
|
||||
if (bind(handle, rp->ai_addr, socklen_t(rp->ai_addrlen)) != 0) { close(); }
|
||||
else
|
||||
{
|
||||
socklen_t len = socklen_t(local_addr.maxLen());
|
||||
if (getsockname(handle, &local_addr.addr(), &len) == 0)
|
||||
{
|
||||
/* great */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (connect(handle, rp->ai_addr, socklen_t(rp->ai_addrlen)) != 0) { close(); }
|
||||
else
|
||||
{
|
||||
assert(size_t(rp->ai_addrlen) <= sizeof remote_addr);
|
||||
memcpy(&remote_addr.addr(), rp->ai_addr, rp->ai_addrlen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
freeaddrinfo(result);
|
||||
result = nullptr;
|
||||
|
||||
if (!rp)
|
||||
{ // we failed miserably
|
||||
setErr(binding ? "bind failed" : "connect failed");
|
||||
assert(handle == -1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace oscpkt
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
#include "ovpCBoxAlgorithmOSCController.h"
|
||||
|
||||
// #include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
|
||||
bool CBoxAlgorithmOSCController::initialize()
|
||||
{
|
||||
const CString address = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
const uint64_t port = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
m_oscAddress = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
|
||||
|
||||
const char* tmp = m_oscAddress.toASCIIString();
|
||||
if (!tmp || !tmp[0] || tmp[0] != '/')
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "OSC Address must start with a '/'\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connect the socket
|
||||
const std::string str = std::string(address.toASCIIString());
|
||||
m_udpSocket.connectTo(str, uint32_t(port));
|
||||
|
||||
if (!m_udpSocket.isOk())
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Error connecting to socket\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get appropriate decoder
|
||||
CIdentifier streamType;
|
||||
this->getStaticBoxContext().getInputType(0, streamType);
|
||||
|
||||
m_decoder = nullptr;
|
||||
if (this->getTypeManager().isDerivedFromStream(streamType,OV_TypeId_StreamedMatrix))
|
||||
{
|
||||
m_decoder = &this->getAlgorithmManager().getAlgorithm(
|
||||
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StreamedMatrixDecoder));
|
||||
}
|
||||
else if (streamType == OV_TypeId_Stimulations)
|
||||
{
|
||||
m_decoder = &this->getAlgorithmManager().getAlgorithm(
|
||||
this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Unsupported type\n";
|
||||
return false;
|
||||
}
|
||||
m_decoder->initialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmOSCController::uninitialize()
|
||||
{
|
||||
if (m_udpSocket.isOk()) { m_udpSocket.close(); }
|
||||
|
||||
if (m_decoder)
|
||||
{
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
|
||||
m_decoder = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmOSCController::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmOSCController::process()
|
||||
{
|
||||
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
|
||||
CIdentifier streamType;
|
||||
this->getStaticBoxContext().getInputType(0, streamType);
|
||||
|
||||
oscpkt::PacketWriter pw;
|
||||
oscpkt::Message msg;
|
||||
bool haveData = false;
|
||||
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(0); ++j)
|
||||
{
|
||||
if (this->getTypeManager().isDerivedFromStream(streamType,OV_TypeId_StreamedMatrix))
|
||||
{
|
||||
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
|
||||
m_decoder->getInputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_InputParameterId_MemoryBufferToDecode));
|
||||
Kernel::TParameterHandler<const CMatrix*> op_pMatrix(
|
||||
m_decoder->getOutputParameter(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputParameterId_Matrix));
|
||||
|
||||
ip_buffer = boxContext.getInputChunk(0, j);
|
||||
m_decoder->process();
|
||||
|
||||
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StreamedMatrixDecoder_OutputTriggerId_ReceivedBuffer))
|
||||
{
|
||||
// Check that the dimensions are acceptable
|
||||
const CMatrix* matrix = op_pMatrix;
|
||||
if (matrix->getDimensionCount() < 1 || matrix->getDimensionCount() > 2)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Only matrixes of 1 or 2 dimensions are supported\n";
|
||||
return false;
|
||||
}
|
||||
if (matrix->getDimensionCount() == 2 && matrix->getDimensionSize(0) != 1)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "The matrix should have only 1 channel. Use e.g. Channel Selector to prune\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!haveData)
|
||||
{
|
||||
haveData = true;
|
||||
pw.startBundle();
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < matrix->getBufferElementCount(); ++k)
|
||||
{
|
||||
const float inputVal = float(matrix->getBuffer()[k]);
|
||||
pw.addMessage(msg.init(m_oscAddress.toASCIIString()).pushFloat(inputVal));
|
||||
// std::cout << "Add float " << inputVal << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (streamType == OV_TypeId_Stimulations)
|
||||
{
|
||||
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
|
||||
m_decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
|
||||
const Kernel::TParameterHandler<const IStimulationSet*> op_pStimulationSet(
|
||||
m_decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
|
||||
|
||||
ip_buffer = boxContext.getInputChunk(0, j);
|
||||
m_decoder->process();
|
||||
|
||||
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
|
||||
{
|
||||
if (!haveData)
|
||||
{
|
||||
haveData = true;
|
||||
pw.startBundle();
|
||||
}
|
||||
|
||||
for (size_t k = 0; k < op_pStimulationSet->getStimulationCount(); ++k)
|
||||
{
|
||||
const uint64_t stimulus = op_pStimulationSet->getStimulationIdentifier(k);
|
||||
pw.addMessage(msg.init(m_oscAddress.toASCIIString()).pushInt32(int32_t(stimulus)));
|
||||
// std::cout << "Add stimulus " << stimulus << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Unknown stream type\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
boxContext.markInputAsDeprecated(0, j);
|
||||
}
|
||||
|
||||
if (haveData)
|
||||
{
|
||||
pw.endBundle();
|
||||
if (!m_udpSocket.sendPacket(pw.packetData(), pw.packetSize())) { this->getLogManager() << Kernel::LogLevel_Warning << "Error sending out UDP packet\n"; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../ovp_defines.h"
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "oscpkt.h"
|
||||
#include "oscpkt_udp.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
/**
|
||||
* \class CBoxAlgorithmOSCController
|
||||
* \author Ozan Caglayan (Galatasaray University)
|
||||
* \date Thu May 8 20:57:24 2014
|
||||
* \brief The class CBoxAlgorithmOSCController describes the box OSC Controller.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmOSCController final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
|
||||
//Here is the different process callbacks possible
|
||||
// - On new input received (the most common behaviour for signal processing) :
|
||||
bool processInput(const size_t index) override;
|
||||
|
||||
bool process() override;
|
||||
|
||||
// As we do with any class in openvibe, we use the macro below
|
||||
// to associate this box to an unique identifier.
|
||||
// The inheritance information is also made available,
|
||||
// as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_OSCController)
|
||||
|
||||
private:
|
||||
|
||||
// Decodes the stream
|
||||
Kernel::IAlgorithmProxy* m_decoder = nullptr;
|
||||
|
||||
// UDP Socket (oscpkt_udp.h)
|
||||
oscpkt::UdpSocket m_udpSocket;
|
||||
|
||||
// OSC Address to some device
|
||||
CString m_oscAddress;
|
||||
};
|
||||
|
||||
/**
|
||||
* \class CBoxAlgorithmOSCControllerDesc
|
||||
* \author Ozan Caglayan (Galatasaray University)
|
||||
* \date Thu May 8 20:57:24 2014
|
||||
* \brief Descriptor of the box OSC Controller.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmOSCControllerDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { }
|
||||
|
||||
CString getName() const override { return CString("OSC Controller"); }
|
||||
CString getAuthorName() const override { return CString("Ozan Caglayan"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Galatasaray University"); }
|
||||
// + Stimulation support & some code refactoring in v1.1 by Jussi T. Lindgren / Inria
|
||||
CString getShortDescription() const override { return CString("Sends OSC messages to an OSC controller"); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString(
|
||||
"This box allows OpenViBE to send OSC (Open Sound Control) messages to an OSC server. See http://www.opensoundcontrol.org to learn about the OSC protocol and its use cases.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString("Acquisition and network IO"); }
|
||||
CString getVersion() const override { return CString("1.1"); }
|
||||
CString getStockItemName() const override { return CString("gtk-network"); }
|
||||
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_OSCController; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmOSCController; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Input",OV_TypeId_Signal);
|
||||
|
||||
prototype.addInputSupport(OV_TypeId_Signal);
|
||||
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
|
||||
prototype.addInputSupport(OV_TypeId_Stimulations);
|
||||
|
||||
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
|
||||
|
||||
prototype.addSetting("OSC Server IP",OV_TypeId_String, "127.0.0.1");
|
||||
prototype.addSetting("OSC Server Port",OV_TypeId_Integer, "9001");
|
||||
prototype.addSetting("OSC Address",OV_TypeId_String, "/a/b/c");
|
||||
|
||||
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_OSCControllerDesc)
|
||||
};
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#if defined TARGET_HAS_ThirdPartyLSL
|
||||
|
||||
#include "ovpCBoxLSLExportGipsa.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
|
||||
|
||||
bool CBoxAlgorithmLSLExportGipsa::initialize()
|
||||
{
|
||||
m_inputChannel1.initialize(this);
|
||||
|
||||
m_streamName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
m_streamType = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
|
||||
m_outlet = nullptr;
|
||||
m_stims.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmLSLExportGipsa::uninitialize()
|
||||
{
|
||||
m_inputChannel1.uninitialize();
|
||||
m_stims.clear();
|
||||
delete m_outlet;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmLSLExportGipsa::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmLSLExportGipsa::process()
|
||||
{
|
||||
if (!m_inputChannel1.isWorking())
|
||||
{
|
||||
m_inputChannel1.waitForSignalHeader();
|
||||
|
||||
if (m_inputChannel1.isWorking())
|
||||
{
|
||||
try
|
||||
{
|
||||
//if it fails here then most likely you are using the wrong dll - e.x debug instead of release or vice-versa
|
||||
lsl::stream_info info(m_streamName.toASCIIString(), m_streamType.toASCIIString(), int(m_inputChannel1.getNChannels()) + 1,
|
||||
double(m_inputChannel1.getSamplingRate()), lsl::cf_float32);
|
||||
|
||||
lsl::xml_element channels = info.desc().append_child("channels");
|
||||
|
||||
for (size_t i = 0; i < m_inputChannel1.getNChannels(); ++i)
|
||||
{
|
||||
channels.append_child("channel")
|
||||
.append_child_value("label", m_inputChannel1.getChannelName(i))
|
||||
.append_child_value("type", "EEG")
|
||||
.append_child_value("unit", "microvolts");
|
||||
}
|
||||
|
||||
channels.append_child("channel")
|
||||
.append_child_value("label", "Stimulations")
|
||||
.append_child_value("type", "marker");
|
||||
|
||||
if (m_outlet != nullptr) { this->getLogManager() << Kernel::LogLevel_Error << "Possible double initialization!\n"; }
|
||||
|
||||
m_outlet = new lsl::stream_outlet(info); //here the length of the buffered signal can be specified
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Could not initialize LSL library: " << e.what() << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//stimulations
|
||||
for (size_t i = 0; i < m_inputChannel1.getNStimulationBuffers(); ++i)
|
||||
{
|
||||
uint64_t tStart, tEnd;
|
||||
IStimulationSet* set = m_inputChannel1.getStimulation(tStart, tEnd, i);
|
||||
|
||||
for (size_t j = 0; j < set->getStimulationCount(); ++j)
|
||||
{
|
||||
uint64_t time = m_inputChannel1.getStartTimestamp() + set->getStimulationDate(j);
|
||||
const uint64_t identifier = set->getStimulationIdentifier(j);
|
||||
|
||||
|
||||
if (m_stims.empty())
|
||||
{
|
||||
m_stims.push_back(std::pair<float, uint64_t>(float(identifier), time));
|
||||
//std::cout<< "added: " << m_stims[m_stims.size()-1].first << " " << m_stims[m_stims.size()-1].second<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto last = m_stims[m_stims.size() - 1];
|
||||
if (last.first != identifier && last.second != time)
|
||||
{
|
||||
m_stims.push_back(std::pair<float, uint64_t>(float(identifier), time));
|
||||
//std::cout<< "added: " << m_stims[m_stims.size()-1].first << " " << m_stims[m_stims.size()-1].second<< "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
//std::cout<< "duplicate: " << m_stims[m_stims.size()-1].first << " " << m_stims[m_stims.size()-1].second<< "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//signal
|
||||
for (size_t i = 0; i < m_inputChannel1.getNSignalBuffers(); ++i)
|
||||
{
|
||||
uint64_t tStart, tEnd;
|
||||
double* inputBuffer = m_inputChannel1.getSignal(tStart, tEnd, i);
|
||||
|
||||
if (inputBuffer)
|
||||
{
|
||||
const size_t samplesPerChannelInput = m_inputChannel1.getNSamples();
|
||||
std::vector<std::vector<float>> mychunk(samplesPerChannelInput);
|
||||
|
||||
for (size_t k = 0; k < samplesPerChannelInput; ++k) { mychunk[k] = std::vector<float>(m_inputChannel1.getNChannels() + 1); }
|
||||
|
||||
//Fill a matrix - OpenVibe provides the data ch1 (all values from all samples), ch2(all values from all samples) ... chN,
|
||||
//In the generated chunk every row is a single sample (containing the data from all channels) and every column number is the number of the channel
|
||||
for (size_t k = 0; k < m_inputChannel1.getNChannels(); ++k)
|
||||
{
|
||||
for (size_t j = 0; j < samplesPerChannelInput; ++j)
|
||||
{
|
||||
const size_t index = (k * samplesPerChannelInput) + j;
|
||||
mychunk[j][k] = float(inputBuffer[index]); // @note 64bit->32bit conversion
|
||||
}
|
||||
}
|
||||
|
||||
//Process stimulations and add them to the output in a dedicated channel
|
||||
std::vector<float> stimChan = std::vector<float>(samplesPerChannelInput);
|
||||
|
||||
auto it = m_stims.begin();
|
||||
while (it != m_stims.end())
|
||||
{
|
||||
auto current = *it;
|
||||
|
||||
if (!(current.second >= tStart && current.second <= tEnd))
|
||||
{
|
||||
// not in current time range, do not send now.
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
const uint64_t posCurrent = CTime(current.second).toSampleCount(m_inputChannel1.getSamplingRate());
|
||||
const uint64_t posStart = CTime(tStart).toSampleCount(m_inputChannel1.getSamplingRate());
|
||||
//uint64_t posEnd = CTime(tStart).toSampleCount(m_inputChannel1.getSamplingRate());
|
||||
|
||||
int pos = int(posCurrent) - int(posStart);
|
||||
if (pos < 0) { pos = 0; } //fix position
|
||||
if (pos == int(stimChan.size())) { pos = int(stimChan.size() - 1); } //fix position
|
||||
|
||||
if (pos >= 0 && pos < int(stimChan.size()))
|
||||
{
|
||||
stimChan[pos] = float(current.first);
|
||||
//std::cout<< "pos relative: " << pos << " value: " << stim_chan[pos] << " time:" << CTime(current.second).toSeconds()<< "\n";
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Warning << "Bad stimulation position: " << pos << "stim code: " << current.first << "\n"; }
|
||||
|
||||
// processed, erase
|
||||
it = m_stims.erase(it);
|
||||
}
|
||||
|
||||
//add the stim channel at the end of the matrix
|
||||
const size_t k = m_inputChannel1.getNChannels();
|
||||
for (size_t j = 0; j < samplesPerChannelInput; ++j) { mychunk[j][k] = stimChan[j]; }
|
||||
|
||||
//send all channels
|
||||
m_outlet->push_chunk(mychunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyLSL
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <vector>
|
||||
|
||||
#include "../ovpCInputChannel.h"
|
||||
#include <lsl_cpp.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
class CBoxAlgorithmLSLExportGipsa final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
|
||||
CBoxAlgorithmLSLExportGipsa() : m_inputChannel1(0) {}
|
||||
|
||||
void release() override { delete this; }
|
||||
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool processInput(const size_t index) override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsa)
|
||||
|
||||
protected:
|
||||
|
||||
int64_t m_decimationFactor = 0;
|
||||
uint64_t m_outputSampling = 0;
|
||||
CString m_streamName;
|
||||
CString m_streamType;
|
||||
|
||||
SignalProcessing::CInputChannel m_inputChannel1;
|
||||
|
||||
lsl::stream_outlet* m_outlet = nullptr;
|
||||
std::vector<std::pair<float, uint64_t>> m_stims;//identifier,time
|
||||
};
|
||||
|
||||
class CBoxAlgorithmLSLExportGipsaDesc final : public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { }
|
||||
|
||||
CString getName() const override { return CString("LSL Export (Gipsa)"); }
|
||||
CString getAuthorName() const override { return CString("Anton Andreev"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Gipsa-lab"); }
|
||||
|
||||
CString getShortDescription() const override { return CString("Streams signal outside OpenVibe using Lab Streaming Layer library"); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString("More on how to read the signal in your application: https://code.google.com/p/labstreaminglayer/");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString("Acquisition and network IO"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getStockItemName() const override { return CString("gtk-connect"); }
|
||||
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsa; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmLSLExportGipsa; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Input signal", OV_TypeId_Signal);
|
||||
prototype.addInput("Input stimulations", OV_TypeId_Stimulations);
|
||||
prototype.addSetting("Stream name", OV_TypeId_String, "OpenViBE Stream");
|
||||
prototype.addSetting("Stream type", OV_TypeId_String, "EEG");
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsaDesc)
|
||||
};
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
#include "ovpCInputChannel.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace SignalProcessing {
|
||||
|
||||
bool CInputChannel::initialize(Toolkit::TBoxAlgorithm<IBoxAlgorithm>* boxAlgorithm)
|
||||
{
|
||||
m_isWorking = false;
|
||||
|
||||
m_startTimestamp = 0;
|
||||
m_endTimestamp = 0;
|
||||
|
||||
m_stimulationSet = nullptr;
|
||||
m_boxAlgorithm = boxAlgorithm;
|
||||
|
||||
m_signalDecoder = new Toolkit::TSignalDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>();
|
||||
m_signalDecoder->initialize(*m_boxAlgorithm, 0);
|
||||
|
||||
m_stimDecoder = new Toolkit::TStimulationDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>();
|
||||
m_stimDecoder->initialize(*m_boxAlgorithm, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInputChannel::uninitialize() const
|
||||
{
|
||||
m_stimDecoder->uninitialize();
|
||||
delete m_stimDecoder;
|
||||
|
||||
m_signalDecoder->uninitialize();
|
||||
delete m_signalDecoder;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInputChannel::waitForSignalHeader()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
|
||||
|
||||
if (boxContext.getInputChunkCount(m_signalChannel))
|
||||
{
|
||||
m_signalDecoder->decode(0);
|
||||
|
||||
if (m_signalDecoder->isHeaderReceived())
|
||||
{
|
||||
m_isWorking = true;
|
||||
|
||||
m_startTimestamp = boxContext.getInputChunkStartTime(m_signalChannel, 0);
|
||||
m_endTimestamp = boxContext.getInputChunkEndTime(m_signalChannel, 0);
|
||||
|
||||
boxContext.markInputAsDeprecated(m_signalChannel, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
IStimulationSet* CInputChannel::getStimulation(uint64_t& startTime, uint64_t& endTime, const size_t index)
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
|
||||
|
||||
m_stimDecoder->decode(index);
|
||||
m_stimulationSet = m_stimDecoder->getOutputStimulationSet();
|
||||
|
||||
startTime = boxContext.getInputChunkStartTime(m_stimulationChannel, index);
|
||||
endTime = boxContext.getInputChunkEndTime(m_stimulationChannel, index);
|
||||
|
||||
boxContext.markInputAsDeprecated(m_stimulationChannel, index);
|
||||
|
||||
return m_stimulationSet;
|
||||
}
|
||||
|
||||
IStimulationSet* CInputChannel::discardStimulation(const size_t index)
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
|
||||
|
||||
m_stimDecoder->decode(index);
|
||||
m_stimulationSet = m_stimDecoder->getOutputStimulationSet();
|
||||
|
||||
boxContext.markInputAsDeprecated(m_stimulationChannel, index);
|
||||
|
||||
return m_stimulationSet;
|
||||
}
|
||||
|
||||
|
||||
double* CInputChannel::getSignal(uint64_t& startTime, uint64_t& endTime, const size_t index) const
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
|
||||
m_signalDecoder->decode(index);
|
||||
if (!m_signalDecoder->isBufferReceived()) { return nullptr; }
|
||||
|
||||
startTime = boxContext.getInputChunkStartTime(m_signalChannel, index);
|
||||
endTime = boxContext.getInputChunkEndTime(m_signalChannel, index);
|
||||
|
||||
boxContext.markInputAsDeprecated(m_signalChannel, index);
|
||||
|
||||
return m_signalDecoder->getOutputMatrix()->getBuffer();
|
||||
}
|
||||
|
||||
|
||||
double* CInputChannel::discardSignal(const size_t index) const
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = m_boxAlgorithm->getDynamicBoxContext();
|
||||
m_signalDecoder->decode(index);
|
||||
if (!m_signalDecoder->isBufferReceived()) { return nullptr; }
|
||||
|
||||
boxContext.markInputAsDeprecated(m_signalChannel, index);
|
||||
|
||||
return m_signalDecoder->getOutputMatrix()->getBuffer();
|
||||
}
|
||||
|
||||
#if 0
|
||||
void CInputChannel::copyData(const bool copyFirstBlock, size_t index)
|
||||
{
|
||||
CMatrix*& matrixBuffer = m_oMatrixBuffer[index & 1];
|
||||
|
||||
double* srcData = m_signalDecoder->getOutputMatrix()->getBuffer() + (copyFirstBlock ? 0 : m_firstBlock);
|
||||
double* dstData = matrixBuffer->getBuffer() + (copyFirstBlock ? m_secondBlock : 0);
|
||||
size_t size = (copyFirstBlock ? m_firstBlock : m_secondBlock)*sizeof(double);
|
||||
|
||||
for (size_t i=0; i < m_nChannels; i++, srcData += m_nSamples, dstData += m_nSamples) { System::Memory::copy(dstData, srcData, size); }
|
||||
}
|
||||
#endif
|
||||
} // namespace SignalProcessing
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
// @author Gipsa-lab
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
/**
|
||||
Use this class to receive send and stimulations channels
|
||||
*/
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace SignalProcessing {
|
||||
class CInputChannel
|
||||
{
|
||||
typedef enum
|
||||
{
|
||||
SIGNAL_CHANNEL,
|
||||
STIMULATION_CHANNEL,
|
||||
NB_CHANNELS,
|
||||
} channel_t;
|
||||
|
||||
public:
|
||||
|
||||
explicit CInputChannel(const uint16_t index = 0)
|
||||
: m_signalChannel(index * NB_CHANNELS + SIGNAL_CHANNEL), m_stimulationChannel(index * NB_CHANNELS + STIMULATION_CHANNEL) {}
|
||||
|
||||
~CInputChannel() { }
|
||||
|
||||
bool initialize(Toolkit::TBoxAlgorithm<IBoxAlgorithm>* boxAlgorithm);
|
||||
bool uninitialize() const;
|
||||
|
||||
bool isLastChannel(const size_t index) const { return index == m_stimulationChannel; }
|
||||
bool isWorking() const { return m_isWorking; }
|
||||
bool waitForSignalHeader();
|
||||
size_t getNStimulationBuffers() const { return m_boxAlgorithm->getDynamicBoxContext().getInputChunkCount(m_stimulationChannel); }
|
||||
size_t getNSignalBuffers() const { return m_boxAlgorithm->getDynamicBoxContext().getInputChunkCount(m_signalChannel); }
|
||||
IStimulationSet* getStimulation(uint64_t& startTime, uint64_t& endTime, size_t index);
|
||||
IStimulationSet* discardStimulation(size_t index);
|
||||
double* getSignal(uint64_t& startTime, uint64_t& endTime, size_t index) const;
|
||||
double* discardSignal(size_t index) const;
|
||||
uint64_t getSamplingRate() const { return m_signalDecoder->getOutputSamplingRate(); }
|
||||
size_t getNChannels() const { return m_signalDecoder->getOutputMatrix()->getDimensionSize(0); }
|
||||
size_t getNSamples() const { return m_signalDecoder->getOutputMatrix()->getDimensionSize(1); }
|
||||
uint64_t getStartTimestamp() const { return m_startTimestamp; }
|
||||
uint64_t getEndTimestamp() const { return m_endTimestamp; }
|
||||
const char* getChannelName(const size_t index) const { return m_signalDecoder->getOutputMatrix()->getDimensionLabel(0, index); }
|
||||
const Kernel::TParameterHandler<CMatrix*>& getOpMatrix() const { return m_signalDecoder->getOutputMatrix(); }
|
||||
|
||||
protected:
|
||||
size_t m_signalChannel = 0;
|
||||
size_t m_stimulationChannel = 0;
|
||||
bool m_isWorking = false;
|
||||
|
||||
uint64_t m_startTimestamp = 0;
|
||||
uint64_t m_endTimestamp = 0;
|
||||
|
||||
IStimulationSet* m_stimulationSet = nullptr;
|
||||
|
||||
// parent memory
|
||||
Toolkit::TBoxAlgorithm<IBoxAlgorithm>* m_boxAlgorithm = nullptr;
|
||||
|
||||
// signal section
|
||||
//Kernel::IAlgorithmProxy* m_signalDecoder;
|
||||
Toolkit::TSignalDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>* m_signalDecoder = nullptr;
|
||||
|
||||
// stimulation section
|
||||
Toolkit::TStimulationDecoder<Toolkit::TBoxAlgorithm<IBoxAlgorithm>>* m_stimDecoder = nullptr;
|
||||
};
|
||||
} // namespace SignalProcessing
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
// Boxes
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#define OVP_ClassId_BoxAlgorithm_OSCController OpenViBE::CIdentifier(0xC66F2F0C, 0x3BA5B424)
|
||||
#define OVP_ClassId_BoxAlgorithm_OSCControllerDesc OpenViBE::CIdentifier(0xF7A35BD7, 0x6331C7D9)
|
||||
#define OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsa OpenViBE::CIdentifier(0x591D2E94, 0x221C23AD)
|
||||
#define OVP_ClassId_BoxAlgorithm_CBoxAlgorithmLSLExportGipsaDesc OpenViBE::CIdentifier(0x22AF11F5, 0x58F2787D)
|
||||
|
||||
// Global defines
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
#include "ovp_global_defines.h"
|
||||
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
|
||||
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#include <vector>
|
||||
#include <openvibe/ov_all.h>
|
||||
#include "ovp_defines.h"
|
||||
|
||||
#include "box-algorithms/osc-controller/ovpCBoxAlgorithmOSCController.h"
|
||||
|
||||
// @BEGIN gipsa
|
||||
|
||||
#include "box-algorithms/ovpCBoxLSLExportGipsa.h"
|
||||
|
||||
// @END gipsa
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
|
||||
OVP_Declare_Begin()
|
||||
OVP_Declare_New(CBoxAlgorithmOSCControllerDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
|
||||
OV_AttributeId_Box_FlagIsUnstable.id());
|
||||
// @BEGIN gipsa
|
||||
#if defined TARGET_HAS_ThirdPartyLSL
|
||||
OVP_Declare_New(CBoxAlgorithmLSLExportGipsaDesc);
|
||||
#endif // TARGET_HAS_ThirdPartyLSL
|
||||
// @END gipsa
|
||||
|
||||
OVP_Declare_End()
|
||||
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
Reference in New Issue
Block a user