This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -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})
@@ -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|
*/
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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