init
This commit is contained in:
+77
@@ -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
|
||||
+99
@@ -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
|
||||
+133
@@ -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
|
||||
+116
@@ -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
|
||||
+813
@@ -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
|
||||
+168
@@ -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
|
||||
+331
@@ -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
|
||||
+124
@@ -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
|
||||
+438
@@ -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
|
||||
+126
@@ -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
|
||||
+378
@@ -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
|
||||
+113
@@ -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
|
||||
+90
@@ -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
|
||||
+110
@@ -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
|
||||
+230
@@ -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
|
||||
+115
@@ -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
|
||||
+140
@@ -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
|
||||
+73
@@ -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
|
||||
+52
@@ -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)
|
||||
+67
@@ -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
|
||||
Reference in New Issue
Block a user