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,77 @@
#include "CBoxAlgorithmStimulationValidator.hpp"
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
///-------------------------------------------------------------------------------------------------
bool CBoxAlgorithmStimulationValidator::initialize()
{
m_decoder.initialize(*this, 0);
m_iStimulation = m_decoder.getOutputStimulationSet();
m_encoder.initialize(*this, 0);
m_oStimulation = m_encoder.getInputStimulationSet();
m_stim = uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0));
m_limit = size_t(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBoxAlgorithmStimulationValidator::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBoxAlgorithmStimulationValidator::processInput(const size_t index)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBoxAlgorithmStimulationValidator::process()
{
Kernel::IBoxIO& boxCtx = this->getDynamicBoxContext();
const uint64_t currentTime = getPlayerContext().getCurrentTime();
m_oStimulation->setStimulationCount(0); // reset stimulation output
//***** Stimulations *****
for (size_t i = 0; i < boxCtx.getInputChunkCount(0); ++i)
{
m_decoder.decode(i); // Decode the chunk
if (m_decoder.isBufferReceived()) // Buffer Received
{
for (size_t j = 0; j < m_iStimulation->getStimulationCount(); ++j)
{
if (m_iStimulation->getStimulationIdentifier(j) == m_stim.id())
{
m_count++;
if (m_count >= m_limit)
{
m_count = 0;
m_oStimulation->appendStimulation(m_stim.id(), m_iStimulation->getStimulationDate(j), 0);
}
}
}
}
}
if (m_lastEndTime == 0) { m_encoder.encodeHeader(); } // First time
else { m_encoder.encodeBuffer(); } // Others
boxCtx.markOutputAsReadyToSend(0, m_lastEndTime, currentTime); // preserve continuous
m_lastEndTime = currentTime; // preserve continuous
return true;
}
///-------------------------------------------------------------------------------------------------
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,99 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CBoxAlgorithmStimulationValidator.hpp
/// \brief Classes of the Box Stimulation Validator.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date Mon Feb 17 10:42:46 2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE
{
namespace Plugins
{
namespace Stimulation
{
/// <summary> The class CBoxAlgorithmStimulationValidator describes the box Stimulation Validator. </summary>
class CBoxAlgorithmStimulationValidator 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 clock ticks :
//bool processClock(Kernel::CMessageClock& msg) override;
// - On new input received (the most common behaviour for signal processing) :
bool processInput(const size_t index) override;
// If you want to use processClock, you must provide the clock frequency.
//uint64_t getClockFrequency() 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_StimulationValidator)
protected:
//***** Codecs *****
Toolkit::TStimulationDecoder<CBoxAlgorithmStimulationValidator> m_decoder; // Stimulation Decoder
Toolkit::TStimulationEncoder<CBoxAlgorithmStimulationValidator> m_encoder; // Stimulation Encoder
IStimulationSet *m_iStimulation = nullptr, *m_oStimulation = nullptr; // Stimulation Receiver
//***** Settings *****
CIdentifier m_stim = OVTK_StimulationId_Label_00;
size_t m_count = 0, m_limit = 0;
uint64_t m_lastEndTime = 0;
};
/// <summary> Descriptor of the box Stimulation Validator. </summary>
class CBoxAlgorithmStimulationValidatorDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Stimulation Validator"); }
CString getAuthorName() const override { return CString("Thibaut Monseigne"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Count a stimulation and send this after N occurence."); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-add"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_StimulationValidator; }
IPluginObject* create() override { return new CBoxAlgorithmStimulationValidator; }
/*
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmStimulationValidatorListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
*/
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input",OV_TypeId_Stimulations);
prototype.addOutput("Output",OV_TypeId_Stimulations);
prototype.addSetting("Stimulation to count",OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("Number for validation",OV_TypeId_Integer, "2");
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_StimulationValidatorDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,133 @@
#include "ovpCBoxAlgorithmStimulationFilter.h"
#include <cstddef>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
bool CBoxAlgorithmStimulationFilter::initialize()
{
m_defaultAction = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_startTime = uint64_t(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)) * (1LL << 32));
m_endTime = uint64_t(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)) * (1LL << 32));
if (m_startTime > m_endTime)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "End time is lower than start time\n";
return false;
}
// we iterate over all Rules. One rule has 3 settings
const size_t nSetting = this->getStaticBoxContext().getSettingCount();
for (size_t i = 3; i < nSetting; i += 3)
{
rule_t rule;
rule.action = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i); // the action to perform
rule.startStimID = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + 1); // first stim
rule.EndStimID = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + 2); // last stim in the desired range
m_rules.push_back(rule);
}
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationEncoder));
m_encoder->initialize();
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
m_decoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
op_stimSet.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
ip_stimSet.initialize(m_encoder->getInputParameter(OVP_GD_Algorithm_StimulationEncoder_InputParameterId_StimulationSet));
op_buffer.initialize(m_encoder->getOutputParameter(OVP_GD_Algorithm_StimulationEncoder_OutputParameterId_EncodedMemoryBuffer));
return true;
}
bool CBoxAlgorithmStimulationFilter::uninitialize()
{
op_buffer.uninitialize();
ip_stimSet.uninitialize();
op_stimSet.uninitialize();
ip_buffer.uninitialize();
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_rules.clear();
return true;
}
bool CBoxAlgorithmStimulationFilter::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmStimulationFilter::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
op_buffer = boxContext.getOutputChunk(0);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader))
{
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeHeader);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
{
ip_stimSet->setStimulationCount(0);
for (size_t s = 0; s < op_stimSet->getStimulationCount(); ++s)
{
const uint64_t id = op_stimSet->getStimulationIdentifier(s);
const uint64_t date = op_stimSet->getStimulationDate(s);
uint64_t action = m_defaultAction;
if ((m_startTime == m_endTime) || (m_startTime != m_endTime && m_startTime <= date && date <= m_endTime))
{
for (size_t r = 0; r < m_rules.size(); ++r)
{
const rule_t& rule = m_rules[r];
if (rule.startStimID <= id && id <= rule.EndStimID)
{
action = rule.action;
this->getLogManager() << Kernel::LogLevel_Debug << "Switches to rule " << r << "\n";
}
}
}
if (action == OVP_TypeId_StimulationFilterAction_Select.id())
{
this->getLogManager() << Kernel::LogLevel_Trace << "Selects stimulation "
<< this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, id) << " !\n";
ip_stimSet->appendStimulation(id, date, op_stimSet->getStimulationDuration(s));
}
if (action == OVP_TypeId_StimulationFilterAction_Reject.id())
{
this->getLogManager() << Kernel::LogLevel_Trace << "Rejects stimulation "
<< this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, id) << " !\n";
}
}
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeBuffer);
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd))
{
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeEnd);
}
boxContext.markInputAsDeprecated(0, i);
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
return true;
}
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,116 @@
#pragma once
#include "../../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <vector>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CBoxAlgorithmStimulationFilter final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
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_StimulationFilter)
protected:
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<IStimulationSet*> op_stimSet;
Kernel::TParameterHandler<IStimulationSet*> ip_stimSet;
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer;
typedef struct
{
uint64_t action;
uint64_t startStimID;
uint64_t EndStimID;
} rule_t;
uint64_t m_defaultAction = 0;
uint64_t m_startTime = 0;
uint64_t m_endTime = 0;
std::vector<rule_t> m_rules;
};
class CBoxAlgorithmStimulationFilterListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onSettingAdded(Kernel::IBox& box, const size_t index) override
{
box.removeSetting(index);
//we had a whole rule (3 settings)
box.addSetting("Action to perform", OVP_TypeId_StimulationFilterAction, "Select");
box.addSetting("Stimulation range begin", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
box.addSetting("Stimulation range end", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_0F");
return true;
}
bool onSettingRemoved(Kernel::IBox& box, const size_t index) override
{
//we must remove the 2 other settings corresponding to the rule
const size_t settingGroupIdx = (index - 3) / 3;
box.removeSetting(settingGroupIdx * 3 + 3);
box.removeSetting(settingGroupIdx * 3 + 3);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmStimulationFilterDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Stimulation Filter"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Filters incoming stimulations selecting or rejecting specific ranges of stimulations"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Stimulation/Adaptation"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-missing-image"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_StimulationFilter; }
IPluginObject* create() override { return new CBoxAlgorithmStimulationFilter; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
prototype.addOutput("Modified Stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Default action", OVP_TypeId_StimulationFilterAction, "Reject");
prototype.addSetting("Time range begin", OV_TypeId_Float, "0");
prototype.addSetting("Time range end", OV_TypeId_Float, "0");
prototype.addSetting("Action to perform", OVP_TypeId_StimulationFilterAction, "Select");
prototype.addSetting("Stimulation range begin", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("Stimulation range end", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_0F");
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
// prototype.addFlag (OV_AttributeId_Box_FlagIsUnstable);
return true;
}
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmStimulationFilterListener; }
virtual void releaseBoxListener(IBoxListener* listener) { delete listener; }
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_StimulationFilterDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,813 @@
#include "ovpCBoxAlgorithmLuaStimulator.h"
#if defined TARGET_HAS_ThirdPartyLua
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
#define __CB_Assert__(condition) \
if(!(condition)) \
{ \
lua_pushstring(state, "Assertion failed : ["#condition"]"); \
lua_error(state); \
return 0; \
}
namespace {
class CLuaThread
{
public:
CLuaThread(CBoxAlgorithmLuaStimulator* pBox) : m_box(pBox) { }
CLuaThread(const CLuaThread& thread) : m_box(thread.m_box) { }
void operator()() const { m_box->doThread(); }
protected:
CBoxAlgorithmLuaStimulator* m_box;
};
} // namespace
static void lua_setcallback(lua_State* state, const char* name, int (*callback)(lua_State*), void* data)
{
lua_getglobal(state, "__openvibe_box_context");
lua_pushlightuserdata(state, data);
lua_pushcclosure(state, callback, 1);
lua_setfield(state, -2, name);
}
// Returns true on success, i.e. if code is 0 (no failure).
static bool lua_report(Kernel::ILogManager& logManager, lua_State* state, const int code)
{
if (code)
{
logManager << Kernel::LogLevel_ImportantWarning << "Lua error: " << lua_tostring(state, -1) << "\n";
lua_pop(state, 1);
}
return (code == 0);
}
static bool lua_check_argument_count(lua_State* state, const char* name, const int count1, const int count2 = -1)
{
if (count2 < 0)
{
if (lua_gettop(state) != count1 + 1)
{
const std::string msg = "[" + std::string(name) + "] takes " + std::to_string(count1) + " parameter" + (count1 >= 2 ? "s" : "");
lua_pushstring(state, msg.c_str());
lua_error(state);
return false;
}
}
else
{
if (lua_gettop(state) != count1 + 1 && lua_gettop(state) != count2 + 1)
{
const std::string msg = "[" + std::string(name) + "] takes either " + std::to_string(count1) + " or " + std::to_string(count2) + " parameter"
+ (count2 >= 2 ? "s" : "");
lua_pushstring(state, msg.c_str());
lua_error(state);
return false;
}
}
return true;
}
static bool lua_check_box_status(lua_State* state, const char* name, const CBoxAlgorithmLuaStimulator::EStates curState)
{
if (curState == CBoxAlgorithmLuaStimulator::EStates::Please_Quit)
{
const std::string msg = "[" + std::string(name) + "] This thread has been requested to quit...";
lua_pushstring(state, msg.c_str());
lua_error(state);
return false;
}
if (curState != CBoxAlgorithmLuaStimulator::EStates::Processing)
{
const std::string msg = "[" + std::string(name) + "] should only be called in the [process] callback";
lua_pushstring(state, msg.c_str());
lua_error(state);
return false;
}
return true;
}
static int lua_get_input_count_cb(lua_State* state)
{
size_t nInput = 0;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "get_input_count", 0)) { return 0; }
__CB_Assert__(pThis->getInputCountCB(nInput));
lua_pushinteger(state, nInput);
return 1;
}
static int lua_get_output_count_cb(lua_State* state)
{
size_t nOutput = 0;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "get_output_count", 0)) { return 0; }
__CB_Assert__(pThis->getOutputCountCB(nOutput));
lua_pushinteger(state, nOutput);
return 1;
}
static int lua_get_setting_count_cb(lua_State* state)
{
size_t nSetting = 0;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "get_setting_count", 0)) { return 0; }
__CB_Assert__(pThis->getSettingCountCB(nSetting));
lua_pushinteger(state, nSetting);
return 1;
}
static int lua_get_setting_cb(lua_State* state)
{
CString setting;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "get_setting", 1)) { return 0; }
__CB_Assert__(pThis->getSettingCB(size_t(lua_tointeger(state, 2)-1), setting));
lua_pushstring(state, setting.toASCIIString());
return 1;
}
static int lua_get_config_cb(lua_State* state)
{
CString config;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "get_config", 1)) { return 0; }
__CB_Assert__(pThis->getConfigCB(lua_tostring(state, 2), config));
lua_pushstring(state, config.toASCIIString());
return 1;
}
static int lua_get_current_time_cb(lua_State* state)
{
uint64_t time = 0;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "get_current_time", 0)) { return 0; }
__CB_Assert__(pThis->getCurrentTimeCB(time));
lua_pushnumber(state, CTime(time).toSeconds());
return 1;
}
static int lua_sleep_cb(lua_State* state)
{
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_box_status(state, "sleep", pThis->m_State)) { return 0; }
if (!lua_check_argument_count(state, "sleep", 0)) { return 0; }
__CB_Assert__(pThis->sleepCB());
return 0;
}
static int lua_get_stimulation_count_cb(lua_State* state)
{
size_t count = 0;
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_box_status(state, "get_stimulation_count", pThis->m_State)) { return 0; }
if (!lua_check_argument_count(state, "get_stimulation_count", 1)) { return 0; }
__CB_Assert__(pThis->getStimulationCountCB(size_t(lua_tointeger(state, 2)-1), count));
lua_pushinteger(state, count);
return 1;
}
static int lua_get_stimulation_cb(lua_State* state)
{
uint64_t id = uint64_t(-1);
uint64_t time = uint64_t(-1);
uint64_t duration = uint64_t(-1);
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_box_status(state, "get_stimulation", pThis->m_State)) { return 0; }
if (!lua_check_argument_count(state, "get_stimulation", 2)) { return 0; }
__CB_Assert__(pThis->getStimulationCB(size_t(lua_tointeger(state, 2)-1), size_t(lua_tointeger(state, 3)-1), id, time, duration));
lua_pushinteger(state, lua_Integer(id));
lua_pushnumber(state, CTime(time).toSeconds());
lua_pushnumber(state, CTime(duration).toSeconds());
return 3;
}
static int lua_remove_stimulation_cb(lua_State* state)
{
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_box_status(state, "remove_stimulation", pThis->m_State)) { return 0; }
if (!lua_check_argument_count(state, "remove_stimulation", 2)) { return 0; }
__CB_Assert__(pThis->removeStimulationCB(size_t(lua_tointeger(state, 2)-1), size_t(lua_tointeger(state, 3)-1)));
return 0;
}
static int lua_send_stimulation_cb(lua_State* state)
{
const int iArguments = lua_gettop(state);
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_box_status(state, "send_stimulation", pThis->m_State)) { return 0; }
if (!lua_check_argument_count(state, "send_stimulation", 3, 4)) { return 0; }
__CB_Assert__(pThis->sendStimulationCB(size_t(lua_tointeger(state, 2)-1), size_t(lua_tointeger(state, 3)),
CTime(double(lua_tonumber(state, 4))).time(),
(iArguments == 5 ? CTime(double(lua_tonumber(state, 5))).time() : 0)));
return 0;
}
// Has the side effect of setting m_bLuaThreadHadError to true if called with "Error" or "Fatal" loglevels.
static int lua_log_cb(lua_State* state)
{
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "log", 2)) { return 0; }
Kernel::ELogLevel level = Kernel::LogLevel_Debug;
const std::string str(lua_tostring(state, 2));
if (str == "Trace") { level = Kernel::LogLevel_Trace; }
else if (str == "Info") { level = Kernel::LogLevel_Info; }
else if (str == "Warning") { level = Kernel::LogLevel_Warning; }
else if (str == "ImportantWarning") { level = Kernel::LogLevel_ImportantWarning; }
else if (str == "Error")
{
level = Kernel::LogLevel_Error;
pThis->m_LuaThreadHadError = true;
}
else if (str == "Fatal")
{
level = Kernel::LogLevel_Fatal;
pThis->m_LuaThreadHadError = true;
}
__CB_Assert__(pThis->log(level, lua_tostring(state, 3)));
return 0;
}
static int lua_keep_processing_cb(lua_State* state)
{
const auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (pThis->m_State == CBoxAlgorithmLuaStimulator::EStates::Processing) { return 1; }
return 0;
}
static int lua_set_filter_mode_cb(lua_State* state)
{
auto pThis = static_cast<CBoxAlgorithmLuaStimulator*>(lua_touserdata(state, lua_upvalueindex(1)));
__CB_Assert__(pThis != nullptr);
if (!lua_check_argument_count(state, "set_filter_mode", 1)) { return 0; }
pThis->m_FilterMode = (lua_tointeger(state, 2) == 0 ? false : true);
return 0;
}
#if BOOST_VERSION >= 103500
CBoxAlgorithmLuaStimulator::CBoxAlgorithmLuaStimulator()
: m_innerLock(m_mutex, boost::defer_lock), m_outerLock(m_mutex, boost::defer_lock), m_exitLock(m_mutex, boost::defer_lock) { }
#else
CBoxAlgorithmLuaStimulator::CBoxAlgorithmLuaStimulator()
: m_innerLock(m_mutex, false), m_outerLock(m_mutex, false), m_exitLock(m_mutex, false) { }
#endif
uint64_t CBoxAlgorithmLuaStimulator::getClockFrequency()
{
if (m_FilterMode) { return 0; } // Only when input received
return 128LL << 32; // the box clock frequency
}
bool CBoxAlgorithmLuaStimulator::processClock(Kernel::CMessageClock& /*msg*/)
{
if (!m_FilterMode) { this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess(); }
return true;
}
bool CBoxAlgorithmLuaStimulator::processInput(const size_t /*index*/)
{
if (m_FilterMode) { getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess(); }
return true;
}
bool CBoxAlgorithmLuaStimulator::initialize()
{
size_t i;
const Kernel::IBox& boxContext = this->getStaticBoxContext();
CString filename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
filename = this->getConfigurationManager().expand(filename);
m_FilterMode = false;
m_State = EStates::Unstarted;
m_luaState = luaL_newstate();
luaL_openlibs(m_luaState);
lua_newtable(m_luaState);
lua_setglobal(m_luaState, "__openvibe_box_context");
lua_setcallback(m_luaState, "get_input_count", lua_get_input_count_cb, this);
lua_setcallback(m_luaState, "get_output_count", lua_get_output_count_cb, this);
lua_setcallback(m_luaState, "get_setting_count", lua_get_setting_count_cb, this);
lua_setcallback(m_luaState, "get_setting", lua_get_setting_cb, this);
lua_setcallback(m_luaState, "get_config", lua_get_config_cb, this);
lua_setcallback(m_luaState, "get_current_time", lua_get_current_time_cb, this);
lua_setcallback(m_luaState, "sleep", lua_sleep_cb, this);
lua_setcallback(m_luaState, "get_stimulation_count", lua_get_stimulation_count_cb, this);
lua_setcallback(m_luaState, "get_stimulation", lua_get_stimulation_cb, this);
lua_setcallback(m_luaState, "remove_stimulation", lua_remove_stimulation_cb, this);
lua_setcallback(m_luaState, "send_stimulation", lua_send_stimulation_cb, this);
lua_setcallback(m_luaState, "log", lua_log_cb, this);
lua_setcallback(m_luaState, "keep_processing", lua_keep_processing_cb, this);
lua_setcallback(m_luaState, "set_filter_mode", lua_set_filter_mode_cb, this);
if (!lua_report(this->getLogManager(), m_luaState, luaL_dostring(m_luaState, "function initialize(box) end"))) { return false; }
if (!lua_report(this->getLogManager(), m_luaState, luaL_dostring(m_luaState, "function uninitialize(box) end"))) { return false; }
if (!lua_report(this->getLogManager(), m_luaState, luaL_dostring(m_luaState, "function process(box) end"))) { return false; }
if (!lua_report(this->getLogManager(), m_luaState, luaL_dofile(m_luaState, filename.toASCIIString()))) { return false; }
if (!lua_report(this->getLogManager(), m_luaState, luaL_dostring(m_luaState, "initialize(__openvibe_box_context)"))) { return false; }
m_iStimulations.resize(boxContext.getInputCount());
m_oStimulations.resize(boxContext.getOutputCount());
for (i = 0; i < boxContext.getInputCount(); ++i)
{
Toolkit::TStimulationDecoder<CBoxAlgorithmLuaStimulator>* decoder = new Toolkit::TStimulationDecoder<CBoxAlgorithmLuaStimulator>;
decoder->initialize(*this, i);
m_decoders.push_back(decoder);
}
for (i = 0; i < boxContext.getOutputCount(); ++i)
{
Toolkit::TStimulationEncoder<CBoxAlgorithmLuaStimulator>* encoder = new Toolkit::TStimulationEncoder<CBoxAlgorithmLuaStimulator>;
encoder->initialize(*this, i);
m_encoders.push_back(encoder);
}
m_lastTime = 0;
m_luaThread = nullptr;
m_LuaThreadHadError = false;
return true;
}
bool CBoxAlgorithmLuaStimulator::uninitialize()
{
if (m_luaThread)
{
m_outerLock.lock();
// If Lua thread is still running, ask it to stop.
if (m_State == EStates::Processing || m_State == EStates::Please_Quit)
{
this->getLogManager() << Kernel::LogLevel_Debug << "Requesting thread to quit, waiting max 5 secs ...\n";
m_State = EStates::Please_Quit;
m_condition.notify_one();
m_outerLock.unlock();
m_exitLock.lock();
bool gotLock = false;
for (int i = 0; i < 5; ++i)
{
// Wait for the thread to stop (in that case it notifies the lock)
boost::system_time timeout = boost::get_system_time() + boost::posix_time::milliseconds(1000);
if (m_exitCondition.timed_wait(m_exitLock, timeout))
{
gotLock = true;
break;
}
this->getLogManager() << Kernel::LogLevel_Info << "Waiting for thread to exit, " << i + 1 << "/5 ...\n";
}
if (gotLock) { this->getLogManager() << Kernel::LogLevel_Debug << "Ok, thread notified the exit lock\n"; }
else { this->getLogManager() << Kernel::LogLevel_Debug << "Bad, thread did not notify the exit lock in 5s\n"; }
m_exitLock.unlock();
m_outerLock.lock();
}
if (m_State == EStates::Finished)
{
this->getLogManager() << Kernel::LogLevel_Debug << "Ok, thread reached Finished as expected ...\n";
m_luaThread->join();
delete m_luaThread;
m_luaThread = nullptr;
}
else { this->getLogManager() << Kernel::LogLevel_Warning << "Bad, thread still in state " << toString(m_State) << ", cannot delete. Memory leak.\n"; }
m_outerLock.unlock();
}
if (m_luaState)
{
if (!lua_report(this->getLogManager(), m_luaState, luaL_dostring(m_luaState, "uninitialize(__openvibe_box_context)")))
{
this->getLogManager() << Kernel::LogLevel_Warning << "Lua script uninitialize failed\n";
// Don't return here on false, still want to free the resources below
}
lua_close(m_luaState);
m_luaState = nullptr;
}
/* TODO: Remove next comment*/
/*IBox& staticBoxContext=*/
this->getStaticBoxContext();
for (size_t i = 0; i < m_decoders.size(); ++i)
{
m_decoders[i]->uninitialize();
delete m_decoders[i];
}
m_decoders.clear();
for (size_t i = 0; i < m_encoders.size(); ++i)
{
m_encoders[i]->uninitialize();
delete m_encoders[i];
}
m_encoders.clear();
return true;
}
bool CBoxAlgorithmLuaStimulator::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
const size_t nInput = this->getStaticBoxContext().getInputCount();
const uint64_t time = this->getPlayerContext().getCurrentTime();
// Default time range for the generated stimulation chunk
uint64_t start = (m_FilterMode ? std::numeric_limits<uint64_t>::max() : m_lastTime);
uint64_t end = (m_FilterMode ? 0 : time);
if (time == 0)
{
// Send all headers
for (size_t i = 0; i < m_encoders.size(); ++i)
{
m_encoders[i]->encodeHeader();
boxContext.markOutputAsReadyToSend(size_t(i), 0, 0);
}
}
for (size_t i = 0; i < nInput; ++i)
{
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
{
m_decoders[i]->decode(j);
if (m_decoders[i]->isHeaderReceived()) { }
if (m_decoders[i]->isBufferReceived())
{
const IStimulationSet* stimSet = m_decoders[i]->getOutputStimulationSet();
for (size_t k = 0; k < stimSet->getStimulationCount(); ++k)
{
m_iStimulations[i].insert(std::make_pair(stimSet->getStimulationDate(k),
std::make_pair(stimSet->getStimulationIdentifier(k),
stimSet->getStimulationDuration(k))));
}
if (m_FilterMode)
{
// In this mode, the output chunk time range contains all the current input chunk time ranges
start = std::min<uint64_t>(start, boxContext.getInputChunkStartTime(i, j));
end = std::max<uint64_t>(end, boxContext.getInputChunkEndTime(i, j));
if (start < m_lastTime)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Earliest current input chunk start time "
<< CTime(start) << " is older than the last sent block end time "
<< CTime(m_lastTime) << "\n";
}
}
}
if (m_decoders[i]->isEndReceived()) { }
boxContext.markInputAsDeprecated(i, j);
}
}
runLuaThread();
// Send unless endtime=0 (then either we're at header time, or no chunks were received in filter mode)
if (end > 0) { sendStimulations(start, end); }
if (!m_luaThread && m_LuaThreadHadError)
{
// Lua thread has exit, so it was ok to check m_bLuaThreadHadError without a lock
return false;
}
return true;
}
bool CBoxAlgorithmLuaStimulator::runLuaThread()
{
m_outerLock.lock();
// Executes one step of the thread
switch (m_State)
{
case EStates::Unstarted:
m_State = EStates::Processing;
m_luaThread = new boost::thread(CLuaThread(this));
m_condition.wait(m_outerLock);
break;
case EStates::Processing:
m_condition.notify_one();
m_condition.wait(m_outerLock);
break;
case EStates::Please_Quit:
// Shouldn't happen as only uninitialize() sets this
break;
case EStates::Finished:
break;
default:
break;
}
// Executes one step of the box once the thread has finished
if (m_luaThread)
{
switch (m_State)
{
case EStates::Unstarted:
break; // This should never happen
case EStates::Processing:
break;
case EStates::Please_Quit:
// Shouldn't happen as only uninitialize() sets this
break;
case EStates::Finished:
if (m_luaThread)
{
m_luaThread->join();
delete m_luaThread;
m_luaThread = nullptr;
this->getLogManager() << Kernel::LogLevel_Info << "Lua script terminated\n";
}
break;
default:
break;
}
}
m_outerLock.unlock();
return true;
}
bool CBoxAlgorithmLuaStimulator::sendStimulations(const uint64_t startTime, const uint64_t endTime)
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
const size_t nOutput = this->getStaticBoxContext().getOutputCount();
for (size_t i = 0; i < nOutput; ++i)
{
IStimulationSet* stimSet = m_encoders[i]->getInputStimulationSet();
stimSet->clear();
auto it = m_oStimulations[i].begin();
while (it != m_oStimulations[i].end() && it->first < endTime)
{
const auto itStim = it;
++it;
stimSet->appendStimulation(itStim->second.first, itStim->first, itStim->second.second);
this->getLogManager() << Kernel::LogLevel_Debug << "On output " << i << " - should send stimulation " << itStim->second.first << " at date "
<< CTime(itStim->first) << " with duration " << itStim->second.second << "\n";
m_oStimulations[i].erase(itStim);
}
m_encoders[i]->encodeBuffer();
boxContext.markOutputAsReadyToSend(i, startTime, endTime);
}
m_lastTime = endTime;
return true;
}
bool CBoxAlgorithmLuaStimulator::waitForStimulation(const size_t inputIdx, const size_t stimIdx)
{
if (inputIdx >= m_iStimulations.size())
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Input " << (inputIdx + 1) << " does not exist\n";
return false;
}
while (m_iStimulations[inputIdx].size() <= stimIdx)
{
switch (m_State)
{
case EStates::Unstarted: return false; // this should never happen
case EStates::Finished: return false;
case EStates::Please_Quit: return false;
case EStates::Processing: this->sleepCB();
break;
default: break;
}
}
return true;
}
bool CBoxAlgorithmLuaStimulator::getInputCountCB(size_t& count)
{
count = this->getStaticBoxContext().getInputCount();
return true;
}
bool CBoxAlgorithmLuaStimulator::getOutputCountCB(size_t& count)
{
count = this->getStaticBoxContext().getOutputCount();
return true;
}
bool CBoxAlgorithmLuaStimulator::getSettingCountCB(size_t& count)
{
count = this->getStaticBoxContext().getSettingCount();
return true;
}
bool CBoxAlgorithmLuaStimulator::getSettingCB(const size_t index, CString& setting)
{
if (index >= this->getStaticBoxContext().getSettingCount())
{
this->getLogManager() << Kernel::LogLevel_Warning << "Setting " << (index + 1) << " does not exist\n";
setting = CString("");
return true;
}
setting = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), index);
return true;
}
bool CBoxAlgorithmLuaStimulator::getConfigCB(const CString& str, CString& config)
{
config = this->getConfigurationManager().expand(str);
return true;
}
bool CBoxAlgorithmLuaStimulator::getCurrentTimeCB(uint64_t& time)
{
time = this->getPlayerContext().getCurrentTime();
return true;
}
bool CBoxAlgorithmLuaStimulator::sleepCB()
{
m_condition.notify_one();
m_condition.wait(m_innerLock);
return true;
}
bool CBoxAlgorithmLuaStimulator::getStimulationCountCB(const size_t index, size_t& count)
{
if (index >= m_iStimulations.size())
{
this->getLogManager() << Kernel::LogLevel_Warning << "Input " << (index + 1) << " does not exist\n";
count = 0;
return true;
}
count = size_t(m_iStimulations[index].size());
return true;
}
bool CBoxAlgorithmLuaStimulator::getStimulationCB(const size_t inputIdx, const size_t stimIdx, uint64_t& id, uint64_t& time, uint64_t& duration)
{
if (!this->waitForStimulation(inputIdx, stimIdx))
{
id = uint64_t(-1);
time = uint64_t(-1);
duration = uint64_t(-1);
return true;
}
auto it = m_iStimulations[inputIdx].begin();
for (size_t i = 0; i < stimIdx; i++, ++it) {}
id = it->second.first;
time = it->first;
duration = it->second.second;
this->getLogManager() << Kernel::LogLevel_Debug << "getStimulationCB " << (inputIdx + 1) << " " << (stimIdx + 1) << " " << id << " " << time << " "
<< duration << "\n";
return true;
}
bool CBoxAlgorithmLuaStimulator::removeStimulationCB(const size_t inputIdx, const size_t stimIdx)
{
if (!this->waitForStimulation(inputIdx, stimIdx)) { return true; }
auto it = m_iStimulations[inputIdx].begin();
for (size_t i = 0; i < stimIdx; i++, ++it) {}
m_iStimulations[inputIdx].erase(it);
this->getLogManager() << Kernel::LogLevel_Debug << "removeStimulationCB " << (inputIdx + 1) << " " << (stimIdx + 1) << "\n";
return true;
}
bool CBoxAlgorithmLuaStimulator::sendStimulationCB(const size_t outputIdx, uint64_t id, uint64_t time, uint64_t duration)
{
if (outputIdx < m_oStimulations.size())
{
if (time >= m_lastTime)
{
this->getLogManager() << Kernel::LogLevel_Debug << "sendStimulationCB " << (outputIdx + 1) << " " << id << " " << time << " " << duration << "\n";
m_oStimulations[outputIdx].insert(std::make_pair(time, std::make_pair(id, duration)));
}
else
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Ignored outdated stimulation " << id << " " << CTime(time) << " " << CTime(duration)
<< " sent on output " << (outputIdx + 1) << " - older than last chunk end time " << CTime(m_lastTime) << "\n";
}
}
else
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Ignored stimulation " << id << " " << CTime(time) << " " << CTime(duration)
<< " sent on unexistant output " << (outputIdx + 1) << "\n";
}
return true;
}
bool CBoxAlgorithmLuaStimulator::log(const Kernel::ELogLevel level, const CString& text)
{
this->getLogManager() << level << "<" << Kernel::LogColor_PushStateBit << Kernel::LogColor_ForegroundGreen
<< "In Script" << Kernel::LogColor_PopStateBit << "> " << text.toASCIIString() << "\n";
return true;
}
void CBoxAlgorithmLuaStimulator::doThread()
{
m_innerLock.lock();
if (!lua_report(this->getLogManager(), m_luaState, luaL_dostring(m_luaState, "process(__openvibe_box_context)"))) { m_LuaThreadHadError = true; }
m_State = EStates::Finished;
m_innerLock.unlock();
m_condition.notify_one();
m_exitCondition.notify_one();
}
std::string CBoxAlgorithmLuaStimulator::toString(const EStates state)
{
switch (state)
{
case EStates::Unstarted: return "Unstarted";
case EStates::Processing: return "Processing";
case EStates::Please_Quit: return "Please_Quit";
case EStates::Finished: return "Finished";
default: return "Invalid";
}
}
#endif // TARGET_HAS_ThirdPartyLua
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,168 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyLua
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <boost/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/noncopyable.hpp>
#include <map>
#include <vector>
#include <cstdio>
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CBoxAlgorithmLuaStimulator final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>, public boost::noncopyable
{
public:
CBoxAlgorithmLuaStimulator();
~CBoxAlgorithmLuaStimulator() override = default;
void release() override { delete this; }
uint64_t getClockFrequency() override;
bool initialize() override;
bool uninitialize() override;
bool processClock(Kernel::CMessageClock& msg) override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_LuaStimulator)
bool waitForStimulation(size_t inputIdx, size_t stimIdx);
bool getInputCountCB(size_t& count);
bool getOutputCountCB(size_t& count);
bool getSettingCountCB(size_t& count);
bool getSettingCB(size_t index, CString& setting);
bool getConfigCB(const CString& str, CString& config);
bool getCurrentTimeCB(uint64_t& time);
bool sleepCB();
bool getStimulationCountCB(size_t index, size_t& count);
bool getStimulationCB(size_t inputIdx, size_t stimIdx, uint64_t& id, uint64_t& time, uint64_t& duration);
bool removeStimulationCB(size_t inputIdx, size_t stimIdx);
bool sendStimulationCB(size_t outputIdx, uint64_t id, uint64_t time, uint64_t duration);
bool log(Kernel::ELogLevel level, const CString& text);
void doThread();
enum class EStates
{
Unstarted,
Processing,
Please_Quit,
Finished,
};
static std::string toString(const EStates state);
EStates m_State = EStates::Unstarted;
bool m_LuaThreadHadError = false; // Set to true by the lua thread if there were issues
bool m_FilterMode = false; // Output chunk generation driven by inputs (true) or by clock (false)
protected:
lua_State* m_luaState = nullptr;
uint64_t m_lastTime = 0;
std::vector<std::multimap<uint64_t, std::pair<uint64_t, uint64_t>>> m_oStimulations;
std::vector<std::multimap<uint64_t, std::pair<uint64_t, uint64_t>>> m_iStimulations;
boost::thread* m_luaThread = nullptr;
boost::mutex m_mutex;
boost::mutex::scoped_lock m_innerLock;
boost::mutex::scoped_lock m_outerLock;
boost::mutex::scoped_lock m_exitLock;
boost::condition m_condition;
boost::condition m_exitCondition;
std::vector<Toolkit::TStimulationDecoder<CBoxAlgorithmLuaStimulator>*> m_decoders;
std::vector<Toolkit::TStimulationEncoder<CBoxAlgorithmLuaStimulator>*> m_encoders;
private:
CBoxAlgorithmLuaStimulator(const CBoxAlgorithmLuaStimulator&);
bool runLuaThread();
bool sendStimulations(uint64_t startTime, uint64_t endTime);
};
class CBoxAlgorithmLuaStimulatorListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputAdded(Kernel::IBox& box, const size_t index) override
{
box.setInputType(index, OV_TypeId_Stimulations);
return true;
}
bool onOutputAdded(Kernel::IBox& box, const size_t index) override
{
box.setOutputType(index, OV_TypeId_Stimulations);
return true;
}
bool onSettingAdded(Kernel::IBox& box, const size_t index) override
{
box.setSettingType(index, OV_TypeId_String);
box.setSettingName(index, ("Setting " + std::to_string(index)).c_str());
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmLuaStimulatorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Lua Stimulator"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Generates some stimulations according to an Lua script"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting"); }
CString getVersion() const override { return CString("1.1"); }
CString getStockItemName() const override { return CString("gtk-missing-image"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_LuaStimulator; }
IPluginObject* create() override { return new CBoxAlgorithmLuaStimulator; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmLuaStimulatorListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addOutput("Stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Lua Script", OV_TypeId_Script, "");
prototype.addFlag(Kernel::BoxFlag_CanAddOutput);
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
prototype.addFlag(Kernel::BoxFlag_CanModifySetting);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_LuaStimulatorDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyLua
@@ -0,0 +1,331 @@
#if defined TARGET_HAS_ThirdPartyOpenAL
#include "ovpCBoxAlgorithmOpenALSoundPlayer.h"
#include <tcptagging/IStimulusSender.h>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
#define BUFFER_SIZE 32768
#define UNIQUE_SOURCE 1
bool CBoxAlgorithmOpenALSoundPlayer::initialize()
{
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_playTrigger = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_stopTrigger = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_filename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
m_loop = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
m_lastOutputChunkDate = -1;
m_startOfSoundSent = false;
m_endOfSoundSent = false;
if (alutInit(nullptr, nullptr) != AL_TRUE)
{
if (alutGetError() == ALUT_ERROR_INVALID_OPERATION) { this->getLogManager() << Kernel::LogLevel_Trace << "ALUT already initialized.\n"; }
else
{
this->getLogManager() << Kernel::LogLevel_Error << "ALUT initialization returned a bad status.\n";
this->getLogManager() << Kernel::LogLevel_Error << "ALUT ERROR:\n" << alutGetErrorString(alutGetError()) << "\n";
return false;
}
}
m_fileFormat = FILE_FORMAT_UNSUPPORTED;
const std::string file(m_filename.toASCIIString());
if (file.find(".wav") != std::string::npos) { m_fileFormat = FILE_FORMAT_WAV; }
if (file.find(".ogg") != std::string::npos) { m_fileFormat = FILE_FORMAT_OGG; }
m_stimulusSender = TCPTagging::CreateStimulusSender();
if (!m_stimulusSender->connect("localhost", "15361"))
{
this->getLogManager() << Kernel::LogLevel_Warning << "Unable to connect to AS's TCP Tagging plugin, stimuli wont be forwarded.\n";
}
return openSoundFile();
}
bool CBoxAlgorithmOpenALSoundPlayer::uninitialize()
{
m_decoder.uninitialize();
m_encoder.uninitialize();
const bool status = stopSound(false);
#if UNIQUE_SOURCE
alDeleteSources(1, &m_sourceHandle);
#endif
alDeleteBuffers(1, &m_soundBufferHandle);
if (alutExit() != AL_TRUE)
{
if (alutGetError() == ALUT_ERROR_INVALID_OPERATION) { this->getLogManager() << Kernel::LogLevel_Trace << "ALUT already exited.\n"; }
else
{
this->getLogManager() << Kernel::LogLevel_Error << "ALUT uninitialization returned a bad status.\n";
this->getLogManager() << Kernel::LogLevel_Error << "ALUT ERROR:\n" << alutGetErrorString(alutGetError()) << "\n";
return false;
}
}
if (m_stimulusSender)
{
delete m_stimulusSender;
m_stimulusSender = nullptr;
}
return status;
}
bool CBoxAlgorithmOpenALSoundPlayer::processClock(Kernel::CMessageClock& /*msg*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmOpenALSoundPlayer::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmOpenALSoundPlayer::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
if (m_lastOutputChunkDate == uint64_t(-1))
{
// Send header on initialize
m_encoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, 0, 0);
m_lastOutputChunkDate = 0;
}
// Look for command stimulations
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
m_decoder.decode(i);
if (m_decoder.isHeaderReceived()) { } // NOP
if (m_decoder.isBufferReceived())
{
const IStimulationSet* stimSet = m_decoder.getOutputStimulationSet();
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
{
const uint64_t stim = stimSet->getStimulationIdentifier(j);
if (stim == m_playTrigger)
{
playSound();
m_endOfSoundSent = false;
m_startOfSoundSent = false;
}
else if (stim == m_stopTrigger) { stopSound(true); }
else
{
// Immediate passthrough
m_stimulusSender->sendStimulation(stim);
}
}
}
if (m_decoder.isEndReceived())
{
// @fixme potentially bad behavior: the box may send chunks after sending this end
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, m_lastOutputChunkDate, this->getPlayerContext().getCurrentTime());
m_lastOutputChunkDate = this->getPlayerContext().getCurrentTime();
}
}
// n.b. TCP Tagging should be used instead of this socket output. This code is kept for backwards compatibility.
IStimulationSet* oStimSet = m_encoder.getInputStimulationSet();
oStimSet->clear();
ALint status;
alGetSourcei(m_sourceHandle, AL_SOURCE_STATE, &status);
// CASE : the sound has stopped, and we need to send the stimulation
if (status == AL_STOPPED && !m_endOfSoundSent)
{
oStimSet->appendStimulation(m_stopTrigger, this->getPlayerContext().getCurrentTime(), 0);
m_endOfSoundSent = true;
}
// CASE : the sound has started playing, and we need to send the stimulation
if (status == AL_PLAYING && !m_startOfSoundSent)
{
oStimSet->appendStimulation(m_playTrigger, this->getPlayerContext().getCurrentTime(), 0);
m_startOfSoundSent = true;
}
m_encoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, m_lastOutputChunkDate, this->getPlayerContext().getCurrentTime());
m_lastOutputChunkDate = this->getPlayerContext().getCurrentTime();
return true;
}
bool CBoxAlgorithmOpenALSoundPlayer::openSoundFile()
{
switch (m_fileFormat)
{
case FILE_FORMAT_WAV:
{
this->getLogManager() << Kernel::LogLevel_Trace << "Buffering WAV file (this step may take some times for long files).\n";
m_soundBufferHandle = alutCreateBufferFromFile(m_filename);
this->getLogManager() << Kernel::LogLevel_Trace << "WAV file buffered.\n";
if (m_soundBufferHandle == AL_NONE)
{
this->getLogManager() << Kernel::LogLevel_Error << "ALUT can't create buffer from file " << m_filename << "\n";
this->getLogManager() << Kernel::LogLevel_Error << "ALUT ERROR:\n" << alutGetErrorString(alutGetError()) << "\n";
return false;
}
break;
}
case FILE_FORMAT_OGG:
{
// On windows using fopen+ov_open can lead to failure, as stated in the vorbis official documentation:
//http://xiph.org/vorbis/doc/vorbisfile/ov_open.html
// using ov_fopen instead.
//m_oggVorbisStream.File = fopen((const char *)m_filename, "rb");
//if (m_oggVorbisStream.File == nullptr)
//{
// this->getLogManager() << Kernel::LogLevel_Error << "Can't open file "<<m_filename<<": IO error\n.";
// return false;
//}
#if defined TARGET_OS_Windows
if (ov_fopen(const_cast<char*>(m_filename.toASCIIString()), &m_oggVorbisStream.Stream) < 0)
#elif defined TARGET_OS_Linux
if((m_oggVorbisStream.File = fopen((const char *)m_filename, "rb")) == nullptr)
{
this->getLogManager() << Kernel::LogLevel_Error << "Can't open file "<<m_filename<<": IO error\n.";
return false;
}
if(ov_open(m_oggVorbisStream.File, &(m_oggVorbisStream.Stream), nullptr, 0) < 0)
#else
#error "Please port this code"
#endif
{
this->getLogManager() << Kernel::LogLevel_Error << "Can't open file " << m_filename << ": OGG VORBIS stream error\n";
return false;
}
vorbis_info* infos = ov_info(&m_oggVorbisStream.Stream, -1);
m_oggVorbisStream.Format = infos->channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
m_oggVorbisStream.SampleRate = infos->rate;
//Now we fill the raw buffer (good for small piece of sound... use buffering for big files)
this->getLogManager() << Kernel::LogLevel_Trace << "Buffering OGG file (this step may take some times for long files).\n";
int bytesRead;
int bitStream;
char buffer[BUFFER_SIZE];
do
{
// Read up to a buffer's worth of decoded sound data
bytesRead = ov_read(&m_oggVorbisStream.Stream, buffer, BUFFER_SIZE, 0, 2, 1, &bitStream);
// Append to end of buffer
m_rawOggBufferFromFile.insert(m_rawOggBufferFromFile.end(), buffer, buffer + bytesRead);
} while (bytesRead > 0);
this->getLogManager() << Kernel::LogLevel_Trace << "OGG file buffered.\n";
//we have decoded all the file. we drop the decoder (file is closed for us).
ov_clear(&m_oggVorbisStream.Stream);
//create empty buffer
alGenBuffers(1, &m_soundBufferHandle);
//fill it with raw data
alBufferData(m_soundBufferHandle, m_oggVorbisStream.Format, &m_rawOggBufferFromFile[0], ALsizei(m_rawOggBufferFromFile.size()),
m_oggVorbisStream.SampleRate);
break;
}
default:
{
this->getLogManager() << Kernel::LogLevel_Error << "Unsupported file format. Please use only WAV or OGG files.\n";
return false;
}
}
#if UNIQUE_SOURCE
alGenSources(1, &m_sourceHandle);
alSourcei(m_sourceHandle, AL_BUFFER, m_soundBufferHandle);
alSourcei(m_sourceHandle, AL_LOOPING, (m_loop ? AL_TRUE : AL_FALSE));
#endif
return true;
}
bool CBoxAlgorithmOpenALSoundPlayer::playSound()
{
switch (m_fileFormat)
{
case FILE_FORMAT_WAV:
case FILE_FORMAT_OGG:
{
#if UNIQUE_SOURCE
ALint status;
alGetSourcei(m_sourceHandle, AL_SOURCE_STATE, &status);
if (status == AL_PLAYING) { alSourceStop(m_sourceHandle); } // we start back again
alSourcePlay(m_sourceHandle);
#else
ALuint src;
alGenSources(1, &src);
m_sources.push_back(src);
alSourcei (src, AL_BUFFER, m_soundBufferHandle);
alSourcei (src, AL_LOOPING, (m_loop?AL_TRUE:AL_FALSE));
alSourcePlay(src);
#endif
break;
}
default:
{
this->getLogManager() << Kernel::LogLevel_Error << "Unsupported file format. Please use only WAV or OGG files.\n";
return false;
}
}
m_stimulusSender->sendStimulation(m_playTrigger);
return true;
}
bool CBoxAlgorithmOpenALSoundPlayer::stopSound(const bool forwardStim)
{
switch (m_fileFormat)
{
case FILE_FORMAT_WAV:
case FILE_FORMAT_OGG:
{
#if UNIQUE_SOURCE
alSourceStop(m_sourceHandle);
#else
for (size_t i = 0;i<m_sources.size();i++)
{
//stop all sources
alSourceStop(m_sources[i]);
alDeleteSources(1, &m_sources[i]);
}
m_sources.clear();
#endif
break;
}
default:
{
this->getLogManager() << Kernel::LogLevel_Error << "Unsupported file format. Please use only WAV or OGG files.\n";
return false;
}
}
if (forwardStim) { m_stimulusSender->sendStimulation(m_stopTrigger); }
return true;
}
#endif //TARGET_HAS_ThirdPartyOpenAL
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,124 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyOpenAL
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <AL/alut.h>
#include <vorbis/vorbisfile.h>
#include <iostream>
#include <vector>
namespace TCPTagging {
class IStimulusSender; // fwd declare
}
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CBoxAlgorithmOpenALSoundPlayer final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
struct SOggVorbisStream
{
OggVorbis_File Stream;
FILE* File;
ALenum Format;
ALsizei SampleRate;
};
enum ESupportedFileFormat
{
FILE_FORMAT_WAV = 0,
FILE_FORMAT_OGG,
FILE_FORMAT_UNSUPPORTED
};
CBoxAlgorithmOpenALSoundPlayer() { }
void release() override { delete this; }
uint64_t getClockFrequency() override { return (128LL << 32); }
bool initialize() override;
bool uninitialize() override;
bool processClock(Kernel::CMessageClock& msg) override;
bool processInput(const size_t index) override;
bool process() override;
bool openSoundFile();
bool playSound();
bool stopSound(bool forwardStim);
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_OpenALSoundPlayer)
protected:
Toolkit::TStimulationDecoder<CBoxAlgorithmOpenALSoundPlayer> m_decoder;
Toolkit::TStimulationEncoder<CBoxAlgorithmOpenALSoundPlayer> m_encoder;
uint64_t m_lastOutputChunkDate = 0;
bool m_startOfSoundSent = false;
bool m_endOfSoundSent = false;
bool m_loop = false;
uint64_t m_playTrigger = 0;
uint64_t m_stopTrigger = 0;
CString m_filename;
std::vector<ALuint> m_sources;
ESupportedFileFormat m_fileFormat = FILE_FORMAT_UNSUPPORTED;
//The handles
ALuint m_soundBufferHandle;
ALuint m_sourceHandle;
//OGG
SOggVorbisStream m_oggVorbisStream;
std::vector<char> m_rawOggBufferFromFile;
// For sending stimulations to the TCP Tagging
TCPTagging::IStimulusSender* m_stimulusSender = nullptr;
};
class CBoxAlgorithmOpenALSoundPlayerDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Sound Player"); }
CString getAuthorName() const override { return CString("Laurent Bonnet"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Play/Stop a sound, with or without loop."); }
CString getDetailedDescription() const override
{
return CString("Available format : WAV / OGG. Play and stop with input stimulations. Box based on OpenAL.");
}
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("1.1"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_OpenALSoundPlayer; }
IPluginObject* create() override { return new CBoxAlgorithmOpenALSoundPlayer; }
CString getStockItemName() const override { return CString("gtk-media-play"); }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Input triggers", OV_TypeId_Stimulations);
prototype.addOutput("Resync triggers (deprecated)", OV_TypeId_Stimulations);
prototype.addSetting("PLAY trigger", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("STOP trigger", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("File to play", OV_TypeId_Filename, "${Path_Data}/plugins/stimulation/ov_beep.wav");
prototype.addSetting("Loop", OV_TypeId_Boolean, "false");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_OpenALSoundPlayerDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
#endif //TARGET_HAS_ThirdPartyOpenAL
@@ -0,0 +1,438 @@
#include "ovpCBoxAlgorithmP300IdentifierStimulator.h"
#include <cstdlib>
#include <iostream>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
bool CBoxAlgorithmP300IdentifierStimulator::initialize()
{
//get values of the configure windows for all settings
m_startStimulation = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_stimulationBase = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_nImages = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
//the number of images must be different to 0
if (m_nImages == 0)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "This stimulator should at least have 1 Image (got " << m_nImages << ")\n";
return false;
}
m_percentRepetitionTarget = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
//the percent of Repetition contains the target must be between 0% and 100%
if (m_percentRepetitionTarget > 100.)
{
this->getLogManager() << Kernel::LogLevel_Warning << "The percent of repetition contains Target, should not be more than 100% \n";
m_percentRepetitionTarget = 100.;
}
if (m_percentRepetitionTarget < 0.)
{
this->getLogManager() << Kernel::LogLevel_Warning << "The percent of repetition contains Target, should not be less than 0% \n";
m_percentRepetitionTarget = 0.;
}
m_repetitionCountInTrial = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
m_nTrial = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5);
m_flashDuration = uint64_t(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 6)) * double(1LL << 32));
m_noFlashDuration = uint64_t(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 7)) * double(1LL << 32));
m_interRepetitionDuration = uint64_t(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 8)) * double(1LL << 32));
m_interTrialDuration = uint64_t(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 9)) * double(1LL << 32));
//-----------------------------------------------------------------------------------------------------------------------------------------
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationEncoder));
m_encoder->initialize();
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
m_decoder->initialize();
m_targetDecoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
m_targetDecoder->initialize();
//initialized all variables
m_headerSent = false;
m_lastState = EStates::None;
m_nFlashInRepet = m_nImages;
m_nFlashInRepetWithoutTarget = m_nFlashInRepet - 1;
m_repetDuration = m_nFlashInRepet * (m_flashDuration + m_noFlashDuration);
m_repetDurationWithoutTarget = m_nFlashInRepetWithoutTarget * (m_flashDuration + m_noFlashDuration);
m_repetTarget = new uint64_t[m_repetitionCountInTrial];
reset();
return true;
}
bool CBoxAlgorithmP300IdentifierStimulator::reset()
{
m_lastTime = 0;
m_startReceived = false;
m_trialStartTime = m_interTrialDuration;
m_targetNum = 0;
m_trialIdx = 1;
//generate the first trial variables
this->generateTrialVars();
return true;
}
bool CBoxAlgorithmP300IdentifierStimulator::uninitialize()
{
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_targetDecoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_targetDecoder);
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
return true;
}
bool CBoxAlgorithmP300IdentifierStimulator::processInput(const size_t /*index*/)
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
if (!m_startReceived)
{
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
m_decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
const Kernel::TParameterHandler<IStimulationSet*> op_pStimulationSet(
m_decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
ip_buffer = boxContext.getInputChunk(0, i);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) { }
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
{
for (size_t j = 0; j < op_pStimulationSet->getStimulationCount(); ++j)
{
if (op_pStimulationSet->getStimulationIdentifier(j) == m_startStimulation)
{
this->getLogManager() << Kernel::LogLevel_Trace << "Start\n";
m_trialStartTime = op_pStimulationSet->getStimulationDate(j) + m_interTrialDuration;
m_startReceived = true;
}
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) { }
}
boxContext.markInputAsDeprecated(0, i);
}
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
{
if (m_startReceived)
{
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
m_targetDecoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
const Kernel::TParameterHandler<IStimulationSet*> op_stimSet(
m_targetDecoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
ip_buffer = boxContext.getInputChunk(1, i);
m_targetDecoder->process();
if (m_targetDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) { }
if (m_targetDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
{
for (size_t j = 0; j < op_stimSet->getStimulationCount(); ++j)
{
const uint64_t stimulationIdx = op_stimSet->getStimulationIdentifier(j) - m_stimulationBase;
if (stimulationIdx < m_nImages)
{
m_targetNum = int64_t(stimulationIdx);
this->getLogManager() << Kernel::LogLevel_Trace << "Choosen number of the targets " << m_targetNum << "\n";
}
}
}
if (m_targetDecoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) { }
}
boxContext.markInputAsDeprecated(1, i);
}
return true;
}
bool CBoxAlgorithmP300IdentifierStimulator::processClock(Kernel::CMessageClock& /*msg*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmP300IdentifierStimulator::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
const uint64_t time = this->getPlayerContext().getCurrentTime();
CStimulationSet stimSet;
//if start stimulation is receive
if (m_startReceived)
{
EStates state; // = EStates::NoFlash;
uint64_t flashIndex = uint64_t(-1);
//case of inter-trial
if (time < m_trialStartTime) { state = EStates::TrialRest; }
//case of in-trial
else
{
const uint64_t timeInTrial = time - m_trialStartTime;
//case of the current time is out of the trial time
if (timeInTrial >= m_trialDuration)
{
//has next trial
if (m_nTrial == 0 || m_trialIdx < m_nTrial)
{
m_trialStartTime = time + m_interTrialDuration;
state = EStates::TrialRest;
flashIndex = uint64_t(-1);
m_trialIdx++;
generateTrialVars();
}
//it was the last trial
else
{
state = EStates::None;
reset();
}
}
else
{
const int64_t timeInRepetition = getCurrentTimeInRepetition(timeInTrial);
//case of the current time is out of the repetition time
if (timeInRepetition < 0 || uint64_t(timeInRepetition) >= (m_repetWithoutTarget ? m_repetDurationWithoutTarget : m_repetDuration))
{
state = EStates::RepetitionRest;
flashIndex = uint64_t(-1);
}
else
{
flashIndex = timeInRepetition / (m_flashDuration + m_noFlashDuration);
//case of the current time isn't out of the flash time
if (timeInRepetition % (m_flashDuration + m_noFlashDuration) < m_flashDuration) { state = EStates::Flash; }
else { state = EStates::NoFlash; }
}
}
}
//case of the state changed
if (state != m_lastState)
{
//trigger send about the old state
switch (m_lastState)
{
//case of the older state was a flash
case EStates::Flash:
stimSet.appendStimulation(OVTK_StimulationId_VisualStimulationStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_VisualStimulationStop\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
break;
//case of the older state was a no-flash
case EStates::NoFlash:
break;
//case of the older state was a inter-repetition
case EStates::RepetitionRest:
if (state != EStates::TrialRest && state != EStates::None)
{
stimSet.appendStimulation(OVTK_StimulationId_SegmentStart, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStart\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
}
break;
//case of the older state was inter-Trial
case EStates::TrialRest:
stimSet.appendStimulation(OVTK_StimulationId_RestStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_RestStop\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
stimSet.appendStimulation(OVTK_StimulationId_TrialStart, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_TrialStart\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
stimSet.appendStimulation(OVTK_StimulationId_SegmentStart, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStart\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
break;
//case of the older state was a None state
case EStates::None:
stimSet.appendStimulation(OVTK_StimulationId_ExperimentStart, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_ExperimentStart\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
break;
default:
break;
}
//trigger and operation about the new state
switch (state)
{
//case of the new state is a flash
case EStates::Flash:
stimSet.appendStimulation(m_stimulationBase + m_images[flashIndex], time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_LabelId(x)\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
stimSet.appendStimulation(OVTK_StimulationId_VisualStimulationStart, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_VisualStimulationStart\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
break;
//case of the new state is a no-flash
case EStates::NoFlash:
break;
//case of the new state is a inter-repetition
case EStates::RepetitionRest:
stimSet.appendStimulation(OVTK_StimulationId_SegmentStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStop\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
this->generateSequence();
break;
//case of the new state is a inter-trial
case EStates::TrialRest:
m_targetNum = -1;
if (m_lastState != EStates::None)
{
if (m_lastState != EStates::RepetitionRest)
{
stimSet.appendStimulation(OVTK_StimulationId_SegmentStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStop\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
}
stimSet.appendStimulation(OVTK_StimulationId_TrialStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_TrialStop\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
}
stimSet.appendStimulation(OVTK_StimulationId_RestStart, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_RestStart\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
break;
//case of the new state is a none state
case EStates::None:
if (m_lastState != EStates::RepetitionRest)
{
stimSet.appendStimulation(OVTK_StimulationId_SegmentStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStop\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
}
stimSet.appendStimulation(OVTK_StimulationId_TrialStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_TrialStop\n\t; Trial index:" << m_trialIdx <<
" Repetition index: "
<< m_repetIdx << "\n";
stimSet.appendStimulation(OVTK_StimulationId_ExperimentStop, time, 0);
this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_ExperimentStop\n\t; Trial index:" << m_trialIdx
<< " Repetition index: " << m_repetIdx << "\n";
break;
default:
break;
}
m_lastState = state;
}
#if 0
this->getLogManager() << Kernel::LogLevel_Info << "State:" << state_to_string(state) << " - flash index:" << flashIdx << " - repetition index:" << repetitionIdx << " - trial index:" << m_trialIdx << "\n";
#endif
}
Kernel::TParameterHandler<IStimulationSet*> ip_stimSet(m_encoder->getInputParameter(OVP_GD_Algorithm_StimulationEncoder_InputParameterId_StimulationSet));
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer(
m_encoder->getOutputParameter(OVP_GD_Algorithm_StimulationEncoder_OutputParameterId_EncodedMemoryBuffer));
ip_stimSet = &stimSet;
op_buffer = boxContext.getOutputChunk(0);
if (!m_headerSent)
{
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeHeader);
boxContext.markOutputAsReadyToSend(0, m_lastTime, time);
}
if (m_lastTime != time)
{
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeBuffer);
boxContext.markOutputAsReadyToSend(0, m_lastTime, time);
}
m_lastTime = time;
m_headerSent = true;
return true;
}
void CBoxAlgorithmP300IdentifierStimulator::generateSequence()
{
if (m_repetIdx < m_repetitionCountInTrial)
{
this->getLogManager() << Kernel::LogLevel_Trace << "generate_sequence Repetition: " << m_repetIdx << " Target: " << m_targetNum << "\n";
if (m_repetIdx > 0) { m_repetWithoutTarget = m_repetTarget[m_repetIdx] == m_repetTarget[m_repetIdx - 1]; }
else { m_repetWithoutTarget = (m_repetTarget[0] == 0); }
m_repetIdx++;
std::vector<size_t> images;
m_images.clear();
for (size_t i = 0; i < m_nImages; ++i) { images.push_back(i); }
this->getLogManager() << Kernel::LogLevel_Trace << "Number target: " << " Repetition without target: " << m_repetWithoutTarget << "\n";
if (m_repetWithoutTarget && m_targetNum != -1) { images.erase(images.begin() + size_t(m_targetNum)); }
while (!images.empty())
{
const int j = rand() % images.size();
m_images.push_back(size_t(images[j]));
images.erase(images.begin() + j);
}
}
}
void CBoxAlgorithmP300IdentifierStimulator::generateTrialVars()
{
this->getLogManager() << Kernel::LogLevel_Trace << "generate_trial_vars " << "\n";
uint64_t nTargetInTrial = 0;
for (size_t i = 0; i < m_repetitionCountInTrial; ++i)
{
const size_t random = rand();
//increment the chance to display the target. we would'nt like to have no Target display in a trial.
double percentRepetitionTargetInc = m_percentRepetitionTarget + (100 - m_percentRepetitionTarget) / (m_repetitionCountInTrial - 1) * i;
const double percentRepetitionTarget = (nTargetInTrial < 1) ? percentRepetitionTargetInc : m_percentRepetitionTarget;
if (random % 100 < percentRepetitionTarget) { nTargetInTrial++; }
m_repetTarget[i] = nTargetInTrial;
}
m_trialDuration = nTargetInTrial * (m_repetDuration + m_interRepetitionDuration)
+ (m_repetitionCountInTrial - nTargetInTrial) * (m_repetDurationWithoutTarget + m_interRepetitionDuration);
m_repetIdx = 0;
m_targetNum = -1;
}
int64_t CBoxAlgorithmP300IdentifierStimulator::getCurrentTimeInRepetition(const uint64_t timeInTrial)
{
if (m_repetIdx == 0) { return -1; }
const int64_t timeInRepetition = timeInTrial - m_repetDuration * m_repetTarget[m_repetIdx - 1]
- m_repetDurationWithoutTarget * (m_repetIdx - m_repetTarget[m_repetIdx - 1])
- m_interRepetitionDuration * m_repetIdx;
//case of the current time in Repetition is out of the current Repetition time
if (timeInRepetition > 0)
{
generateSequence();
return getCurrentTimeInRepetition(timeInTrial);
}
return timeInRepetition + m_interRepetitionDuration + (m_repetWithoutTarget ? m_repetDurationWithoutTarget : m_repetDuration);
}
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,126 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <map>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CBoxAlgorithmP300IdentifierStimulator final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
uint64_t getClockFrequency() override { return 128LL << 32; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool processClock(Kernel::CMessageClock& msg) override;
bool process() override;
bool reset();
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_P300IdentifierStimulator)
protected:
enum class EStates { None, Flash, NoFlash, RepetitionRest, TrialRest, ExperimentStop };
uint64_t m_startStimulation = 0;
uint64_t m_stimulationBase = 0;
size_t m_nImages = 0;
double m_percentRepetitionTarget = 0;
size_t m_repetitionCountInTrial = 0;
size_t m_nTrial = 0;
uint64_t m_flashDuration = 0;
uint64_t m_noFlashDuration = 0;
uint64_t m_interRepetitionDuration = 0;
uint64_t m_interTrialDuration = 0;
// bool m_avoidNeighborFlashing = false;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::IAlgorithmProxy* m_targetDecoder = nullptr;
uint64_t m_lastTime = 0;
bool m_headerSent = false;
bool m_startReceived = false;
EStates m_lastState = EStates::None;
uint64_t m_trialStartTime = 0;
size_t m_nFlashInRepet = 0;
size_t m_nFlashInRepetWithoutTarget = 0;
uint64_t m_repetDuration = 0;
uint64_t m_repetDurationWithoutTarget = 0;
uint64_t m_trialDuration = 0;
size_t m_trialIdx = 0;
bool m_repetWithoutTarget = false; //true if the repetition doesn't contains the target
uint64_t m_repetIdx = 0;
int64_t m_targetNum = 0;
uint64_t* m_repetTarget = nullptr; //for every Repetition of one trial indicate the number of target should see for the begin of the trial
std::vector<size_t> m_images;
void generateSequence();
void generateTrialVars();
int64_t getCurrentTimeInRepetition(uint64_t timeInTrial);
};
class CBoxAlgorithmP300IdentifierStimulatorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("P300 Identifier Stimulator"); }
CString getAuthorName() const override { return CString("Baptiste Payan"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Generates a P300 stimulation sequence"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-select-font"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_P300IdentifierStimulator; }
IPluginObject* create() override { return new CBoxAlgorithmP300IdentifierStimulator; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Incoming stimulations", OV_TypeId_Stimulations);
prototype.addOutput("Produced stimulations", OV_TypeId_Stimulations);
prototype.addInput("Target Stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Start stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("Stimulation base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("Number of identifiable objects", OV_TypeId_Integer, "6");
prototype.addSetting("Percent of repetitions containing the target", OV_TypeId_Float, "100");
prototype.addSetting("Number of repetitions", OV_TypeId_Integer, "5");
prototype.addSetting("Number of trials", OV_TypeId_Integer, "5");
prototype.addSetting("Flash duration (in sec)", OV_TypeId_Float, "0.075");
prototype.addSetting("No flash duration (in sec)", OV_TypeId_Float, "0.125");
prototype.addSetting("Inter-repetition delay (in sec)", OV_TypeId_Float, "2");
prototype.addSetting("Inter-trial delay (in sec)", OV_TypeId_Float, "5");
// prototype.addSetting("Avoid neighbor flashing", OV_TypeId_Boolean, "false");
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_P300IdentifierStimulatorDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,378 @@
//
// @todo the code logic in process() should be simplified and made more clear, perhaps by adding more states to the machine
//
#include "ovpCBoxAlgorithmP300SpellerStimulator.h"
#include <cstdlib>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
/*
CString toString(const EStates state)
{
switch (state)
{
case EStates::None: return "None";
case EStates::Flash: return "Flash";
case EStates::NoFlash: return "NoFlash";
case EStates::RepetitionRest: return "RepetitionRest";
case EStates::TrialRest: return "TrialRest";
default: return "unknown";
}
}
*/
bool CBoxAlgorithmP300SpellerStimulator::initialize()
{
m_decoder = nullptr;
m_encoder = nullptr;
m_startStimulation = this->getTypeManager().getEnumerationEntryValueFromName(
OV_TypeId_Stimulation, FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0));
m_rowStimulationBase = this->getTypeManager().getEnumerationEntryValueFromName(
OV_TypeId_Stimulation, FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1));
m_columnStimulationBase = this->getTypeManager().getEnumerationEntryValueFromName(
OV_TypeId_Stimulation, FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2));
m_nRow = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
m_nCol = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
if (m_nRow == 0 || m_nCol == 0)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "This stimulator should at least have 1 row and 1 column (got " << m_nRow << " and " <<
m_nCol << "\n";
return false;
}
if (m_nRow != m_nCol)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "This stimulator should have the same number of row(s) and columns(s) (got " << m_nRow <<
" and " << m_nCol << "\n";
return false;
}
m_nRepetInTrial = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5);
m_nTrial = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 6);
m_flashDuration = CTime(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 7))).time();
m_noFlashDuration = CTime(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 8))).time();
m_interRepetDuration = CTime(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 9))).time();
m_interTrialDuration = CTime(double(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 10))).time();
m_avoidNeighborFlashing = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 11);
const uint64_t durationThreshold = (10LL << 32) / 1000; // 10ms
if (m_interRepetDuration < durationThreshold)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Inter repetition duration should not be less than 10 ms\n";
m_interRepetDuration = durationThreshold;
}
if (m_interTrialDuration < durationThreshold)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Inter trial duration should not be less than 10 ms\n";
m_interTrialDuration = durationThreshold;
}
if (m_avoidNeighborFlashing) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Avoid neighbor flashing setting is not considered yet\n"; }
// ----------------------------------------------------------------------------------------------------------------------------------------------------------
m_encoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationEncoder));
m_encoder->initialize();
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
m_decoder->initialize();
m_lastTime = 0;
m_headerSent = false;
m_startReceived = false;
m_lastState = EStates::None;
m_trialStartTime = m_interTrialDuration;
m_nFlashInRepet = m_nRow + m_nCol;
m_repetDuration = m_nFlashInRepet * (m_flashDuration + m_noFlashDuration);
m_trialDuration = m_nRepetInTrial * (m_repetDuration + m_interRepetDuration);
m_trialIdx = 1;
this->generateSequence();
return true;
}
bool CBoxAlgorithmP300SpellerStimulator::uninitialize()
{
if (m_decoder)
{
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
m_decoder = nullptr;
}
if (m_encoder)
{
m_encoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_encoder);
m_encoder = nullptr;
}
return true;
}
bool CBoxAlgorithmP300SpellerStimulator::processInput(const size_t /*index*/)
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
if (!m_startReceived)
{
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer(
m_decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
const Kernel::TParameterHandler<IStimulationSet*> op_stimSet(
m_decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
ip_buffer = boxContext.getInputChunk(0, i);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) { }
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
{
for (size_t j = 0; j < op_stimSet->getStimulationCount(); ++j)
{
if (op_stimSet->getStimulationIdentifier(j) == m_startStimulation)
{
m_trialStartTime = op_stimSet->getStimulationDate(j) + m_interTrialDuration;
m_startReceived = true;
}
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) { }
}
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
bool CBoxAlgorithmP300SpellerStimulator::processClock(Kernel::CMessageClock& /*msg*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmP300SpellerStimulator::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
const uint64_t currentTime = this->getPlayerContext().getCurrentTime();
CStimulationSet stimulationSet;
if (m_startReceived)
{
EStates state = EStates::NoFlash;
size_t flashIndex = size_t(-1);
if (currentTime < m_trialStartTime) { state = EStates::TrialRest; }
else
{
if ((m_trialIdx > m_nTrial) && (m_nTrial > 0)) { state = EStates::ExperimentStop; }
else
{
const uint64_t curTimeInTrial = currentTime - m_trialStartTime;
const uint64_t curTimeInRepet = curTimeInTrial % (m_repetDuration + m_interRepetDuration);
// FIXME is it necessary to keep next line uncomment ?
//size_t repritionIndexInTrial = currentTimeInTrial/(m_repetDuration+m_interRepetDuration);
const size_t flashIndexInRepet = curTimeInRepet / (m_flashDuration + m_noFlashDuration);
flashIndex = flashIndexInRepet;
// FIXME is it necessary to keep next line uncomment ?
//repetitionIdx = repritionIndexInTrial;
if (curTimeInTrial >= m_trialDuration)
{
if (m_nTrial == 0 || m_trialIdx <= m_nTrial)
{
m_trialStartTime = currentTime + m_interTrialDuration;
state = EStates::TrialRest;
flashIndex = size_t(-1);
// FIXME is it necessary to keep next line uncomment ?
//repetitionIdx = size_t(-1);
m_trialIdx++;
}
else
{
m_trialStartTime = currentTime + m_interTrialDuration;
state = EStates::None;
}
}
else
{
if (curTimeInRepet >= m_repetDuration)
{
state = EStates::RepetitionRest;
flashIndex = size_t(-1);
}
else
{
if (curTimeInRepet % (m_flashDuration + m_noFlashDuration) < m_flashDuration) { state = EStates::Flash; }
else { state = EStates::NoFlash; }
}
}
}
}
if (state != m_lastState)
{
const bool bRow = ((flashIndex & 1) == 1);
const long row = long(bRow ? m_rows[flashIndex >> 1] : -1);
const long col = long(bRow ? -1 : m_columns[flashIndex >> 1]);
switch (m_lastState)
{
case EStates::Flash:
stimulationSet.appendStimulation(OVTK_StimulationId_VisualStimulationStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_VisualStimulationStop\n";
break;
case EStates::NoFlash:
break;
case EStates::RepetitionRest:
if (state != EStates::TrialRest && state != EStates::None)
{
stimulationSet.appendStimulation(OVTK_StimulationId_SegmentStart, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStart\n";
}
break;
case EStates::TrialRest:
if (m_trialIdx <= m_nTrial)
{
stimulationSet.appendStimulation(OVTK_StimulationId_RestStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_RestStop\n";
stimulationSet.appendStimulation(OVTK_StimulationId_TrialStart, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_TrialStart\n";
stimulationSet.appendStimulation(OVTK_StimulationId_SegmentStart, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStart\n";
}
break;
case EStates::None:
if (m_trialIdx <= m_nTrial)
{
stimulationSet.appendStimulation(OVTK_StimulationId_ExperimentStart, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_ExperimentStart\n";
}
break;
default:
break;
}
switch (state)
{
case EStates::Flash:
stimulationSet.appendStimulation(bRow ? m_rowStimulationBase + row : m_columnStimulationBase + col, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_LabelId(x)\n";
stimulationSet.appendStimulation(OVTK_StimulationId_VisualStimulationStart, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_VisualStimulationStart\n";
break;
case EStates::NoFlash:
break;
case EStates::RepetitionRest:
stimulationSet.appendStimulation(OVTK_StimulationId_SegmentStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStop\n";
this->generateSequence();
break;
case EStates::TrialRest:
if (m_lastState != EStates::None)
{
if (m_lastState != EStates::RepetitionRest)
{
stimulationSet.appendStimulation(OVTK_StimulationId_SegmentStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStop\n";
}
stimulationSet.appendStimulation(OVTK_StimulationId_TrialStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_TrialStop\n";
}
if (m_trialIdx <= m_nTrial)
{
stimulationSet.appendStimulation(OVTK_StimulationId_RestStart, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_RestStart\n";
}
break;
case EStates::None:
if (m_lastState != EStates::RepetitionRest)
{
stimulationSet.appendStimulation(OVTK_StimulationId_SegmentStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_SegmentStop\n";
}
stimulationSet.appendStimulation(OVTK_StimulationId_TrialStop, currentTime, 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_TrialStop\n";
break;
case EStates::ExperimentStop:
// The experiment stop is sent with some delay to allow the last flash / letter to be processed gracefully by the DSP later
stimulationSet.appendStimulation(OVTK_StimulationId_ExperimentStop, currentTime + CTime(3.0).time(), 0);
//this->getLogManager() << Kernel::LogLevel_Trace << "sends OVTK_StimulationId_ExperimentStop\n";
break;
default: break;
}
m_lastState = state;
}
}
Kernel::TParameterHandler<IStimulationSet*> ip_stimSet(m_encoder->getInputParameter(OVP_GD_Algorithm_StimulationEncoder_InputParameterId_StimulationSet));
Kernel::TParameterHandler<IMemoryBuffer*> op_buffer(
m_encoder->getOutputParameter(OVP_GD_Algorithm_StimulationEncoder_OutputParameterId_EncodedMemoryBuffer));
ip_stimSet = &stimulationSet;
op_buffer = boxContext.getOutputChunk(0);
if (!m_headerSent)
{
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeHeader);
boxContext.markOutputAsReadyToSend(0, m_lastTime, currentTime);
}
if (m_lastTime != currentTime)
{
m_encoder->process(OVP_GD_Algorithm_StimulationEncoder_InputTriggerId_EncodeBuffer);
boxContext.markOutputAsReadyToSend(0, m_lastTime, currentTime);
}
m_lastTime = currentTime;
m_headerSent = true;
return true;
}
void CBoxAlgorithmP300SpellerStimulator::generateSequence()
{
std::vector<size_t> rows;
m_rows.clear();
for (size_t i = 0; i < m_nRow; ++i) { rows.push_back(i); }
for (size_t i = 0; i < m_nRow; ++i)
{
const size_t j = rand() % rows.size();
m_rows[i] = rows[j];
rows.erase(rows.begin() + j);
}
std::vector<size_t> cols;
m_columns.clear();
for (size_t i = 0; i < m_nCol; ++i) { cols.push_back(i); }
for (size_t i = 0; i < m_nCol; ++i)
{
const size_t j = rand() % cols.size();
m_columns[i] = cols[j];
cols.erase(cols.begin() + j);
}
}
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,113 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <map>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CBoxAlgorithmP300SpellerStimulator final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
uint64_t getClockFrequency() override { return 128LL << 32; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool processClock(Kernel::CMessageClock& msg) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_P300SpellerStimulator)
protected:
enum class EStates { None, Flash, NoFlash, RepetitionRest, TrialRest, ExperimentStop };
uint64_t m_startStimulation = 0;
uint64_t m_rowStimulationBase = 0;
uint64_t m_columnStimulationBase = 0;
size_t m_nRow = 0;
size_t m_nCol = 0;
size_t m_nRepetInTrial = 0;
size_t m_nTrial = 0;
uint64_t m_flashDuration = 0;
uint64_t m_noFlashDuration = 0;
uint64_t m_interRepetDuration = 0;
uint64_t m_interTrialDuration = 0;
bool m_avoidNeighborFlashing = false;
Kernel::IAlgorithmProxy* m_encoder = nullptr;
Kernel::IAlgorithmProxy* m_decoder = nullptr;
uint64_t m_lastTime = 0;
bool m_headerSent = false;
bool m_startReceived = false;
EStates m_lastState = EStates::None;
uint64_t m_trialStartTime = 0;
size_t m_nFlashInRepet = 0;
uint64_t m_repetDuration = 0;
uint64_t m_trialDuration = 0;
size_t m_trialIdx = 0;
std::map<size_t, size_t> m_rows;
std::map<size_t, size_t> m_columns;
void generateSequence();
};
class CBoxAlgorithmP300SpellerStimulatorDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("P300 Speller Stimulator"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Generates a stimulation sequence suitable for a P300 speller"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-select-font"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_P300SpellerStimulator; }
IPluginObject* create() override { return new CBoxAlgorithmP300SpellerStimulator; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Incoming stimulations", OV_TypeId_Stimulations);
prototype.addOutput("Produced stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Start stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("Row stimulation base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("Column stimulation base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_07");
prototype.addSetting("Number of rows", OV_TypeId_Integer, "6");
prototype.addSetting("Number of columns", OV_TypeId_Integer, "6");
prototype.addSetting("Number of repetitions", OV_TypeId_Integer, "5");
prototype.addSetting("Number of trials", OV_TypeId_Integer, "5");
prototype.addSetting("Flash duration (in sec)", OV_TypeId_Float, "0.075");
prototype.addSetting("No flash duration (in sec)", OV_TypeId_Float, "0.125");
prototype.addSetting("Inter-repetition delay (in sec)", OV_TypeId_Float, "2");
prototype.addSetting("Inter-trial delay (in sec)", OV_TypeId_Float, "5");
prototype.addSetting("Avoid neighbor flashing", OV_TypeId_Boolean, "false");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_P300SpellerStimulatorDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,90 @@
#include "ovpCBoxAlgorithmRunCommand.h"
#include <cstdlib>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
bool CBoxAlgorithmRunCommand::initialize()
{
m_decoder = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_StimulationDecoder));
m_decoder->initialize();
ip_buffer.initialize(m_decoder->getInputParameter(OVP_GD_Algorithm_StimulationDecoder_InputParameterId_MemoryBufferToDecode));
op_stimSet.initialize(m_decoder->getOutputParameter(OVP_GD_Algorithm_StimulationDecoder_OutputParameterId_StimulationSet));
const size_t nSetting = this->getStaticBoxContext().getSettingCount();
for (size_t i = 0; i < nSetting; i += 2)
{
m_commands[uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i))].
push_back(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i + 1));
}
return true;
}
bool CBoxAlgorithmRunCommand::uninitialize()
{
op_stimSet.uninitialize();
ip_buffer.uninitialize();
m_decoder->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_decoder);
return true;
}
bool CBoxAlgorithmRunCommand::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CBoxAlgorithmRunCommand::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
ip_buffer = boxContext.getInputChunk(0, i);
m_decoder->process();
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedHeader)) { }
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedBuffer))
{
IStimulationSet* stimSet = op_stimSet;
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
{
uint64_t stimulationID = stimSet->getStimulationIdentifier(j);
if (m_commands.find(stimulationID) != m_commands.end())
{
std::vector<CString>& command = m_commands[stimulationID];
auto it = command.begin();
while (it != command.end())
{
CString str = *it;
#if defined(WIN32)
// On Windows, we pad the call with quotes. This addresses the situation where both the
// command and some of its arguments have spaces, e.g. the call being like
//
// "C:\Program Files\blah.exe" --something "C:\Program Files\duh.dat"
//
// Without padding the whole line with quotes, it doesn't seem work.
str = CString("\"") + str + CString("\"");
#else
// On other platforms, we pass the call as-is. We cannot pad the call in the scenario as the
// Linux shell - on the other hand - doesn't like calls like ""blah blah" "blah""
#endif
this->getLogManager() << Kernel::LogLevel_Debug << "Running command [" << str << "]\n";
if (system(str.toASCIIString()) < 0) { this->getLogManager() << Kernel::LogLevel_Warning << "Could not run command " << str << "\n"; }
++it;
}
}
}
}
if (m_decoder->isOutputTriggerActive(OVP_GD_Algorithm_StimulationDecoder_OutputTriggerId_ReceivedEnd)) { }
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,110 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <cstdio>
#include <vector>
#include <map>
#include <iomanip>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CBoxAlgorithmRunCommand final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
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_RunCommand)
protected:
Kernel::IAlgorithmProxy* m_decoder = nullptr;
Kernel::TParameterHandler<const IMemoryBuffer*> ip_buffer;
Kernel::TParameterHandler<IStimulationSet*> op_stimSet;
std::map<uint64_t, std::vector<CString>> m_commands;
};
class CBoxAlgorithmRunCommandListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool check(Kernel::IBox& box) const
{
for (size_t i = 0; i < box.getSettingCount(); i += 2)
{
box.setSettingName(i, ("Stimulation " + std::to_string(i / 2 + 1)).c_str());
box.setSettingType(i, OV_TypeId_Stimulation);
box.setSettingName(i + 1, ("Command " + std::to_string(i / 2 + 1)).c_str());
box.setSettingType(i + 1, OV_TypeId_String);
}
return true;
}
bool onSettingAdded(Kernel::IBox& box, const size_t index) override
{
std::stringstream ss;
ss.fill('0');
ss << std::setw(2) << size_t(index / 2 + 1);
const CString name(ss.str().c_str());
box.setSettingDefaultValue(index, name);
box.setSettingValue(index, name);
box.addSetting("", OV_TypeId_String, "");
this->check(box);
return true;
}
bool onSettingRemoved(Kernel::IBox& box, const size_t index) override
{
box.removeSetting((index / 2) * 2);
this->check(box);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
class CBoxAlgorithmRunCommandDesc final : public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Run Command"); }
CString getAuthorName() const override { return CString("Yann Renard"); }
CString getAuthorCompanyName() const override { return CString("INRIA"); }
CString getShortDescription() const override { return CString("Runs some command using system call depending on provided stimulations"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("1.0"); }
CString getStockItemName() const override { return CString("gtk-execute"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_RunCommand; }
IPluginObject* create() override { return new CBoxAlgorithmRunCommand; }
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmRunCommandListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Stimulation 1", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("Command 1", OV_TypeId_String, "");
prototype.addFlag(Kernel::BoxFlag_CanAddSetting);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_RunCommandDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,230 @@
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "ovpCKeyboardStimulator.h"
#include <fstream>
#include <string>
#include <sstream>
#include <tcptagging/IStimulusSender.h>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
//! Callback for the close window button
//static void keyboard_stimulator_gtk_widget_do_nothing(GtkWidget* widget) { }
// Called when a key is pressed on the keyboard
static gboolean key_press_cb(GtkWidget* /*widget*/, GdkEventKey* thisEvent, gpointer data)
{
reinterpret_cast<CKeyboardStimulator*>(data)->processKey(thisEvent->keyval, true);
return true;
}
// Called when a key is released on the keyboard
static gboolean key_release_cb(GtkWidget* /*widget*/, GdkEventKey* thisEvent, gpointer data)
{
reinterpret_cast<CKeyboardStimulator*>(data)->processKey(thisEvent->keyval, false);
return true;
}
/**
* Called when a key has been pressed.
* \param key The gdk value to the pressed key.
* \param state state of the pressed key
* */
void CKeyboardStimulator::processKey(const guint key, const bool state)
{
//if there is one entry, adds the stimulation to the list of stims to be sent
if (m_keyToStimulation.count(key) != 0 && state != m_keyToStimulation[key].status)
{
if (state)
{
// getLogManager() << Kernel::LogLevel_Trace << "Pressed key code " << (size_t)key << "\n";
m_stimulusSender->sendStimulation(m_keyToStimulation[key].press);
m_stimulationToSend.push_back(m_keyToStimulation[key].press);
}
else
{
// getLogManager() << Kernel::LogLevel_Trace << "Released key code " << (size_t)key << "\n";
m_stimulusSender->sendStimulation(m_keyToStimulation[key].release);
m_stimulationToSend.push_back(m_keyToStimulation[key].release);
}
m_keyToStimulation[key].status = state;
}
else
{
// this->getLogManager() << Kernel::LogLevel_Warning << "Unhandled key code " << (size_t)key << "\n";
m_unknownKeyPressed = true;
m_unknownKeyCode = size_t(key);
}
}
/**
* Parse the configuration file and creates the Key/Stimulation associations.
* \param filename The name of the configuration file.
* \return True if the file was correctly parsed.
* */
bool CKeyboardStimulator::parseConfigurationFile(const char* filename)
{
std::ifstream file;
file.open(filename);
if (!file) { return false; }
std::string keyName, stimPress, stimRelease;
//reads all the couples key name/stim
while (!file.eof() && !file.fail())
{
file >> keyName >> stimPress >> stimRelease;
key_t key;
key.press = 0;
key.release = 0;
key.status = false;
// MAY CAUSE ENDIANNESS PROBLEMS !
sscanf(stimPress.c_str(), "0x%08llx", &key.press);
sscanf(stimRelease.c_str(), "0x%08llx", &key.release);
m_keyToStimulation[gdk_keyval_from_name(keyName.c_str())] = key;
}
file.close();
return true;
}
bool CKeyboardStimulator::initialize()
{
// Parses box settings to find input file's name
const CString fileName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
//CString tcpTaggingHostAddress = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
//CString tcpTaggingHostPort = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
const CString tcpTaggingHostAddress("localhost");
const CString tcpTaggingHostPort("15361");
if (!parseConfigurationFile(fileName.toASCIIString()))
{
getBoxAlgorithmContext()->getPlayerContext()->getLogManager() << Kernel::LogLevel_Warning << "Problem while parsing configuration file!\n";
m_error = true;
return false;
}
m_encoder.initialize(*this, 0);
//const std::string red("#602020");
const std::string green("#206020");
const std::string blue("#202060");
std::stringstream ss;
ss << "\nUse your keyboard to send stimulations\nAvailable keys are :\n\n";
for (auto i = m_keyToStimulation.begin(); i != m_keyToStimulation.end(); ++i)
{
ss << "<span size=\"smaller\">\t";
ss << "<span style=\"italic\" foreground=\"" << green << "\">" << gdk_keyval_name(i->first) << "</span>";
ss << "\t";
ss << "Pressed : <span style=\"italic\" foreground=\"" << blue << "\">"
<< this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, i->second.press) << "</span>";
ss << "\t";
ss << "Released : <span style=\"italic\" foreground=\"" << blue << "\">"
<< this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, i->second.release) << "</span>";
ss << "\t</span>\n";
}
GtkBuilder* builder =
gtk_builder_new(); // glade_xml_new(Directories::getDataDir() + "/plugins/stimulation/keyboard-stimulator.ui", nullptr, nullptr);
gtk_builder_add_from_file(builder, Directories::getDataDir() + "/plugins/stimulation/keyboard-stimulator.ui", nullptr);
gtk_builder_connect_signals(builder, nullptr);
m_widget = GTK_WIDGET(gtk_builder_get_object(builder, "keyboard_stimulator-eventbox"));
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(builder, "keyboard_stimulator-label")), ss.str().c_str());
g_signal_connect(m_widget, "key-press-event", G_CALLBACK(key_press_cb), this);
g_signal_connect(m_widget, "key-release-event", G_CALLBACK(key_release_cb), this);
g_object_unref(builder);
m_visualizationCtx = dynamic_cast<VisualizationToolkit::IVisualizationContext*>(this->createPluginObject(OVP_ClassId_Plugin_VisualizationCtx));
m_visualizationCtx->setWidget(*this, m_widget);
//TCP TAGGING
m_stimulusSender = TCPTagging::CreateStimulusSender();
if (!m_stimulusSender->connect(tcpTaggingHostAddress, tcpTaggingHostPort) && tcpTaggingHostAddress.toASCIIString()[0] != 0)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Unable to connect to AS's TCP Tagging plugin, stimuli wont be forwarded.\n";
}
return true;
}
bool CKeyboardStimulator::uninitialize()
{
if (m_stimulusSender)
{
delete m_stimulusSender;
m_stimulusSender = nullptr;
}
m_encoder.uninitialize();
if (m_widget)
{
g_object_unref(m_widget);
m_widget = nullptr;
}
if (m_visualizationCtx)
{
this->releasePluginObject(m_visualizationCtx);
m_visualizationCtx = nullptr;
}
return true;
}
bool CKeyboardStimulator::processClock(Kernel::CMessageClock& msg)
{
if (m_error) { return false; }
if (m_unknownKeyPressed)
{
this->getLogManager() << Kernel::LogLevel_Warning << "Unhandled key code " << m_unknownKeyCode << "\n";
m_unknownKeyPressed = false;
}
const uint64_t currentTime = msg.getTime();
if (currentTime == 0)
{
m_encoder.encodeHeader();
getBoxAlgorithmContext()->getDynamicBoxContext()->markOutputAsReadyToSend(0, 0, 0);
}
if (currentTime != m_previousActivationTime)
{
Kernel::IBoxIO* boxIO = getBoxAlgorithmContext()->getDynamicBoxContext();
IStimulationSet* stimulationSet = m_encoder.getInputStimulationSet();
stimulationSet->clear(); // The encoder may retain the buffer from the previous round, clear it
for (size_t i = 0; i < m_stimulationToSend.size(); ++i) { stimulationSet->appendStimulation(m_stimulationToSend[i], currentTime, 0); }
m_stimulationToSend.clear();
m_encoder.encodeBuffer();
boxIO->markOutputAsReadyToSend(0, m_previousActivationTime, currentTime);
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
}
m_previousActivationTime = currentTime;
return true;
}
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
#endif
@@ -0,0 +1,115 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGTK)
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
#include <vector>
#include <map>
#include <visualization-toolkit/ovviz_all.h>
namespace TCPTagging {
class IStimulusSender; // fwd declare
}
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CKeyboardStimulator final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
CKeyboardStimulator() { }
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
uint64_t getClockFrequency() override { return (32LL << 32); }
bool processClock(Kernel::CMessageClock& msg) override;
bool process() override { return true; }
bool parseConfigurationFile(const char* filename);
void processKey(const guint key, const bool state);
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_KeyboardStimulator)
protected:
Toolkit::TStimulationEncoder<CKeyboardStimulator> m_encoder;
GtkWidget* m_widget = nullptr;
typedef struct
{
uint64_t press;
uint64_t release;
bool status;
} key_t;
//! Stores keyvalue/stimulation couples
std::map<guint, key_t> m_keyToStimulation;
//! Vector of the stimulations to send when possible
std::vector<uint64_t> m_stimulationToSend;
//! Plugin's previous activation date
uint64_t m_previousActivationTime = 0;
bool m_error = false;
// TCP Tagging
TCPTagging::IStimulusSender* m_stimulusSender = nullptr;
bool m_unknownKeyPressed = false;
size_t m_unknownKeyCode = 0;
VisualizationToolkit::IVisualizationContext* m_visualizationCtx = nullptr;
};
/**
* Plugin's description
*/
class CKeyboardStimulatorDesc final : public IBoxAlgorithmDesc
{
public:
CString getName() const override { return CString("Keyboard stimulator"); }
CString getAuthorName() const override { return CString("Bruno Renier"); }
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
CString getShortDescription() const override { return CString("Stimulation generator"); }
CString getDetailedDescription() const override { return CString("Sends stimulations according to key presses"); }
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("0.2"); }
void release() override { }
CIdentifier getCreatedClass() const override { return OVP_ClassId_KeyboardStimulator; }
IPluginObject* create() override { return new CKeyboardStimulator(); }
bool hasFunctionality(const EPluginFunctionality functionality) const override { return functionality == EPluginFunctionality::Visualization; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addOutput("Outgoing Stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Filename", OV_TypeId_Filename, "${Path_Data}/plugins/stimulation/simple-keyboard-to-stimulations.txt");
// @note we don't want to expose these to the user as there is no latency correction in tcp tagging; its best to use localhost
// prototype.addSetting("TCP Tagging Host address", OV_TypeId_String, "");
// prototype.addSetting("TCP Tagging Host port", OV_TypeId_Integer, "15361");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_KeyboardStimulatorDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
#endif
@@ -0,0 +1,140 @@
#include "ovpCSignChangeDetector.h"
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
bool CSignChangeDetector::initialize()
{
// we read the settings:
// The stimulations names:
const CString on = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
const CString off = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
m_onStimId = getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, on);
m_offStimId = getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, off);
m_lastSample = 0;
m_firstSample = true;
m_decoder.initialize(*this, 0);
m_encoder.initialize(*this, 0);
m_channelIdx = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
if (m_channelIdx == 0)
{
this->getLogManager() << Kernel::LogLevel_Info << "Channel Index is 0. The channel indexing convention starts from 1.\n";
return false;
}
m_channelIdx--; // Convert from [1,n] indexing to [0,n-1] indexing used later
return true;
}
bool CSignChangeDetector::uninitialize()
{
m_encoder.uninitialize();
m_decoder.uninitialize();
return true;
}
bool CSignChangeDetector::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
bool CSignChangeDetector::process()
{
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
// Get a few convenience handles
const CMatrix* matrix = m_decoder.getOutputMatrix();
IStimulationSet* stimSet = m_encoder.getInputStimulationSet();
// We decode the stream matrix
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
const uint64_t start = boxContext.getInputChunkStartTime(0, i);
const uint64_t end = boxContext.getInputChunkEndTime(0, i);
m_decoder.decode(i);
// if we received the header
if (m_decoder.isHeaderReceived())
{
//we analyse the header (meaning the input matrix size)
if (matrix->getDimensionCount() != 2)
{
this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Streamed matrix must have exactly 2 dimensions\n";
return false;
}
if (m_channelIdx >= matrix->getDimensionSize(0))
{
this->getLogManager() << Kernel::LogLevel_Info << "Channel Index out of bounds. Incoming matrix has fewer channels than specified index.\n";
return false;
}
m_samplesPerChannel = matrix->getDimensionSize(1);
// we send a header on the stimulation output:
m_encoder.encodeHeader();
boxContext.markOutputAsReadyToSend(0, start, end);
}
// if we received a buffer
if (m_decoder.isBufferReceived())
{
stimSet->clear();
const double* data = matrix->getBuffer();
// for each data sample of the buffer we look for sign change
for (size_t j = 0; j < matrix->getDimensionSize(1); ++j)
{
const double currentSample = data[(m_channelIdx * m_samplesPerChannel) + j];
if (m_firstSample)
{
m_lastSample = currentSample;
m_firstSample = false;
}
// Change from positive to negative
if (m_lastSample >= 0 && currentSample < 0)
{
const uint64_t time = start + (end - start) * j / m_samplesPerChannel;
stimSet->appendStimulation(m_offStimId, time, 0);
}
// Change from negative to positive
if (m_lastSample < 0 && currentSample >= 0)
{
const uint64_t time = start + (end - start) * j / m_samplesPerChannel;
stimSet->appendStimulation(m_onStimId, time, 0);
}
m_lastSample = currentSample;
}
m_encoder.encodeBuffer();
boxContext.markOutputAsReadyToSend(0, start, end);
}
// if we received the End
if (m_decoder.isEndReceived())
{
// we send the End signal to the stimulation output:
m_encoder.encodeEnd();
boxContext.markOutputAsReadyToSend(0, start, end);
}
// The stream matrix chunk i has been processed
boxContext.markInputAsDeprecated(0, i);
}
return true;
}
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
class CSignChangeDetector final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
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_SignChangeDetector)
protected:
Toolkit::TStreamedMatrixDecoder<CSignChangeDetector> m_decoder;
Toolkit::TStimulationEncoder<CSignChangeDetector> m_encoder;
uint64_t m_onStimId = 0;
uint64_t m_offStimId = 0;
uint64_t m_channelIdx = 0;
uint64_t m_samplesPerChannel = 0;
double m_lastSample = 0;
bool m_firstSample = false;
};
class CSignChangeDetectorDesc final : virtual public IBoxAlgorithmDesc
{
public:
void release() override { }
CString getName() const override { return CString("Sign Change Detector"); }
CString getAuthorName() const override { return CString("Joan Fruitet and Jozef Legeny"); }
CString getAuthorCompanyName() const override { return CString("Inria Sophia"); }
CString getShortDescription() const override { return CString("Detects the change of input's sign"); }
CString getDetailedDescription() const override
{
return CString("Triggers a stimulation when one of the input's sign changes (input gets positive or negative");
}
CString getCategory() const override { return CString("Stimulation"); }
CString getVersion() const override { return CString("1.2"); }
CString getStockItemName() const override { return CString("gtk-missing-image"); }
CIdentifier getCreatedClass() const override { return OVP_ClassId_SignChangeDetector; }
IPluginObject* create() override { return new CSignChangeDetector; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addInput("Signal", OV_TypeId_StreamedMatrix);
prototype.addOutput("Generated stimulations", OV_TypeId_Stimulations);
prototype.addSetting("Sign switch to positive stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
prototype.addSetting("Sign switch to negative stimulation", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
prototype.addSetting("Channel Index", OV_TypeId_Integer, "1");
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_SignChangeDetectorDesc)
};
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,52 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_BoxAlgorithm_StimulationMultiplexer OpenViBE::CIdentifier(0x07DB4EFA, 0x472B0938)
#define OVP_ClassId_BoxAlgorithm_StimulationMultiplexerDesc OpenViBE::CIdentifier(0x79EF4E4D, 0x178F09E6)
#define OVP_ClassId_BoxAlgorithm_SoundPlayer OpenViBE::CIdentifier(0x18D06E9F, 0x68D43C23)
#define OVP_ClassId_BoxAlgorithm_SoundPlayerDesc OpenViBE::CIdentifier(0x246E5EC4, 0x127D21AA)
#define OVP_ClassId_BoxAlgorithm_StimulationVoter OpenViBE::CIdentifier(0x2BBD61FC, 0x041A4EDB)
#define OVP_ClassId_BoxAlgorithm_StimulationVoterDesc OpenViBE::CIdentifier(0x1C36287C, 0x6F143FBF)
#define OVP_ClassId_KeyboardStimulator OpenViBE::CIdentifier(0x00D317B9, 0x6324C3FF)
#define OVP_ClassId_KeyboardStimulatorDesc OpenViBE::CIdentifier(0x00E51ACD, 0x284CA2CF)
#define OVP_ClassId_BoxAlgorithm_P300IdentifierStimulator OpenViBE::CIdentifier(0x00F27FDB, 0x8203D1A5)
#define OVP_ClassId_BoxAlgorithm_P300IdentifierStimulatorDesc OpenViBE::CIdentifier(0x000F20CA, 0x2A4EA9C3)
#define OVP_ClassId_SignChangeDetector OpenViBE::CIdentifier(0x04FA78CD, 0xAFE45DE7)
#define OVP_ClassId_SignChangeDetectorDesc OpenViBE::CIdentifier(0x798ACD86, 0xEF1287A4)
#define OVP_ClassId_BoxAlgorithm_StimulationFilter OpenViBE::CIdentifier(0x02F96101, 0x5E647CB8)
#define OVP_ClassId_BoxAlgorithm_StimulationFilterDesc OpenViBE::CIdentifier(0x4D2A23FC, 0x28191E18)
#define OVP_ClassId_BoxAlgorithm_LuaStimulator OpenViBE::CIdentifier(0x0B5A2787, 0x02750621)
#define OVP_ClassId_BoxAlgorithm_LuaStimulatorDesc OpenViBE::CIdentifier(0x67AF36F3, 0x2B424F46)
#define OVP_ClassId_BoxAlgorithm_OpenALSoundPlayer OpenViBE::CIdentifier(0x7AC2396F, 0x7EE52EFE)
#define OVP_ClassId_BoxAlgorithm_OpenALSoundPlayerDesc OpenViBE::CIdentifier(0x6FD040EF, 0x7E2F1284)
#define OVP_ClassId_BoxAlgorithm_P300SpellerStimulator OpenViBE::CIdentifier(0x88857F9A, 0xF560D3EB)
#define OVP_ClassId_BoxAlgorithm_P300SpellerStimulatorDesc OpenViBE::CIdentifier(0xCEAFBB05, 0x5DA19DCB)
#define OVP_ClassId_BoxAlgorithm_RunCommand OpenViBE::CIdentifier(0x48843891, 0x7BFC57F4)
#define OVP_ClassId_BoxAlgorithm_RunCommandDesc OpenViBE::CIdentifier(0x29D449AE, 0x2CA94942)
#define OVP_ClassId_BoxAlgorithm_StimulationValidator OpenViBE::CIdentifier(0x393d15e9, 0x5b6f63b9)
#define OVP_ClassId_BoxAlgorithm_StimulationValidatorDesc OpenViBE::CIdentifier(0x553bdcca, 0xe66d09bf)
// Global defines
//---------------------------------------------------------------------------------------------------
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#include "ovp_global_defines.h"
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
#define OVP_TypeId_Voting_ClearVotes OpenViBE::CIdentifier(0x17AE30F8, 0x40B57661)
#define OVP_TypeId_Voting_ClearVotes_AfterOutput OpenViBE::CIdentifier(0x7FA81A20, 0x484023F9)
#define OVP_TypeId_Voting_ClearVotes_WhenExpires OpenViBE::CIdentifier(0x02766639, 0x00B155B4)
#define OVP_TypeId_Voting_OutputTime OpenViBE::CIdentifier(0x48583E8F, 0x47F22462)
#define OVP_TypeId_Voting_OutputTime_Vote OpenViBE::CIdentifier(0x2F37507F, 0x00C06761)
#define OVP_TypeId_Voting_OutputTime_Winner OpenViBE::CIdentifier(0x72416689, 0x17673658)
#define OVP_TypeId_Voting_OutputTime_Last OpenViBE::CIdentifier(0x4F2830DB, 0x716C2930)
#define OVP_TypeId_Voting_RejectClass_CanWin OpenViBE::CIdentifier(0x442F2F14, 0x7A17336C)
#define OVP_TypeId_Voting_RejectClass_CanWin_Yes OpenViBE::CIdentifier(0x40011974, 0x54BB3C71)
#define OVP_TypeId_Voting_RejectClass_CanWin_No OpenViBE::CIdentifier(0x275B746A, 0x480B302C)
#define OVP_TypeId_StimulationFilterAction OpenViBE::CIdentifier(0x09E59E57, 0x8D4A553A)
#define OVP_TypeId_StimulationFilterAction_Select OpenViBE::CIdentifier(0xBDBBA98D, 0xC0477399)
#define OVP_TypeId_StimulationFilterAction_Reject OpenViBE::CIdentifier(0xB7C594D2, 0x32474226)
@@ -0,0 +1,67 @@
#include "ovp_defines.h"
#include "box-algorithms/ovpCKeyboardStimulator.h"
#include "box-algorithms/ovpCSignChangeDetector.h"
#include "box-algorithms/ovpCBoxAlgorithmRunCommand.h"
#if defined TARGET_HAS_ThirdPartyLua
#include "box-algorithms/ovpCBoxAlgorithmLuaStimulator.h"
#endif // TARGET_HAS_ThirdPartyLua
#if defined TARGET_HAS_ThirdPartyOpenAL
#include "box-algorithms/ovpCBoxAlgorithmOpenALSoundPlayer.h"
#endif // TARGET_HAS_ThirdPartyOpenAL
#include "box-algorithms/adaptation/ovpCBoxAlgorithmStimulationFilter.h"
#include "box-algorithms/ovpCBoxAlgorithmP300SpellerStimulator.h"
#include "box-algorithms/ovpCBoxAlgorithmP300IdentifierStimulator.h"
#include "box-algorithms/CBoxAlgorithmStimulationValidator.hpp"
namespace OpenViBE {
namespace Plugins {
namespace Stimulation {
OVP_Declare_Begin()
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
OV_AttributeId_Box_FlagIsUnstable.id());
context.getTypeManager().registerEnumerationType(OVP_TypeId_StimulationFilterAction, "Stimulation Filter Action");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_StimulationFilterAction, "Select", OVP_TypeId_StimulationFilterAction_Select.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_StimulationFilterAction, "Reject", OVP_TypeId_StimulationFilterAction_Reject.id());
#if defined(TARGET_HAS_ThirdPartyGTK)
OVP_Declare_New(CKeyboardStimulatorDesc);
#endif
OVP_Declare_New(CSignChangeDetectorDesc);
OVP_Declare_New(CBoxAlgorithmRunCommandDesc);
#if defined TARGET_HAS_ThirdPartyLua
OVP_Declare_New(CBoxAlgorithmLuaStimulatorDesc);
#endif // TARGET_HAS_ThirdPartyLua
#if defined TARGET_HAS_ThirdPartyOpenAL
OVP_Declare_New(CBoxAlgorithmOpenALSoundPlayerDesc);
#endif // TARGET_HAS_ThirdPartyOpenAL
OVP_Declare_New(CBoxAlgorithmStimulationFilterDesc);
OVP_Declare_New(CBoxAlgorithmP300SpellerStimulatorDesc);
OVP_Declare_New(CBoxAlgorithmP300IdentifierStimulatorDesc);
OVP_Declare_New(CBoxAlgorithmStimulationValidatorDesc);
context.getTypeManager().registerEnumerationType(OVP_TypeId_Voting_ClearVotes, "Clear votes");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_ClearVotes, "When expires", OVP_TypeId_Voting_ClearVotes_WhenExpires.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_ClearVotes, "After output", OVP_TypeId_Voting_ClearVotes_AfterOutput.id());
context.getTypeManager().registerEnumerationType(OVP_TypeId_Voting_OutputTime, "Output time");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_OutputTime, "Time of voting", OVP_TypeId_Voting_OutputTime_Vote.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_OutputTime, "Time of last winning stimulus", OVP_TypeId_Voting_OutputTime_Winner.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_OutputTime, "Time of last voting stimulus", OVP_TypeId_Voting_OutputTime_Last.id());
context.getTypeManager().registerEnumerationType(OVP_TypeId_Voting_RejectClass_CanWin, "Reject can win");
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_RejectClass_CanWin, "Yes", OVP_TypeId_Voting_RejectClass_CanWin_Yes.id());
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Voting_RejectClass_CanWin, "No", OVP_TypeId_Voting_RejectClass_CanWin_No.id());
OVP_Declare_End()
} // namespace Stimulation
} // namespace Plugins
} // namespace OpenViBE