This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,118 @@
IF(OV_DISABLE_GTK)
MESSAGE(STATUS "Skipping Tracker, no GTK")
RETURN()
ENDIF(OV_DISABLE_GTK)
PROJECT(openvibe-tracker)
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl include/*.h)
# @FIXME bad hack, we are cannibalizing a lot of non-exported code from Designer and Kernel
# Its better than copy-paste but it'd be preferable to either export the necessary materials
# or make another solution.
SET(OV_AVP_PATH "${CMAKE_SOURCE_DIR}/../designer/plugins/visualization/ovp-advanced-visualization/src/")
LIST(APPEND SRC_FILES ${OV_AVP_PATH}/m_defines.hpp
${OV_AVP_PATH}/m_GtkGL.cpp
${OV_AVP_PATH}/m_GtkGL.hpp
${OV_AVP_PATH}/m_VisualizationTools.cpp
${OV_AVP_PATH}/m_VisualizationTools.hpp
${OV_AVP_PATH}/mTGtkGLWidget.hpp
${OV_AVP_PATH}/mIRuler.hpp
)
FILE(GLOB RULER_FILES RELATIVE ${OV_AVP_PATH}/ruler/ *.hpp)
# MESSAGE(STATUS "FOUND ${RULER_FILES} in ${OV_AVP_PATH}")
# AUX_SOURCE_DIRECTORY(${OV_AVP_PATH}/ruler RULER_FILES)
LIST(APPEND SRC_FILES ${RULER_FILES})
# from wip-jlindgre-feature-renderer-offset
ADD_DEFINITIONS("-DRENDERER_SUPPORTS_OFFSET")
SET(OV_DESIGNER_PATH "${CMAKE_SOURCE_DIR}/../designer/applications/platform/designer/src/")
FILE(GLOB designer_files
${OV_DESIGNER_PATH}/dynamic_settings/*.h ${OV_DESIGNER_PATH}/dynamic_settings/*.cpp
${OV_DESIGNER_PATH}/ovd_base.h
${OV_DESIGNER_PATH}/ovdCBoxConfigurationDialog.*
${OV_DESIGNER_PATH}/ovdCLogListenerDesigner.*
${OV_DESIGNER_PATH}/ovdCCommentEditorDialog.*
${OV_DESIGNER_PATH}/ovdCSettingCollectionHelper.*
)
LIST(APPEND SRC_FILES ${designer_files})
configure_file(${OV_DESIGNER_PATH}/../share/interface.ui-base "${CMAKE_CURRENT_BINARY_DIR}/designer-interface.ui")
configure_file(${OV_DESIGNER_PATH}/../share/interface-settings.ui-base "${CMAKE_CURRENT_BINARY_DIR}/designer-interface-settings.ui")
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/designer-interface.ui" DESTINATION ${DIST_DATADIR}/openvibe/applications/tracker)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/designer-interface-settings.ui" DESTINATION ${DIST_DATADIR}/openvibe/applications/tracker)
SET(OV_KERNEL_PATH "${CMAKE_SOURCE_DIR}/../sdk/kernel/src/kernel/")
FILE(GLOB kernel_files
${OV_KERNEL_PATH}/scenario/ovkCBoxProto.*
${OV_KERNEL_PATH}/scenario/ovkTAttributable.h
${OV_KERNEL_PATH}/ovkTKernelObject.h
)
LIST(APPEND SRC_FILES ${kernel_files})
ADD_EXECUTABLE(${PROJECT_NAME} ${SRC_FILES})
SET_PROPERTY(TARGET ${PROJECT_NAME} PROPERTY FOLDER ${APP_FOLDER})
INCLUDE_DIRECTORIES("include")
INCLUDE_DIRECTORIES(${OV_AVP_PATH})
INCLUDE_DIRECTORIES(${OV_DESIGNER_PATH})
INCLUDE_DIRECTORIES(${OV_KERNEL_PATH})
INCLUDE_DIRECTORIES(${OV_KERNEL_PATH}/scenario)
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
INCLUDE("FindOpenViBEModuleSocket")
INCLUDE("FindOpenViBEModuleSystem")
INCLUDE("FindOpenViBEModuleFS")
INCLUDE("FindOpenViBEModuleMessaging")
INCLUDE("FindOpenViBEModuleXML")
INCLUDE("FindThirdPartyGTK")
INCLUDE("FindThirdPartyBoost_System")
INCLUDE("FindThirdPartyOpenGL")
INCLUDE("FindOpenViBEVisualizationToolkit")
INCLUDE("FindLibMensiaAdvancedVisualisation")
IF(WIN32)
# @FIXME CERT getting timeBeginPeriod() linker issues without this
TARGET_LINK_LIBRARIES(${PROJECT_NAME} winmm)
ENDIF(WIN32)
INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyRT") # external stims shared memory needs this
# ---------------------------------
# Target macros
# Defines target operating system, architecture and compiler
# ---------------------------------
IF(WIN32)
ADD_DEFINITIONS(-DWIN32_LEAN_AND_MEAN)
ENDIF(WIN32)
# ----------------------
# Generate launch script
# ----------------------
OV_INSTALL_LAUNCH_SCRIPT(SCRIPT_PREFIX "${PROJECT_NAME}" EXECUTABLE_NAME "${PROJECT_NAME}")
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
configure_file(share/tracker.ui-base "${CMAKE_CURRENT_BINARY_DIR}/tracker.ui" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tracker.ui" DESTINATION ${DIST_DATADIR}/openvibe/applications/tracker)
INSTALL(DIRECTORY signals DESTINATION ${DIST_DATADIR}/openvibe/scenarios/)
INSTALL(DIRECTORY share/ DESTINATION ${DIST_DATADIR}/openvibe/applications/tracker PATTERN "*-base" EXCLUDE)
INSTALL(DIRECTORY workspaces DESTINATION ${DIST_DATADIR}/openvibe/scenarios/)
@@ -0,0 +1,4 @@
Nothing for now. See git history for previous ideas.
@@ -0,0 +1,101 @@
#pragma once
#include <openvibe/ov_all.h>
#include "Contexted.h"
#include "StreamBundle.h"
#include "BoxAdapterHelper.h"
#include "Encoder.h"
#include "Decoder.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class BoxAdapter
* \brief A partially abstract class allowing processing data with OpenViBE boxes using a simple interface.
*
* The class implements a few of its members, but derived classes should implement the method spool() that feeds the data to the box,
* as well as members allowing setting the sources and destinations of the data.
*
* \author J. T. Lindgren
*
*/
class BoxAdapter : protected Contexted
{
public:
BoxAdapter(const Kernel::IKernelContext& ctx, const CIdentifier& algorithmId)
: Contexted(ctx), m_boxAlgorithmCtx(ctx), m_algorithmID(algorithmId) { }
~BoxAdapter() override { }
virtual bool initialize();
virtual bool spool(const bool verbose) = 0;
virtual bool uninitialize();
virtual TrackerBox& getBox() { return m_boxAlgorithmCtx.m_StaticBoxCtx; }
virtual CIdentifier getAlgorithmId() { return m_algorithmID; }
protected:
TrackerBoxAlgorithmContext m_boxAlgorithmCtx;
Plugins::IBoxAlgorithm* m_boxAlgorithm = nullptr;
CIdentifier m_algorithmID = CIdentifier::undefined();
};
/**
* \class BoxAdapterStream
* \brief A box adapter that reads from a Stream and writes to another Stream, having effect equivalent to outStream = process(inStream);
* \author J. T. Lindgren
*
*/
class BoxAdapterStream final : public BoxAdapter
{
public:
BoxAdapterStream(const Kernel::IKernelContext& ctx, const CIdentifier& algorithmId) : BoxAdapter(ctx, algorithmId) { }
void setSource(const StreamPtr& source) { m_src = source; }
void setTarget(const StreamPtr& target) { m_dst = target; }
bool initialize() override;
// Process the whole stream from source to target
bool spool(const bool verbose) override;
protected:
StreamPtr m_src = nullptr, m_dst = nullptr;
};
/**
* \class BoxAdapterBundle
* \brief A box adapter that reads from a StreamBundle and writes to another StreamBundle
* \author J. T. Lindgren
*
*/
class BoxAdapterBundle : public BoxAdapter
{
public:
BoxAdapterBundle(const Kernel::IKernelContext& ctx, const CIdentifier& algorithmId) : BoxAdapter(ctx, algorithmId) { }
~BoxAdapterBundle() override;
virtual void setSource(StreamBundle* source) { m_src = source; }
virtual void setTarget(StreamBundle* target) { m_dst = target; }
bool initialize() override;
// Process the whole bundle from source to target
bool spool(const bool verbose) override;
protected:
StreamBundle *m_src = nullptr, *m_dst = nullptr;
std::vector<EncoderBase*> m_encoders;
std::vector<DecoderBase*> m_decoders;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,44 @@
#pragma once
#include "BoxAdapter.h"
static const OpenViBE::CIdentifier STREAM_WRITER_ID = OpenViBE::CIdentifier(0x09C92218, 0x7C1216F8);
namespace OpenViBE {
namespace Tracker {
/**
* \class BoxAdapterGenericStreamWriter
* \brief A specific wrapper for Generic Stream Writer allowing to write tracks as .ov files from the Tracker
* \details
* This class can be considered as the counterpart of the Demuxer class, except here we simply wrap the Generic Stream Writer
* code instead of developing a new class.
* \author J. T. Lindgren
*
*/
class BoxAdapterGenericStreamWriter final : public BoxAdapterBundle
{
public:
BoxAdapterGenericStreamWriter(const Kernel::IKernelContext& ctx, StreamBundle& source, const std::string& filename)
: BoxAdapterBundle(ctx, STREAM_WRITER_ID), m_filename(filename) { BoxAdapterBundle::setSource(&source); }
bool initialize() override
{
// @fixme these should actually come from the box description
Kernel::IBox* boxCtx = const_cast<Kernel::IBox*>(m_boxAlgorithmCtx.getStaticBoxContext());
boxCtx->addSetting("Filename", 0, CString(m_filename.c_str()));
boxCtx->addSetting("Compress", 0, "false");
BoxAdapterBundle::initialize();
return true;
}
protected:
std::string m_filename;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,761 @@
/*
* This file implements various contexts we need in order to run a box.
*
* @fixme most of the implementation is very primitive and may be even missing altogether
*
*/
#pragma once
#include <thread>
#include <mutex>
#include <condition_variable>
#include <stack>
#include <map>
#include <vector>
#include <openvibe/ov_all.h>
#include <openvibe/CMatrix.hpp>
#include <fs/Files.h>
#include <ebml/CReader.h>
#include <ebml/CReaderHelper.h>
#include <algorithm> // std::find on Ubuntu
#include "Contexted.h"
#include "EncodedChunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TrackerPlayerContext
* \brief Implements Kernel::IPlayerContext
* \author J. T. Lindgren
*
*/
class TrackerPlayerContext final : protected Contexted, public Kernel::IPlayerContext
{
public:
explicit TrackerPlayerContext(const Kernel::IKernelContext& ctx) : Contexted(ctx) { }
uint64_t getCurrentTime() const override { return 0; }
uint64_t getCurrentLateness() const override { return 0; }
double getCurrentCPUUsage() const override { return 0; }
double getCurrentFastForwardMaximumFactor() const override { return 0; }
bool stop() override { return true; }
bool pause() override { return true; }
bool play() override { return true; }
bool forward() override { return true; }
Kernel::EPlayerStatus getStatus() const override { return Kernel::EPlayerStatus::Stop; }
Kernel::IConfigurationManager& getConfigurationManager() const override { return m_kernelCtx.getConfigurationManager(); }
Kernel::IAlgorithmManager& getAlgorithmManager() const override { return m_kernelCtx.getAlgorithmManager(); }
Kernel::ILogManager& getLogManager() const override { return m_kernelCtx.getLogManager(); }
Kernel::IErrorManager& getErrorManager() const override { return m_kernelCtx.getErrorManager(); }
Kernel::IScenarioManager& getScenarioManager() const override { return m_kernelCtx.getScenarioManager(); }
Kernel::ITypeManager& getTypeManager() const override { return m_kernelCtx.getTypeManager(); }
bool canCreatePluginObject(const CIdentifier& /*pluginID*/) const override { return false; }
Plugins::IPluginObject* createPluginObject(const CIdentifier& /*pluginID*/) const override { return nullptr; }
bool releasePluginObject(Plugins::IPluginObject* /*pluginObject*/) const override { return true; }
CIdentifier getClassIdentifier() const override { return CIdentifier(); }
};
/**
* \class TrackerBox
* \brief Implements Kernel::IBox
* \author J. T. Lindgren
*
*/
class TrackerBox : protected Contexted, public Kernel::IBox
{
public:
explicit TrackerBox(const Kernel::IKernelContext& ctx) : Contexted(ctx) { }
CIdentifier getIdentifier() const override { return m_ID; }
CString getName() const override { return m_Name; }
/*
CIdentifier getAlgorithmClassIdentifier() override { return m_AlgorithmClassID; };
*/
// @f
CIdentifier getUnusedSettingIdentifier(const CIdentifier& id = CIdentifier::undefined()) const override { return id; }
CIdentifier getUnusedInputIdentifier(const CIdentifier& id = CIdentifier::undefined()) const override { return id; }
CIdentifier getUnusedOutputIdentifier(const CIdentifier& id = CIdentifier::undefined()) const override { return id; }
bool addInterfacor(const Kernel::EBoxInterfacorType /*type*/, const CString& /*name*/, const CIdentifier& /*id1*/,
const CIdentifier& /*id2*/, const bool /*notify*/ = true) override { return false; }
bool removeInterfacor(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, const bool /*notify*/ = true) override { return false; }
size_t getInterfacorCount(const Kernel::EBoxInterfacorType interfacor) const override
{
if (interfacor == Kernel::Setting) { return getSettingCount(); }
if (interfacor == Kernel::Input) { return getInputCount(); }
if (interfacor == Kernel::Output) { return getOutputCount(); }
return 0;
}
size_t getInterfacorCountIncludingDeprecated(const Kernel::EBoxInterfacorType type) const override { return getInterfacorCount(type); }
bool getInterfacorIdentifier(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, CIdentifier& /*id*/) const override { return false; }
bool getInterfacorIndex(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/, size_t& /*idx*/) const override { return false; }
bool getInterfacorIndex(const Kernel::EBoxInterfacorType /*type*/, const CString& /*name*/, size_t& /*idx*/) const override { return false; }
bool getInterfacorType(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, CIdentifier& /*id*/) const override { return false; }
bool getInterfacorType(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id1*/,
CIdentifier& /*id2*/) const override { return false; }
bool getInterfacorType(const Kernel::EBoxInterfacorType /*type*/, const CString& /*name*/, CIdentifier& /*id*/) const
override { return false; }
bool getInterfacorName(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, CString& /*name*/) const override { return false; }
bool getInterfacorName(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/, CString& /*name*/) const
override { return false; }
bool getInterfacorDeprecatedStatus(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, bool& /*value*/) const override { return false; }
bool getInterfacorDeprecatedStatus(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/, bool& /*value*/) const
override { return false; }
bool hasInterfacorWithIdentifier(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/) const override { return false; }
bool hasInterfacorWithType(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, const CIdentifier& /*id*/) const
override { return false; }
bool hasInterfacorWithNameAndType(const Kernel::EBoxInterfacorType /*type*/, const CString& /*name*/,
const CIdentifier& /*id*/) const override { return false; }
bool setInterfacorType(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, const CIdentifier& /*id*/) override { return false; }
bool setInterfacorType(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id1*/,
const CIdentifier& /*id2*/) override { return false; }
bool setInterfacorType(const Kernel::EBoxInterfacorType /*type*/, const CString& /*name*/, const CIdentifier& /*id*/)
override { return false; }
bool setInterfacorName(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, const CString& /*name*/) override { return false; }
bool setInterfacorName(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/, const CString& /*name*/)
override { return false; }
bool setInterfacorDeprecatedStatus(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, const bool /*value*/) override { return false; }
bool setInterfacorDeprecatedStatus(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/, const bool /*value*/)
override { return false; }
bool updateInterfacorIdentifier(const Kernel::EBoxInterfacorType /*type*/, const size_t /*idx*/, const CIdentifier& /*newID*/)
override { return false; }
bool addInterfacorTypeSupport(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/) override { return false; }
bool hasInterfacorTypeSupport(const Kernel::EBoxInterfacorType /*type*/, const CIdentifier& /*id*/) const override { return false; }
bool setIdentifier(const CIdentifier& id) override
{
m_ID = id;
return true;
}
bool setName(const CString& name) override
{
m_Name = name;
return true;
}
bool setAlgorithmClassIdentifier(const CIdentifier& algorithmClassID) override
{
m_AlgorithmClassID = algorithmClassID;
return true;
}
bool initializeFromAlgorithmClassIdentifier(const CIdentifier& /*algorithmClassID*/) override { return false; }
bool initializeFromExistingBox(const IBox& existingBox) override
{
// this->clearBox();
this->setName(existingBox.getName());
this->setAlgorithmClassIdentifier(existingBox.getAlgorithmClassIdentifier());
for (size_t i = 0; i < existingBox.getInputCount(); ++i)
{
CIdentifier typeID = CIdentifier::undefined();
CString name;
existingBox.getInputType(i, typeID);
existingBox.getInputName(i, name);
addInput(name, typeID);
}
for (size_t i = 0; i < existingBox.getOutputCount(); ++i)
{
CIdentifier typeID = CIdentifier::undefined();
CString name;
existingBox.getOutputType(i, typeID);
existingBox.getOutputName(i, name);
addOutput(name, typeID);
}
for (size_t i = 0; i < existingBox.getSettingCount(); ++i)
{
CIdentifier typeID = CIdentifier::undefined();
CString name;
CString value;
CString defaultValue;
bool modifiability = false;
const bool notify = false;
existingBox.getSettingType(i, typeID);
existingBox.getSettingName(i, name);
existingBox.getSettingValue(i, value);
existingBox.getSettingDefaultValue(i, defaultValue);
existingBox.getSettingMod(i, modifiability);
addSetting(name, typeID, defaultValue, -1, modifiability);
setSettingValue(i, value, notify);
}
CIdentifier id = existingBox.getNextAttributeIdentifier(CIdentifier::undefined());
while (id != CIdentifier::undefined())
{
this->addAttribute(id, existingBox.getAttributeValue(id));
id = existingBox.getNextAttributeIdentifier(id);
}
CIdentifier streamTypeID = CIdentifier::undefined();
while ((streamTypeID = this->getKernelContext().getTypeManager().getNextTypeIdentifier(streamTypeID)) != CIdentifier::undefined())
{
if (this->getKernelContext().getTypeManager().isStream(streamTypeID))
{
//First check if it is a stream
if (existingBox.hasInputSupport(streamTypeID)) { this->addInputSupport(streamTypeID); }
if (existingBox.hasOutputSupport(streamTypeID)) { this->addOutputSupport(streamTypeID); }
}
}
// this->enableNotification();
// this->notify(EBoxModification::Initialized);
return true;
}
bool addInput(const CString& name, const CIdentifier& typeID, const CIdentifier& /*id*/ = CIdentifier::undefined(), const bool /*notify*/ = true) override
{
IOEntry tmp;
tmp.name = name;
tmp.id = typeID;
m_Inputs.push_back(tmp);
return true;
}
bool removeInput(const size_t /*idx*/, const bool /*notify*/ = true) override { return false; }
size_t getInputCount() const override { return m_Inputs.size(); }
bool getInputType(const size_t index, CIdentifier& typeID) const override
{
typeID = m_Inputs[index].id;
return true;
}
bool getInputName(const size_t index, CString& name) const override
{
name = m_Inputs[index].name;
return true;
}
bool setInputType(const size_t index, const CIdentifier& typeID) override
{
if (m_Inputs.size() <= index) { m_Inputs.resize(index + 1); }
m_Inputs[index].id = typeID;
return true;
}
bool setInputName(const size_t index, const CString& name) override
{
if (m_Inputs.size() <= index) { m_Inputs.resize(index + 1); }
m_Inputs[index].name = name;
return true;
}
bool addOutput(const CString& name, const CIdentifier& typeID, const CIdentifier& /*id*/ = CIdentifier::undefined(), const bool /*notify*/ = true) override
{
IOEntry tmp;
tmp.name = name;
tmp.id = typeID;
m_Outputs.push_back(tmp);
return true;
}
bool removeOutput(const size_t /*index*/, const bool /*notify*/ = true) override { return false; }
size_t getOutputCount() const override { return m_Outputs.size(); }
bool getOutputType(const size_t index, CIdentifier& typeID) const override
{
typeID = m_Outputs[index].id;
return true;
}
bool getOutputName(const size_t index, CString& name) const override
{
name = m_Outputs[index].name;
return true;
}
bool setOutputType(const size_t index, const CIdentifier& typeID) override
{
if (m_Outputs.size() <= index) { m_Outputs.resize(index + 1); }
m_Outputs[index].id = typeID;
return true;
}
bool setOutputName(const size_t index, const CString& name) override
{
if (m_Outputs.size() <= index) { m_Outputs.resize(index + 1); }
m_Outputs[index].name = name;
return true;
}
bool addSetting(const CString& name, const CIdentifier& typeID, const CString& defaultValue,
const size_t index = size_t(-1), const bool /*modiafiability*/ = false,
const CIdentifier& /*identifier*/ = CIdentifier::undefined(), const bool /*notify*/ = true) override
{
Kernel::ITypeManager& typeManager = m_kernelCtx.getTypeManager();
CString value(defaultValue);
if (typeManager.isEnumeration(typeID))
{
if (typeManager.getEnumerationEntryValueFromName(typeID, defaultValue) == CIdentifier::undefined().id())
{
if (typeManager.getEnumerationEntryCount(typeID) != 0)
{
// get value to the first enum entry
// and eventually correct this after
uint64_t tmp = 0;
typeManager.getEnumerationEntry(typeID, 0, value, tmp);
// Find if the default value string actually is an identifier, otherwise just keep the zero index name as default.
CIdentifier id = CIdentifier::undefined();
id.fromString(defaultValue);
// Finally, if it is an identifier, then a name should be found
// from the type manager ! Otherwise value is left to the default.
const CString candidateValue = typeManager.getEnumerationEntryNameFromValue(typeID, id.id());
if (candidateValue != CString("")) { value = candidateValue; }
}
}
}
Setting tmp;
tmp.name = name;
tmp.id = typeID;
tmp.value = value;
tmp.defaultValue = value;
const size_t idx = index;
if (index == size_t(-1) || index == m_Settings.size()) { m_Settings.push_back(tmp); }
else
{
auto it = m_Settings.begin();
it += idx;
m_Settings.insert(it, tmp);
}
return true;
}
bool removeSetting(const size_t index, const bool /*notify*/ = true) override
{
auto it = m_Settings.begin() + index;
if (it == m_Settings.end())
{
getLogManager() << Kernel::LogLevel_Error << "Error: No setting found\n";
return false;
}
it = m_Settings.erase(it);
return true;
}
size_t getSettingCount() const override { return m_Settings.size(); }
bool hasSettingWithName(const CString& /*name*/) const override { return false; }
//virtual int getSettingIndex(const CString& name) const override { return -1; }
bool getSettingType(const size_t index, CIdentifier& typeID) const override
{
typeID = m_Settings[index].id;
return true;
}
bool getSettingName(const size_t index, CString& name) const override
{
name = m_Settings[index].name;
return true;
}
bool getSettingDefaultValue(const CIdentifier& /*identifier*/, CString& /*rDefaultValue*/) const override { return false; }
bool getSettingDefaultValue(const size_t index, CString& rDefaultValue) const override
{
rDefaultValue = m_Settings[index].defaultValue;
return true;
}
bool getSettingDefaultValue(const CString& /*name*/, CString& /*rDefaultValue*/) const override { return false; }
bool getSettingValue(const size_t index, CString& value) const override
{
if (m_Settings.size() < index) { return false; }
value = m_Settings[index].value;
return true;
}
bool setSettingType(const size_t index, const CIdentifier& typeID) override
{
if (m_Settings.size() <= index) { m_Settings.resize(index + 1); }
m_Settings[index].id = typeID;
return true;
}
bool setSettingName(const size_t index, const CString& name) override
{
if (m_Settings.size() <= index) { m_Settings.resize(index + 1); }
m_Settings[index].name = name;
return true;
}
bool setSettingDefaultValue(const size_t index, const CString& rDefaultValue) override
{
if (m_Settings.size() <= index) { m_Settings.resize(index + 1); }
m_Settings[index].defaultValue = rDefaultValue;
return true;
}
bool setSettingValue(const size_t index, const CString& rValue, const bool /*notify*/ = true) override
{
if (m_Settings.size() <= index) { m_Settings.resize(index + 1); }
m_Settings[index].value = rValue;
return true;
}
bool getSettingMod(const size_t /*index*/, bool& /*value*/) const override { return false; }
bool setSettingMod(const size_t /*index*/, const bool /*value*/) override { return false; }
bool getSettingValue(const CIdentifier& /*identifier*/, CString& /*value*/) const override { return false; }
bool getSettingValue(const CString& /*name*/, CString& /*value*/) const override { return false; }
bool setSettingDefaultValue(const CIdentifier& /*identifier*/, const CString& /*rDefaultValue*/) override { return false; }
bool setSettingDefaultValue(const CString& /*name*/, const CString& /*rDefaultValue*/) override { return false; }
bool setSettingValue(const CIdentifier& /*identifier*/, const CString& /*value*/) override { return false; }
bool setSettingValue(const CString& /*name*/, const CString& /*value*/) override { return false; }
bool getSettingMod(const CIdentifier& /*identifier*/, bool& /*value*/) const override { return false; }
bool getSettingMod(const CString& /*name*/, bool& /*value*/) const override { return false; }
bool setSettingMod(const CIdentifier& /*identifier*/, const bool /*value*/) override { return false; }
bool setSettingMod(const CString& /*name*/, const bool /*value*/) override { return false; }
bool swapSettings(const size_t /*indexA*/, const size_t /*indexB*/) override { return false; }
bool swapInputs(const size_t /*indexA*/, const size_t /*indexB*/) override { return false; }
bool swapOutputs(const size_t /*indexA*/, const size_t /*indexB*/) override { return false; }
bool hasModifiableSettings() const override { return false; }
std::vector<CIdentifier> getInputSupportTypes() const override
{
std::vector<CIdentifier> tmp;
return tmp;
}
std::vector<CIdentifier> getOutputSupportTypes() const override
{
std::vector<CIdentifier> tmp;
return tmp;
}
size_t* getModifiableSettings(size_t& rCount) const override
{
rCount = 0;
return nullptr;
}
bool addInputSupport(const CIdentifier& typeID) override
{
m_InputSupports.push_back(typeID);
return true;
}
bool hasInputSupport(const CIdentifier& typeID) const override
{
return (m_InputSupports.empty() || std::find(m_InputSupports.begin(), m_InputSupports.end(), typeID) != m_InputSupports.end());
}
bool addOutputSupport(const CIdentifier& typeID) override
{
m_OutputSupports.push_back(typeID);
return true;
}
bool hasOutputSupport(const CIdentifier& typeID) const override
{
return (m_OutputSupports.empty() || std::find(m_OutputSupports.begin(), m_OutputSupports.end(), typeID) != m_OutputSupports.end());
}
bool setSupportTypeFromAlgorithmIdentifier(const CIdentifier& /*typeID*/) override { return true; }
CIdentifier getClassIdentifier() const override { return CIdentifier(); }
_IsDerivedFromClass_(Kernel::IAttributable, OV_ClassId_Kernel_Scenario_Box)
struct Setting
{
CIdentifier id = CIdentifier::undefined();
CString name;
CString value;
CString defaultValue;
};
struct IOEntry
{
CIdentifier id = CIdentifier::undefined();
CString name;
};
std::vector<Setting> m_Settings;
std::vector<IOEntry> m_Inputs;
std::vector<IOEntry> m_Outputs;
std::vector<CIdentifier> m_InputSupports;
std::vector<CIdentifier> m_OutputSupports;
// This box
CString m_Name;
CIdentifier m_ID = CIdentifier::undefined();
CIdentifier m_AlgorithmClassID = CIdentifier::undefined();
// Attributable
bool addAttribute(const CIdentifier& /*attributeID*/, const CString& /*sAttributeValue*/) override { return true; }
bool removeAttribute(const CIdentifier& /*attributeID*/) override { return true; }
bool removeAllAttributes() override { return true; }
CString getAttributeValue(const CIdentifier& /*attributeID*/) const override { return CString(""); }
bool setAttributeValue(const CIdentifier& /*attributeID*/, const CString& /*sAttributeValue*/) override { return true; }
bool hasAttribute(const CIdentifier& /*attributeID*/) const override { return false; }
bool hasAttributes() const override { return false; }
CIdentifier getNextAttributeIdentifier(const CIdentifier& /*previousID*/) const override { return CIdentifier(); }
// @f
void clearOutputSupportTypes() override { }
void clearInputSupportTypes() override { }
CIdentifier getAlgorithmClassIdentifier() const override { return CIdentifier(); }
};
/**
* \class TrackerBoxProto
* \brief Implements Kernel::IBoxProto
* \author J. T. Lindgren
*
*/
class TrackerBoxProto : public TrackerBox, public Kernel::IBoxProto
{
public:
explicit TrackerBoxProto(const Kernel::IKernelContext& ctx) : TrackerBox(ctx) {}
virtual bool addSetting(const CString& name, const CIdentifier& typeID, const CString& defaultValue,
const bool modifiable = false) { return TrackerBox::addSetting(name, typeID, defaultValue, -1, modifiable); }
};
/**
* \class TrackerBoxIO
* \brief Implements Kernel::IBoxIO
* \author J. T. Lindgren
*
*/
class TrackerBoxIO final : public Kernel::IBoxIO
{
public:
struct SBufferWithStamps
{
CMemoryBuffer* buffer;
CTime startTime;
CTime endTime;
};
std::vector<std::vector<SBufferWithStamps>> m_InputChunks; // Queue per input. See code for comments.
std::vector<IMemoryBuffer*> m_OutputChunks; // Just one buffer per output
std::vector<size_t> m_Ready;
std::vector<CTime> m_StartTime;
std::vector<CTime> m_EndTime;
~TrackerBoxIO() override { for (size_t i = 0; i < m_OutputChunks.size(); ++i) { delete m_OutputChunks[i]; } }
bool initialize(const Kernel::IBox* boxCtx)
{
for (size_t i = 0; i < m_OutputChunks.size(); ++i) { delete m_OutputChunks[i]; }
m_InputChunks.resize(boxCtx->getInputCount());
m_OutputChunks.resize(boxCtx->getOutputCount());
m_Ready.resize(boxCtx->getOutputCount(), 0);
m_StartTime.resize(boxCtx->getOutputCount());
m_EndTime.resize(boxCtx->getOutputCount());
for (size_t i = 0; i < m_OutputChunks.size(); ++i) { m_OutputChunks[i] = new CMemoryBuffer(); }
return true;
}
size_t getInputChunkCount(const size_t index) const override
{
if (index >= m_InputChunks.size()) { return 0; }
return m_InputChunks[index].size();
}
bool getInputChunk(const size_t index, const size_t chunkIdx, uint64_t& startTime, uint64_t& endTime, size_t& size,
const uint8_t*& buffer) const override
{
const std::vector<SBufferWithStamps>& chunks = m_InputChunks[index];
const SBufferWithStamps& chk = chunks[chunkIdx];
startTime = chk.startTime.time();
endTime = chk.endTime.time();
size = chk.buffer->getSize();
buffer = chk.buffer->getDirectPointer();
return true;
}
// Essentially this function being const and requiring its output as IMemoryBuffer prevents us from simply carrying our data as std::vector<EncodedChunk>.
const IMemoryBuffer* getInputChunk(const size_t index, const size_t chunkIdx) const override
{
const std::vector<SBufferWithStamps>& chunks = m_InputChunks[index];
const SBufferWithStamps& chk = chunks[chunkIdx];
return chk.buffer;
}
uint64_t getInputChunkStartTime(const size_t index, const size_t chunkIdx) const override
{
const std::vector<SBufferWithStamps>& chunks = m_InputChunks[index];
const SBufferWithStamps& chk = chunks[chunkIdx];
return chk.startTime.time();
}
uint64_t getInputChunkEndTime(const size_t index, const size_t chunkIdx) const override
{
const std::vector<SBufferWithStamps>& chunks = m_InputChunks[index];
const SBufferWithStamps& chk = chunks[chunkIdx];
return chk.endTime.time();
}
bool markInputAsDeprecated(const size_t /*index*/, const size_t /*chunkIdx*/) override { return true; }
size_t getOutputChunkSize(const size_t index) const override
{
if (index >= m_OutputChunks.size()) { return false; }
return m_OutputChunks[index]->getSize();
}
bool setOutputChunkSize(const size_t index, const size_t size, const bool discard = true) override
{
if (m_OutputChunks.size() <= index) { m_OutputChunks.resize(index + 1); }
m_OutputChunks[index]->setSize(size, discard);
return true;
}
uint8_t* getOutputChunkBuffer(const size_t index) override
{
if (m_OutputChunks.size() >= index) { return nullptr; }
return m_OutputChunks[index]->getDirectPointer();
}
bool appendOutputChunkData(const size_t index, const uint8_t* buffer, const size_t size) override { return m_OutputChunks[index]->append(buffer, size); }
IMemoryBuffer* getOutputChunk(const size_t index) override { return m_OutputChunks[index]; }
bool getOutputChunk(const size_t index, EncodedChunk& chk)
{
chk.m_Buffer.assign(m_OutputChunks[index]->getDirectPointer(),
m_OutputChunks[index]->getDirectPointer() + m_OutputChunks[index]->getSize());
chk.m_StartTime = m_StartTime[index];
chk.m_EndTime = m_EndTime[index];
return true;
}
bool markOutputAsReadyToSend(const size_t index, const uint64_t startTime, const uint64_t endTime) override
{
m_StartTime[index] = startTime;
m_EndTime[index] = endTime;
m_Ready[index] = true;
return true;
}
CIdentifier getClassIdentifier() const override { return CIdentifier(); }
bool addInputChunk(const size_t index, const EncodedChunk& chk)
{
if (m_InputChunks.size() <= index) { m_InputChunks.resize(index + 1); }
SBufferWithStamps buf;
buf.startTime = chk.m_StartTime;
buf.endTime = chk.m_EndTime;
// We cannot copy CMemoryBuffer, so have to use new() @fixme SDK should implement working copy or prevent
buf.buffer = new CMemoryBuffer(&chk.m_Buffer[0], chk.m_Buffer.size());
m_InputChunks[index].push_back(buf);
return true;
}
bool clearInputChunks()
{
for (size_t i = 0; i < m_InputChunks.size(); ++i)
{
for (auto& chk : m_InputChunks[i]) { delete chk.buffer; }
m_InputChunks[i].clear();
}
return true;
}
bool isReadyToSend(const size_t outputIdx) { return (m_Ready[outputIdx] != 0); }
bool deprecateOutput(const size_t outputIdx)
{
m_OutputChunks[outputIdx]->setSize(0, true);
m_Ready[outputIdx] = false;
return true;
}
};
/**
* \class TrackerBoxAlgorithmContext
* \brief Implements Kernel::IBoxAlgorithmContext
* \author J. T. Lindgren
*
*/
class TrackerBoxAlgorithmContext final : protected Contexted, public Kernel::IBoxAlgorithmContext
{
public:
explicit TrackerBoxAlgorithmContext(const Kernel::IKernelContext& ctx) : Contexted(ctx), m_StaticBoxCtx(ctx), m_PlayerCtx(ctx) { }
const Kernel::IBox* getStaticBoxContext() override { return &m_StaticBoxCtx; }
Kernel::IBoxIO* getDynamicBoxContext() override { return &m_DynamicBoxCtx; }
Kernel::IPlayerContext* getPlayerContext() override { return &m_PlayerCtx; }
bool markAlgorithmAsReadyToProcess() override { return true; }
CIdentifier getClassIdentifier() const override { return CIdentifier(); }
TrackerBoxIO* getTrackerBoxIO() { return &m_DynamicBoxCtx; }
TrackerBox m_StaticBoxCtx;
TrackerBoxIO m_DynamicBoxCtx;
TrackerPlayerContext m_PlayerCtx;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,74 @@
//
//
#pragma once
#include <string>
#include <vector>
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include "Contexted.h"
namespace OpenViBE {
namespace Kernel {
/**
* \class IBoxIOProxy
* \brief This proxy is intended for reusing codecs from the toolkit.
* @fixme it'd be more elegant not to duplicate this interface as a very similar one is already implemented in BoxAdapterHelper.
* however unifying the two would need some work.
* \author J. T. Lindgren
*
*/
class OV_API_Export IBoxIOProxy final : public IBoxIO
{
public:
size_t getInputChunkCount(const size_t /*index*/) const override { return 0; }
bool getInputChunk(const size_t /*inputIdx*/, const size_t /*chunkIdx*/, uint64_t& /*startTime*/, uint64_t& /*endTime*/, size_t& /*chunkSize*/,
const uint8_t*& /*chunkBuffer*/) const override { return true; }
const IMemoryBuffer* getInputChunk(const size_t /*inputIdx*/, const size_t /*chunkIdx*/) const override { return &m_InBuffer; }
uint64_t getInputChunkStartTime(const size_t /*inputIdx*/, const size_t /*chunkIdx*/) const override { return 0; }
uint64_t getInputChunkEndTime(const size_t /*inputIdx*/, const size_t /*chunkIdx*/) const override { return 0; }
bool markInputAsDeprecated(const size_t /*inputIdx*/, const size_t /*chunkIdx*/) override { return true; }
size_t getOutputChunkSize(const size_t /*index*/) const override { return m_OutBuffer.getSize(); }
bool setOutputChunkSize(const size_t /*index*/, const size_t size, const bool /*discard*/ = true) override { return m_OutBuffer.setSize(size, true); }
uint8_t* getOutputChunkBuffer(const size_t /*index*/) override { return m_OutBuffer.getDirectPointer(); }
bool appendOutputChunkData(const size_t /*index*/, const uint8_t* /*buffer*/, const size_t /*size*/) override { return false; } // not implemented
IMemoryBuffer* getOutputChunk(const size_t /*outputIdx*/) override { return &m_OutBuffer; }
bool markOutputAsReadyToSend(const size_t /*outputIdx*/, const uint64_t /*startTime*/, const uint64_t /*endTime*/) override { return true; }
CIdentifier getClassIdentifier() const override { return 0; }
CMemoryBuffer m_InBuffer;
CMemoryBuffer m_OutBuffer;
};
} // namespace Kernel
} // namespace OpenViBE
/**
* \class BoxAlgorithmProxy
* \brief This proxy is needed in order to use the stream codecs from the toolkit
* \author J. T. Lindgren
*
*/
class BoxAlgorithmProxy final : protected OpenViBE::Tracker::Contexted
{
public:
explicit BoxAlgorithmProxy(const OpenViBE::Kernel::IKernelContext& ctx) : Contexted(ctx) { }
OpenViBE::Kernel::IBoxIO& getDynamicBoxContext() { return dummy; }
static bool markAlgorithmAsReadyToProcess() { return true; }
OpenViBE::Kernel::IAlgorithmManager& getAlgorithmManager() const override { return Contexted::getAlgorithmManager(); }
OpenViBE::Kernel::IBoxIOProxy dummy;
};
@@ -0,0 +1,34 @@
#pragma once
#include <openvibe/ov_all.h>
#include "BoxAdapter.h"
#include "Contexted.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class BoxPlugins
* \brief Box Plugins is a factory-like class that keeps a list of box plugins that can be 'applied' to streams in Tracker
* \details Boxes can be registered in the constructor
* \author J. T. Lindgren
*
*/
class BoxPlugins final : protected Contexted
{
public:
explicit BoxPlugins(const Kernel::IKernelContext& ctx);
~BoxPlugins() override { for (auto ptr : m_boxPlugins) { delete ptr; } }
// Get a list of available box plugins
const std::vector<BoxAdapterStream*>& getBoxPlugins() const { return m_boxPlugins; }
protected:
bool create(const CIdentifier& streamType, const CIdentifier& alg, const CIdentifier& desc);
std::vector<BoxAdapterStream*> m_boxPlugins;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,153 @@
#pragma once
#include "ovd_base.h"
#include "ovdCLogListenerDesigner.h"
#include <vector>
#include <map>
namespace OpenViBE {
namespace Tracker {
/**
* \class CLogListenerTracker
* \brief Log listener wrapper for CLogListenerDesigner that can be called from multiple threads
* \author J. T. Lindgren
*
*/
class CLogListenerTracker final : public Kernel::ILogListener
{
public:
CLogListenerTracker(const Kernel::IKernelContext& ctx, GtkBuilder* builder) : m_impl(ctx, builder) { }
// @note Only call from gtk thread
void clearMessages()
{
std::unique_lock<std::mutex> lock(m_mutex);
// std::cout << "Intercepted\n";
m_impl.clearMessages();
}
bool isActive(const Kernel::ELogLevel level) override
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_impl.isActive(level);
}
bool activate(const Kernel::ELogLevel logLevel, const bool active) override
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_impl.activate(logLevel, active);
}
bool activate(const Kernel::ELogLevel startLogLevel, const Kernel::ELogLevel endLogLevel, const bool active) override
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_impl.activate(startLogLevel, endLogLevel, active);
}
bool activate(const bool active) override
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_impl.activate(active);
}
// This function encapsulates CLogListenerDesigner so that its gtk calls will be run from g_idle_add().
template <class T>
void logWrapper(T param)
{
auto ptr = new std::pair<CLogListenerTracker*, T>(this, param);
auto fun = [](gpointer data)
{
auto ptr = static_cast<std::pair<CLogListenerTracker*, T>*>(data);
// LogListenerDesigner may not be thread safe, so we lock here for safety as activate() and isActive() do not go through the g_idle_add().
std::unique_lock<std::mutex> lock(ptr->first->getMutex());
ptr->first->getImpl().log(ptr->second);
delete ptr;
return gboolean(FALSE);
};
g_idle_add(fun, ptr);
}
// Specialization for const char*. It frees memory. We don't call it
// template<> logWrapper() to avoid gcc complaining.
void logWrapperCharPtr(const char* param)
{
const auto ptr = new std::pair<CLogListenerTracker*, const char*>(this, param);
const auto fun = [](gpointer data)
{
auto ptr = static_cast<std::pair<CLogListenerTracker*, const char*>*>(data);
// LogListenerDesigner may not be thread safe, so we lock here for safety as activate() and isActive() do not go through the g_idle_add().
std::unique_lock<std::mutex> lock(ptr->first->getMutex());
ptr->first->getImpl().log(ptr->second);
delete[] ptr->second;
delete ptr;
return gboolean(FALSE);
};
g_idle_add(fun, ptr);
}
void log(const CTime value) override { logWrapper(value); }
void log(const uint64_t value) override { logWrapper(value); }
void log(const uint32_t value) override { logWrapper(value); }
void log(const int64_t value) override { logWrapper(value); }
void log(const int value) override { logWrapper(value); }
void log(const double value) override { logWrapper(value); }
void log(const bool value) override { logWrapper(value); }
void log(const Kernel::ELogLevel eLogLevel) override { logWrapper(eLogLevel); }
void log(const Kernel::ELogColor eLogColor) override { logWrapper(eLogColor); }
// With these calls we need to make copies, as by the time the actual calls are run in gtk_idle, the originals may be gone
void log(const CIdentifier& value) override
{
const CIdentifier nonRef = value;
logWrapper(nonRef);
}
void log(const CString& value) override
{
const CString nonRef = value;
logWrapper(nonRef);
}
void log(const std::string& value) override
{
const std::string nonRef = value;
logWrapper(nonRef);
}
void log(const char* value) override
{
// This one is tricksy, we cannot simply use CString as we'd lose color, but with char* we need to free the memory. Use a specialization.
#if defined(TARGET_OS_Windows)
const char* valueCopy = _strdup(value);
#else
const char* valueCopy = strdup(value);
#endif
logWrapperCharPtr(valueCopy);
}
// Callbacks need access to these
Designer::CLogListenerDesigner& getImpl() { return m_impl; }
std::mutex& getMutex() { return m_mutex; }
// getClassIdentifier()
_IsDerivedFromClass_Final_(Kernel::ILogListener, CIdentifier::undefined())
protected:
Designer::CLogListenerDesigner m_impl;
std::mutex m_mutex;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,93 @@
//
// OpenViBE Tracker
//
#pragma once
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include "Workspace.h"
#include "BoxPlugins.h"
#include "TrackerPlugins.h"
#include "Contexted.h"
#include "ParallelExecutor.h"
/**
* \namespace OpenViBE::Tracker
* \author J.T. Lindgren (Inria)
* \date 2018-09
* \brief Main OpenViBE Tracker namespace
*
* All the classes defined in the OpenViBE Tracker are currently included in
* this namespace.
*/
namespace OpenViBE {
namespace Tracker {
/**
* \class CTracker
* \author J. T. Lindgren
* \brief Tracker is the main class of OpenViBE Tracker that encapsulates the Workspace
* \details
* The tracker is in one of the states Stopped, Playing, Paused.
*
* - play() will start sending data to a specified processor from the tracks in the workspace.
* - step() will send out the chunk that is next in time order. Usually called by the GUI idle loop.
* - stop() will stop sending. Tracker can also stop if the processor quits or if all the data has been sent.
*
* In the stopped state, the Tracker also allows plugins to be applied to the Workspace content.
*
* In the future we can extend this class to host more than one workspace at the same time.
*
*/
class CTracker final : protected Contexted
{
public:
explicit CTracker(const Kernel::IKernelContext& ctx);
~CTracker() override;
enum class EStates
{
Stopped = 0,
Playing,
Paused,
Stopping
};
bool initialize();
bool play(const bool playFast);
bool stop();
EStates step();
// Useful getters
EStates getCurrentState() const { return m_state; }
Workspace& getWorkspace() { return m_workspace; }
const Kernel::IKernelContext& getKernelContext() const override { return m_kernelCtx; }
// Plugin handling
const BoxPlugins& getBoxPlugins() const { return m_boxPlugins; }
const TrackerPlugins& getTrackerPlugins() const { return m_trackerPlugins; }
bool applyBoxPlugin(size_t index);
bool applyTrackerPlugin(size_t index);
uint32_t getNumThreads() const { return m_executor.getNumThreads(); }
bool setNumThreads(uint32_t numThreads);
// Configuration handling
bool saveConfig(const CString& filename) const;
bool loadConfig(const CString& filename);
protected:
Workspace m_workspace;
EStates m_state = EStates::Stopped;
BoxPlugins m_boxPlugins;
TrackerPlugins m_trackerPlugins;
ParallelExecutor m_executor;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,32 @@
#pragma once
#include <openvibe/ov_all.h>
namespace OpenViBE {
namespace Tracker {
/**
* \class Chunk
* \brief Base class for all OpenViBE stream content (chunk) types
* \details
*
* Regardless of the type, all chunks in OpenViBE have start and end timestamps. Hence
* they should derive from this class. In particular, Header, Buffer and End chunks
* for some particular type should ultimately derive from this class. However,
* this class defines no content.
*
* @todo In the future if these types turn out to be more generally useful, we could consider
* moving them to the Kernel.
*
* \author J. T. Lindgren
*
*/
class Chunk
{
public:
CTime m_StartTime = CTime::min();
CTime m_EndTime = CTime::min();
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,105 @@
//
// OpenViBE Tracker
//
#pragma once
#include <openvibe/ov_all.h>
#include "EncodedChunk.h"
#include "Encoder.h"
#include "Decoder.h"
#include "StreamBase.h"
#include "Stream.h"
#include "CodecsAll.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class CodecFactory
* \brief A factory class for obtaining encoders and decoders for streams.
* \details In OpenViBE, every stream is passed around as EBML encoded due to an old design choice.
* The encoders and decoders are used to pass between the plain and encoded formats of the data.
* \author J. T. Lindgren
*
*/
class CodecFactory
{
public:
// Factory for encodes
static EncoderBase* getEncoder(const Kernel::IKernelContext& ctx, StreamBase& stream)
{
EncoderBase* encoder;
const CIdentifier typeID = stream.getTypeIdentifier();
if (typeID == OVTK_TypeId_StreamedMatrix) { encoder = new Encoder<TypeMatrix>(ctx, reinterpret_cast<Stream<TypeMatrix>&>(stream)); }
else if (typeID == OVTK_TypeId_Signal) { encoder = new Encoder<TypeSignal>(ctx, reinterpret_cast<Stream<TypeSignal>&>(stream)); }
else if (typeID == OVTK_TypeId_Stimulations) { encoder = new Encoder<TypeStimulation>(ctx, reinterpret_cast<Stream<TypeStimulation>&>(stream)); }
else if (typeID == OVTK_TypeId_ExperimentInfo)
{
encoder = new Encoder<TypeExperimentInfo>(ctx, reinterpret_cast<Stream<TypeExperimentInfo>&>(stream));
}
else if (typeID == OV_TypeId_ChannelLocalisation) // bug in toolkit atm with OVTK_ define
{
encoder = new Encoder<TypeChannelLocalization>(ctx, reinterpret_cast<Stream<TypeChannelLocalization>&>(stream));
}
else if (typeID == OV_TypeId_ChannelUnits) // bug in toolkit atm with OVTK_ define
{
encoder = new Encoder<TypeChannelUnits>(ctx, reinterpret_cast<Stream<TypeChannelUnits>&>(stream));
}
else if (typeID == OVTK_TypeId_Spectrum) { encoder = new Encoder<TypeSpectrum>(ctx, reinterpret_cast<Stream<TypeSpectrum>&>(stream)); }
else if (typeID == OVTK_TypeId_FeatureVector) { encoder = new Encoder<TypeFeatureVector>(ctx, reinterpret_cast<Stream<TypeFeatureVector>&>(stream)); }
else
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Warning: Unknown Decoder type " << ctx.getTypeManager().getTypeName(typeID) << " " <<
typeID.str() << "\n";
encoder = new Encoder<TypeError>(ctx, reinterpret_cast<Stream<TypeError>&>(stream));
}
return encoder;
}
// Factory for decoders
static DecoderBase* getDecoder(const Kernel::IKernelContext& ctx, StreamBase& stream)
{
DecoderBase* decoder;
const CIdentifier typeID = stream.getTypeIdentifier();
if (typeID == OVTK_TypeId_StreamedMatrix) { decoder = new Decoder<TypeMatrix>(ctx, reinterpret_cast<Stream<TypeMatrix>&>(stream)); }
else if (typeID == OVTK_TypeId_Signal) { decoder = new Decoder<TypeSignal>(ctx, reinterpret_cast<Stream<TypeSignal>&>(stream)); }
else if (typeID == OVTK_TypeId_Stimulations) { decoder = new Decoder<TypeStimulation>(ctx, reinterpret_cast<Stream<TypeStimulation>&>(stream)); }
else if (typeID == OVTK_TypeId_ExperimentInfo)
{
decoder = new Decoder<TypeExperimentInfo>(ctx, reinterpret_cast<Stream<TypeExperimentInfo>&>(stream));
}
else if (typeID == OV_TypeId_ChannelLocalisation) // bug in toolkit atm with OVTK_ define
{
decoder = new Decoder<TypeChannelLocalization>(ctx, reinterpret_cast<Stream<TypeChannelLocalization>&>(stream));
}
else if (typeID == OV_TypeId_ChannelUnits) // bug in toolkit atm with OVTK_ define
{
decoder = new Decoder<TypeChannelUnits>(ctx, reinterpret_cast<Stream<TypeChannelUnits>&>(stream));
}
else if (typeID == OVTK_TypeId_Spectrum) { decoder = new Decoder<TypeSpectrum>(ctx, reinterpret_cast<Stream<TypeSpectrum>&>(stream)); }
else if (typeID == OVTK_TypeId_FeatureVector) { decoder = new Decoder<TypeFeatureVector>(ctx, reinterpret_cast<Stream<TypeFeatureVector>&>(stream)); }
else
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Warning: Unknown Decoder type " << ctx.getTypeManager().getTypeName(typeID) << " " <<
typeID.str() << "\n";
decoder = new Decoder<TypeError>(ctx, reinterpret_cast<Stream<TypeError>&>(stream));
}
return decoder;
}
// Prevent constructing
CodecFactory() = delete;
CodecFactory(const CodecFactory&) = delete;
CodecFactory(CodecFactory&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,70 @@
#pragma once
#include <vector>
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "Encoder.h"
#include "Decoder.h"
#include "TypeError.h"
// This macro makes specializations by instantiating an adapter that handles a type with
// the actual encoder and decoder from the toolkit
#define CODEC_IMPL_VIA_TOOLKIT(TYPENAME, TOOLKITENCODER, TOOLKITDECODER) \
template<> class DecoderImpl<TYPENAME> : public DecoderAdapter< TYPENAME, OpenViBE::Toolkit::TOOLKITDECODER<BoxAlgorithmProxy> > \
{ \
public: \
DecoderImpl(const OpenViBE::Kernel::IKernelContext& ctx) : DecoderAdapter(ctx) { } \
};\
template<> class EncoderImpl<TYPENAME> : public EncoderAdapter< TYPENAME, OpenViBE::Toolkit::TOOLKITENCODER<BoxAlgorithmProxy> > \
{ \
public: \
EncoderImpl(const OpenViBE::Kernel::IKernelContext& ctx) : EncoderAdapter(ctx) { } \
};
namespace OpenViBE {
namespace Tracker {
/**
* \class DecoderImpl
* \brief Fallback class for invalid situations
* \author J. T. Lindgren
*
*/
template <>
class DecoderImpl<TypeError>
{
public:
explicit DecoderImpl(const Kernel::IKernelContext& /*ctx*/) { }
virtual ~DecoderImpl() = default;
virtual bool decode(const EncodedChunk& /*source*/) { return false; }
virtual bool getHeader(TypeError::Header& /*h*/) { return false; }
virtual bool getBuffer(TypeError::Buffer& /*b*/) { return false; }
virtual bool getEnd(TypeError::End& /*e*/) { return false; }
virtual bool isHeaderReceived() { return false; }
virtual bool isBufferReceived() { return false; }
virtual bool isEndReceived() { return false; }
};
/**
* \class EncoderImpl
* \brief Fallback class for invalid situations
* \author J. T. Lindgren
*
*/
template <>
class EncoderImpl<TypeError>
{
public:
explicit EncoderImpl(const Kernel::IKernelContext& /*ctx*/) { }
virtual ~EncoderImpl() = default;
bool encodeHeader(EncodedChunk& /*chk*/, const TypeError::Header& /*hdr*/) { return false; }
bool encodeBuffer(EncodedChunk& /*chk*/, const TypeError::Buffer& /*buf*/) { return false; }
bool encodeEnd(EncodedChunk& /*chk*/, const TypeError::End& /*end*/) { return false; }
bool setEncodeOffset(CTime /*newOffset*/) { return false; }
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,32 @@
#pragma once
#include "CodecImpl.h"
#include "TypeChannelLocalization.h"
#include "TypeChannelUnits.h"
#include "TypeExperimentInfo.h"
#include "TypeFeatureVector.h"
#include "TypeMatrix.h"
#include "TypeSignal.h"
#include "TypeSpectrum.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
CODEC_IMPL_VIA_TOOLKIT(TypeChannelLocalization, TChannelLocalisationEncoder, TChannelLocalisationDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeChannelUnits, TChannelUnitsEncoder, TChannelUnitsDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeExperimentInfo, TExperimentInfoEncoder, TExperimentInfoDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeFeatureVector, TFeatureVectorEncoder, TFeatureVectorDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeMatrix, TStreamedMatrixEncoder, TStreamedMatrixDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeSignal, TSignalEncoder, TSignalDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeSpectrum, TSpectrumEncoder, TSpectrumDecoder)
CODEC_IMPL_VIA_TOOLKIT(TypeStimulation, TStimulationEncoder, TStimulationDecoder)
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,41 @@
#pragma once
#include <openvibe/ov_all.h>
namespace OpenViBE {
namespace Tracker {
/**
* \class Contexted
* \brief This class can be used to provide KernelContext and related getters for derived classes without overly complicating the derived class interface in their headers.
* \author J. T. Lindgren
* @fixme actually this class does pretty much the same thing as Kernel::TKernelObject
*
*/
class Contexted
{
public:
explicit Contexted(const Kernel::IKernelContext& ctx) : m_kernelCtx(ctx) { }
virtual ~Contexted() { }
virtual const Kernel::IKernelContext& getKernelContext() const { return m_kernelCtx; }
virtual Kernel::IAlgorithmManager& getAlgorithmManager() const { return m_kernelCtx.getAlgorithmManager(); }
virtual Kernel::IErrorManager& getErrorManager() const { return m_kernelCtx.getErrorManager(); }
virtual Kernel::ILogManager& getLogManager() const { return m_kernelCtx.getLogManager(); }
virtual Kernel::ITypeManager& getTypeManager() const { return m_kernelCtx.getTypeManager(); }
// Convenience handle to getLogManager()
virtual Kernel::ILogManager& log() const { return getLogManager(); }
Contexted() = delete;
// Needed if Contexted is used as a base class for TAttributable
// virtual bool isDerivedFromClass(const CIdentifier& classID) const { return false; }
protected:
const Kernel::IKernelContext& m_kernelCtx;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,145 @@
#pragma once
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "BoxAlgorithmProxy.h"
#include "Chunk.h"
#include "EncodedChunk.h"
#include "Stream.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class DecoderBase
* \brief Base, non-typed abstract class for decoders
* \details Derived classes can be type-specific
* \author J. T. Lindgren
*
*/
class DecoderBase
{
public:
virtual bool decode(const EncodedChunk& chk) = 0;
virtual ~DecoderBase() { }
};
// Fwd declare, realized by Codecs*
template <typename T>
class DecoderImpl;
/**
* \class Decoder
* \brief Decoder for a specific type T.
* \details The constructor is passed a stream to decode to. The decode() call pushes chunks to that stream.
* \author J. T. Lindgren
*
*/
template <class T>
class Decoder final : public DecoderBase
{
public:
Decoder(const Kernel::IKernelContext& ctx, Stream<T>& stream) : m_stream(stream), m_decoder(ctx) { }
bool decode(const EncodedChunk& chk) override
{
m_decoder.decode(chk);
if (m_decoder.isHeaderReceived()) { return m_decoder.getHeader(m_stream.getHeader()); }
if (m_decoder.isBufferReceived())
{
typename T::Buffer* chunk = new typename T::Buffer();
if (m_decoder.getBuffer(*chunk))
{
m_stream.push(chunk);
return true;
}
return false;
}
if (m_decoder.isEndReceived()) { return m_decoder.getEnd(m_stream.getEnd()); }
return false;
}
protected:
Stream<T>& m_stream;
DecoderImpl<T> m_decoder;
};
/**
* \class DecoderAdapter
* \brief This decoder is a wrapper over the decoders in OpenViBEToolkit.
*
* The toolkit decoders require a box context to run, and do not return class objects.
* This decoder wrapper addresses those limitations.
* The usage is to create e.g. Decoder<TypeSignal> dec; and use the member functions to
* obtain the chunks with the signal type.
*
* \author J. T. Lindgren
*
*/
template <class T, class TToolkitDecoder>
class DecoderAdapter : protected Contexted
{
public:
explicit DecoderAdapter(const Kernel::IKernelContext& ctx) : Contexted(ctx), m_box(ctx), m_impl(m_box, 0) {}
~DecoderAdapter() override { }
virtual bool decode(const EncodedChunk& source)
{
if (source.m_Buffer.size() == 0)
{
// std::cout << "Empty chunk received\n";
return false;
}
m_box.dummy.m_InBuffer.setSize(0, true);
m_box.dummy.m_InBuffer.append(&source.m_Buffer[0], source.m_Buffer.size());
m_lastStart = source.m_StartTime;
m_lastEnd = source.m_EndTime;
return m_impl.decode(0);
}
virtual bool getHeader(typename T::Header& h)
{
setTimeStamps(h);
return getHeaderImpl(h);
}
virtual bool getBuffer(typename T::Buffer& b)
{
setTimeStamps(b);
return getBufferImpl(b);
}
virtual bool getEnd(typename T::End& e)
{
setTimeStamps(e);
return getEndImpl(e);
}
virtual bool isHeaderReceived() { return m_impl.isHeaderReceived(); }
virtual bool isBufferReceived() { return m_impl.isBufferReceived(); }
virtual bool isEndReceived() { return m_impl.isEndReceived(); }
protected:
// These two must be written as specilizations (Codec*cpp)
bool getHeaderImpl(typename T::Header& h);
bool getBufferImpl(typename T::Buffer& b);
static bool getEndImpl(typename T::End& /*e*/) { return true; } // NOP regardless of type
bool setTimeStamps(Chunk& chunk) const
{
chunk.m_StartTime = m_lastStart;
chunk.m_EndTime = m_lastEnd;
return true;
}
BoxAlgorithmProxy m_box;
CTime m_lastStart = CTime::min();
CTime m_lastEnd = CTime::min();
TToolkitDecoder m_impl;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,93 @@
#pragma once
#include <thread>
#include <mutex>
#include <condition_variable>
#include <stack>
#include <map>
#include <vector>
#include <openvibe/ov_all.h>
#include <openvibe/CMatrix.hpp>
#include <socket/IConnectionServer.h>
#include <fs/Files.h>
#include <ebml/CReader.h>
#include <ebml/CReaderHelper.h>
#include "StreamBundle.h"
#include "EBMLSourceFile.h"
#include "Contexted.h"
#include "EncodedChunk.h"
#include "Decoder.h"
class CClientHandler;
class OutputEncoder;
class EBMLSource;
namespace OpenViBE {
namespace Tracker {
/**
* \class Demuxer
* \brief Demuxes (and decodes) EBML streams
* \details EBML containers such as .ov multiplex one or more streams. Demuxer takes an EBML source and splits and decodes it to a Stream Bundle.
*
* It has similar purpose as the Generic Stream Reader box.
*
* \author J. T. Lindgren
*
*/
class Demuxer final : public EBML::IReaderCallback, protected Contexted
{
public:
Demuxer(const Kernel::IKernelContext& ctx, EBMLSourceFile& origin, StreamBundle& target)
: Contexted(ctx), m_origin(origin), m_target(target), m_reader(*this) { initialize(); }
bool initialize();
bool uninitialize() const;
// Reads some data from the origin and decodes it to target. Returns false on EOF.
bool step();
static bool stop() { return true; }
protected:
EBMLSourceFile& m_origin;
StreamBundle& m_target;
bool m_playingStarted = false;
EBML::CReader m_reader;
EBML::CReaderHelper m_readerHelper;
EncodedChunk m_pendingChunk;
uint32_t m_chunksSent = 0;
bool m_pending = false;
bool m_hasEBMLHeader = false;
std::stack<EBML::CIdentifier> m_nodes;
std::map<uint32_t, uint32_t> m_streamIdxToOutputIdxs;
std::map<uint32_t, CIdentifier> m_streamIdxToTypeIDs;
std::vector<size_t> m_chunkStreamType;
std::vector<DecoderBase*> m_decoders;
private:
bool isMasterChild(const EBML::CIdentifier& identifier) override;
void openChild(const EBML::CIdentifier& identifier) override;
void processChildData(const void* buffer, const size_t size) override;
void closeChild() override;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,95 @@
#pragma once
#include <iostream>
#include <thread>
#include <deque>
#include <vector>
#include "../../../../plugins/processing/file-io/src/ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include <fs/Files.h>
#include "Contexted.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class EBMLSourceFile
* \brief Reads bytes from an .ov file
* \author J. T. Lindgren
*
* @note some ov files which have lots of stimulation chunks take a long time to import
* when launching the Tracker from visual studio. this is probably due to memory allocation, similar to slow simple dsp grammar parsing.
*
*
*/
class EBMLSourceFile final : protected Contexted
{
public:
explicit EBMLSourceFile(const Kernel::IKernelContext& ctx) : Contexted(ctx) { }
bool initialize(const char* signalFile)
{
log() << Kernel::LogLevel_Trace << "EBMLSource: Initializing with " << signalFile << "\n";
m_file = FS::Files::open(signalFile, "rb");
if (!m_file) { return false; }
m_src = std::string(signalFile);
return true;
}
bool uninitialize()
{
log() << Kernel::LogLevel_Trace << "EBMLSource: Uninitializing\n";
if (m_file)
{
fclose(m_file);
m_file = nullptr;
}
return true;
}
bool isEOF() const
{
if (!m_file) { return true; }
return (feof(m_file) != 0 ? true : false);
}
bool read(std::vector<uint8_t>& bytes, const size_t numBytes) const
{
if (!m_file)
{
log() << Kernel::LogLevel_Error << "Error: No EBML source file set\n";
return false;
}
if (isEOF())
{
log() << Kernel::LogLevel_Trace << "EBML Source file EOF reached\n";
return false;
}
bytes.resize(numBytes);
const size_t s = fread(&bytes[0], sizeof(uint8_t), numBytes, m_file);
bytes.resize(s); // array size tells how much was read
return true;
}
const std::string& getSource() const { return m_src; }
protected:
std::string m_src;
FILE* m_file = nullptr;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,41 @@
#pragma once
#include <vector>
#include "Chunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class EncodedChunk
* \brief Data class for encoded chunks
* \details
* Stream content in OpenViBE is carried as encoded chunks. Although this inherits from Chunk,
* this type is a bit special since its not explicitly have (header, buffer, end) subclasses. This is
* because in its encoded form, the three are not differentiated but coded into bufferData.
*
* \note Here we use uint32_t for m_streamIdx as we assume it has fixed bitcount (unlike size_t which may change).
*/
class EncodedChunk : public Chunk
{
public:
// These are actually passed around in OpenViBE
std::vector<uint8_t> m_Buffer;
// These are convenience information for Tracker
uint64_t m_StreamType = 0;
size_t m_StreamIndex = 0;
};
// Note that we do not store this chunk type in EncodedChunk because when we receive EncodedChunks,
// the type is not available until 'bufferData' has been decoded -- especially the case for the End type?
enum class EChunkType
{
Header = 0,
Buffer = 1,
End = 2,
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,192 @@
#pragma once
#include <string.h> // memcpy() on Ubuntu
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "EncodedChunk.h"
#include "BoxAlgorithmProxy.h"
#include "Chunk.h"
#include "Stream.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class EncoderBase
* \brief Base, non-typed abstract class for encoders
* \author J. T. Lindgren
*
*/
class EncoderBase
{
public:
virtual ~EncoderBase() { }
// Encodes the current chunk of the attached stream (in the derived class)
// chkType will contain details about the specific chunk type (since EncodedChunk doesn't show it explicitly)
virtual bool encode(EncodedChunk& chk, EChunkType& chkType) = 0;
// Advance all timestamps of the encoded chunks by this amount
// Used for catenating multiple tracks
virtual bool setEncodeOffset(CTime newOffset) = 0;
};
// Fwd declare, realized by Codecs*
template <class T>
class EncoderImpl;
/**
* \class Encoder
* \brief Encoder class taking in typed Stream and it into EBML-containing encoded chunks.
* \details The constructor is passed a Stream to encode from. The encode() call pulls the next chunk from the Stream and returns an EncodedChunk.
* \author J. T. Lindgren
*
*/
template <class T>
class Encoder final : public EncoderBase
{
public:
Encoder(const Kernel::IKernelContext& ctx, Stream<T>& stream) : m_stream(stream), m_encoder(ctx) { }
// Encode the current chunk. The caller must step the stream.
bool encode(EncodedChunk& chk, EChunkType& chkType) override
{
const size_t position = m_stream.getPosition();
if (position == size_t(-1))
{
// std::cout << "Encode header\n";
chkType = EChunkType::Header;
return m_encoder.encodeHeader(chk, m_stream.getHeader());
}
if (position < m_stream.getChunkCount())
{
chkType = EChunkType::Buffer;
const bool retVal = m_encoder.encodeBuffer(chk, *m_stream.getChunk(position));
// std::cout << "Encoder gave " << chk.bufferData.size() << "\n";
return retVal;
}
if (position == m_stream.getChunkCount())
{
chkType = EChunkType::End;
auto end = m_stream.getEnd();
// Some streams might not have an end in the file. Here we force one to be present.
// @fixme move to load
if (end.m_StartTime == CTime::max() || end.m_EndTime == CTime::max())
{
// @note modifies the stream
end.m_StartTime = m_stream.getDuration();
end.m_EndTime = end.m_StartTime;
}
return m_encoder.encodeEnd(chk, end);
}
return false;
}
bool setEncodeOffset(CTime newOffset) override { return m_encoder.setEncodeOffset(newOffset); }
protected:
Stream<T>& m_stream;
EncoderImpl<T> m_encoder;
};
/**
* \class EncoderAdapter
* \brief Adapter to use implementations from Toolkit
*
* Note that this class does not set the encoded chunk times or index, these are currently
* handled outside the encoder.
*
* \author J. T. Lindgren
*
*/
template <class T, class TToolkitEncoder>
class EncoderAdapter : protected Contexted
{
public:
explicit EncoderAdapter(const Kernel::IKernelContext& ctx) : Contexted(ctx), m_box(ctx), m_impl(m_box, 0) {}
~EncoderAdapter() override { }
// If chunk timestamps are adjusted, this function should be used to adjust any possible
// timestamps inside the chunks accordingly
bool setEncodeOffset(const CTime newOffset)
{
m_offset = newOffset;
return true;
}
bool encodeHeader(EncodedChunk& chk, const typename T::Header& inputHdr)
{
m_box.dummy.setOutputChunkSize(0, 0, true);
chk.m_StartTime = inputHdr.m_StartTime + m_offset;
chk.m_EndTime = inputHdr.m_EndTime + m_offset;
encodeHeaderImpl(inputHdr);
fillChunk(chk);
return true;
}
bool encodeBuffer(EncodedChunk& chk, const typename T::Buffer& inputBuf)
{
m_box.dummy.setOutputChunkSize(0, 0, true);
chk.m_StartTime = inputBuf.m_StartTime + m_offset;
chk.m_EndTime = inputBuf.m_EndTime + m_offset;
encodeBufferImpl(inputBuf);
fillChunk(chk);
return true;
}
bool encodeEnd(EncodedChunk& chk, const typename T::End& inputEnd)
{
m_box.dummy.setOutputChunkSize(0, 0, true);
chk.m_StartTime = inputEnd.m_StartTime + m_offset;
chk.m_EndTime = inputEnd.m_EndTime + m_offset;
encodeEndImpl(inputEnd);
fillChunk(chk);
return true;
}
protected:
// These two must be written as specializations (Codec*cpp)
bool encodeHeaderImpl(const typename T::Header& hdr);
bool encodeBufferImpl(const typename T::Buffer& buf);
bool encodeEndImpl(const typename T::End& /*end*/) { return m_impl.encodeEnd(); }
bool fillChunk(EncodedChunk& chk)
{
const size_t chunkSize = size_t(m_box.dummy.getOutputChunkSize(0));
chk.m_Buffer.resize(chunkSize);
if (chunkSize > 0) { memcpy(&chk.m_Buffer[0], m_box.dummy.getOutputChunkBuffer(0), chunkSize); }
return true;
}
BoxAlgorithmProxy m_box;
TToolkitEncoder m_impl;
CTime m_offset = CTime::min();
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,219 @@
#pragma once
#include "CTracker.h"
#include "Workspace.h"
#include "Stream.h"
#include "TypeSignal.h"
#include "TypeSpectrum.h"
#include "TypeStimulation.h"
#include "Contexted.h"
#include "CLogListenerTracker.h"
// Forward declare
struct _GtkBuilder;
typedef struct _GtkWidget GtkWidget;
// typedef struct _GtkTable GtkTable;
namespace OpenViBE {
namespace Tracker {
class StreamRendererBase;
/**
* \class GUI
* \brief The main GUI code for the OpenViBE Tracker.
* \author J. T. Lindgren
*
* \details
* This file handles most of the GTK related GUI of the Tracker, with the actual functions modifying the data trying to be as much as possible
* implemented in the non-GUI classes. Although the GUI is currently quite monolithic, the rendering of the different Stream types has been
* delegated to StreamRenderer* classes, which in turn use the Mensia Advanced Visualizations toolkit. Code from Designer is also used by
* making CMake include it.
*
*/
class GUI final : protected Contexted
{
public:
GUI(int argc, char* argv[], CTracker& app);
~GUI() override;
bool run();
/**
* \class GUITrack
* \brief Holds the renderers for each stream of a Track, and the correspondig GTK table for the widgets.
* \author J. T. Lindgren
*/
class GUITrack
{
public:
GUITrack() { }
~GUITrack();
GtkWidget* m_Frame = nullptr;
// Renderer per stream
std::vector<StreamRendererBase*> m_Renderers;
};
protected:
// The main loop to do work, e.g. feed the tracks to processor
bool step();
CString getWorkspaceInfo() const;
bool addTracksToMenu();
// Enable/disable certain widgets during play (processing)
bool setPlaytimeWidgetState(bool enabled);
// Enable/disable certain widgets depending on the current state (streams, processors, etc)
bool updateIdleWidgetState();
bool storeRendererSettings();
// Callbacks
bool addTrackCB();
bool openWorkspaceCB();
bool openProcessorCB() const;
bool openProcessorCBFinal();
bool saveAsCB();
bool saveCB();
bool incrementRevSaveCB();
bool clearCB();
bool quitCB();
bool stopCB() const;
bool playCB();
bool playFastCB();
bool workspaceInfoCB() const;
bool aboutCB() const;
bool selectWorkspacePathCB() const;
bool selectWorkspacePathCBFinal() const;
bool editSelectionCB();
bool editSelectionCB2() { return editSelectionCB(); }
bool selectAllCB();
bool selectNoneCB();
bool processorPreferencesCB() const;
bool processorPreferencesButtonOkCB();
bool processorPreferencesButtonCancelCB() const;
bool processorEditCB() const;
bool processorEditCB2() const;
bool editNotesCB() const;
bool editNotesCB2() const { return editNotesCB(); }
bool hScrollCB(GtkWidget* widget);
bool hScaleCB(GtkWidget* widget);
bool deleteAllTracksCB();
bool deleteTrackCB(GtkWidget* widget);
bool moveTrackCB(GtkWidget* widget);
bool toggleRulerCB(GtkWidget* widget);
bool toggleShowSelectedOnlyCB(GtkWidget* widget);
static bool showChunksCB(GtkWidget* widget);
bool moveStreamCB(GtkWidget* widget);
bool deleteStreamCB(GtkWidget* widget);
bool applyBoxPluginCB(GtkWidget* widget);
bool trackerPluginCB(GtkWidget* widget);
bool setSelectionCB(GtkWidget* widget);
bool clearMessagesCB() const;
// Render a stream by pushing the chunks in view to the renderer
template <typename T, typename TRenderer>
bool draw(const Stream<T>* stream, TRenderer& renderer, CTime startTime, CTime endTime)
{
renderer.reset();
typename T::Buffer* buf;
// Push the visible chunks to renderer
uint64_t chunksSent = 0;
for (size_t i = 0; i < stream->getChunkCount(); ++i)
{
if (stream->getChunk(i, &buf) && buf->m_StartTime >= startTime && buf->m_EndTime <= endTime)
{
renderer.push(*buf);
chunksSent++;
}
}
/*
CTime viewDuration = (endTime - startTime).ceil();
uint64_t chunkCount = viewDuration /renderer.getChunkDuration();
std::cout << "View duration " << viewDuration.toSeconds() << ", chks " << chunkCount << "\n";
std::cout << "Pushed " << chunksSent << " chks, padding " << chunkCount-chunksSent << " -> " << chunkCount << "\n";
if (buf && chunksSent < chunkCount)
{
while (chunksSent < chunkCount)
{
// @fixme push empty
renderer.push(*buf, true);
chunksSent++;
}
}
*/
renderer.finalize();
return true;
}
bool redrawTrack(size_t index);
bool redrawAllTracks();
bool redrawStream(size_t trackIndex, size_t streamIndex);
bool resetWidgetProperties();
bool initializeRenderers();
bool clearRenderers();
bool updateRulerState();
CTracker& m_tracker;
std::vector<GUITrack*> m_tracks; // One per each track in workspace
struct _GtkBuilder* m_interface = nullptr;
GtkWidget* m_mainWindow = nullptr;
GtkWidget* m_scrollbar = nullptr;
GtkWidget* m_scale = nullptr;
GtkWidget* m_workspaceLabel = nullptr;
GtkWidget* m_timeDisplay = nullptr;
GtkWidget* m_trackCounter = nullptr;
GtkWidget* m_selectionWindow = nullptr;
uint64_t m_numChannels = 0;
uint64_t m_chunkSize = 0;
uint64_t m_totalChunks = 0;
CTime m_previousTime = CTime::min();
CTime m_lastRedraw = CTime::min();
#if defined(TARGET_OS_Windows)
int m_requestResetInNFrames = -1;
bool m_expanderOpen = false;
double m_hScrollBarValue = 0;
#endif
bool m_requestRedraw = false;
bool m_waitingForStop = false;
CLogListenerTracker* m_logListener = nullptr;
// This hack is needed since on Ubuntu 16.04, for some reason
// gtk_widget_set_sensitive doesn't work on main level menu items,
// and descending the menu as a container doesn't seem to allow
// graying out the menu items either
std::vector<GtkWidget*> m_trackMenuWidgets;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,79 @@
#pragma once
#include <openvibe/ov_all.h>
#include "Contexted.h"
#include "StreamBundle.h"
#include "Workspace.h"
#include "Selection.h"
#include "ParallelExecutor.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class ITrackerPlugin
* \brief Brief
* \author J. T. Lindgren
* \details
*
* Tracker Plugins are processing plugins specific to the OpenViBE Tracker.
* The idea is to allow plugins to access the whole track content (StreamBundle)
* with a simple interface and minimal overhead.
*
* In detail, the difference between Tracker Plugins and Box Plugins is as follows.
* A box plugin wraps openvibe box code, and subsequently the code will have
* to deal with the classical openvibe objects such as encoders, decoders, and so on.
* The Tracker Plugin, on the other hand, gives the programmer access
* to the StreamBundle structure. This allows the plugin code to request and manipulate
* the streams and their chunks freely. A tracker plugin can also add or remove streams
* to the bundle.
*
* Tracker plugins do not currently have explicit parameters, but you
* can pass parameters in using configuration tokens, and then request/set them
* via m_kernelCtx.getConfigurationManager() interface.
*
* Note that the StreamBundle given as input to a Tracker Plugin is in not
* safeguarded against "bad" modifications by the plugin.
*
* @todo tracker and box plugins might be refactorable to be under same interface.
* @todo allow non-inplace mode operation
* @todo derive different subclasses for the 'capabilities' instead of one monolithic interface?
*
*/
class ITrackerPlugin : protected Contexted
{
public:
// For the moment these are mutually exclusive capabilities; Tracker will prefer 'workspace' if the plugin supports it.
enum class ECapabilities
{
Tracks = 1LL,
Workspace = 2LL
};
// Constructor
explicit ITrackerPlugin(const Kernel::IKernelContext& ctx) : Contexted(ctx) { }
// @param track The input track. It can be modified by the process() call.
// @return True on success, False otherwise
// @note If the plugin changes the track, it must set the tracks 'dirty bit' as true.
virtual bool process(StreamBundle& /*track*/) { return false; }
// @param wp The input workspace. It can be modified by the process() call.
// @param exec A reference to a parallel executor that the plugin can use (if it prefers)
// @return True on success, False otherwise
// @note If the plugin changes any track in the workspace, it must set the tracks 'dirty bit' as true.
// @note Passing the parallel executor as a parameter is not too neat, ideally it'd be part of IKernelContext but that'd require importing the exec to SDK.
virtual bool process(Workspace& /*wp*/, ParallelExecutor& /*exec*/) { return false; }
// @param Capability to ask for
// @return True if the plugin supports this
// @note By default, the plugins are expected to have the track processing capability. If not, override this default.
virtual bool hasCapability(const ECapabilities capability) { return (capability == ECapabilities::Tracks); }
// @return the name of the plugin
virtual std::string getName() { return std::string("Unnamed"); }
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,102 @@
#pragma once
#include <map>
#include <list>
#include <deque>
#include <vector>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <thread>
/*
* Class allowing creating a set of worker threads that then pull jobs from a job list.
*
* Before this class can be used safely, various components in the OV SDK should be made
* thread safe. These are at least
* - LogManager
* - CKernelObjectFactory
* - AlgorithmManager?
* - ConfigurationManager? (if any plugin adds tokens)
*
*/
namespace OpenViBE {
namespace Tracker {
typedef std::function<void(size_t)> jobCall;
// Fwd declare
class CWorkerThread;
class ParallelExecutor
{
public:
ParallelExecutor() { }
bool initialize(const uint32_t nThreads);
bool uninitialize();
uint32_t getNumThreads() const { return m_threads.size(); }
// Clients call these
bool pushJob(const jobCall& someJob); // add job, pass to threads
bool pushJobList(const std::deque<jobCall>& vJobList); // add jobs
bool clearPendingJobs(); // Delete all jobs thath haven't been launched yet
bool waitForAll(); // wait until all pushed jobs are done
size_t getJobCount() const; // Number of remaining jobs
bool isIdle() const; // Nothing running & no tasks in list?
// Worker threads call these
bool getJob(jobCall& job); // The call will hang until there is a job available or the executor is told to quit
bool declareDone(); // Declare the previously obtained job as done
bool launchTest();
private:
mutable std::mutex m_jobMutex;
std::deque<jobCall> m_jobList;
uint32_t m_nJobsRunning = 0;
std::condition_variable m_haveWork;
std::condition_variable m_jobDone;
bool m_quit = false;
std::vector<CWorkerThread*> m_workerThreads;
std::vector<std::thread*> m_threads;
};
// Provides the worker threads a limited access to the parallel executor
class ExecutorView
{
public:
explicit ExecutorView(ParallelExecutor& exec) : m_exec(exec) { }
bool getJob(jobCall& job) const { return m_exec.getJob(job); }
bool declareDone() const { return m_exec.declareDone(); }
private:
ParallelExecutor& m_exec;
};
class CWorkerThread
{
public:
CWorkerThread() { }
static void run(const ExecutorView ctx, const uint32_t threadNumber)
{
while (true)
{
jobCall job;
if (!ctx.getJob(job)) { return; }
// @todo pass job failed to caller
job(threadNumber);
ctx.declareDone();
}
}
static void startWorkerThread(CWorkerThread* /*thread*/, const ExecutorView ctx, const uint32_t threadNumber) { run(ctx, threadNumber); }
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,141 @@
#pragma once
#include "StreamBundle.h"
#include <iostream>
#include <thread>
#include "Processor.h"
#include "ProcExternalProcessingHelper.h" // Push and Pull clients
#include "Encoder.h"
#include "Decoder.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class ProcExternalProcessing
* \brief A processor implemented by passing data to/from External Processing Boxes inserted into Designer scenarios
* \author J. T. Lindgren
*
*/
class ProcExternalProcessing final : public Processor
{
public:
explicit ProcExternalProcessing(const Kernel::IKernelContext& ctx) : Processor(ctx) {}
ProcExternalProcessing(const Kernel::IKernelContext& ctx, const ProcExternalProcessing& other);
bool initialize(const std::string& xmlFile) override;
bool uninitialize() override;
// Set targets for push and pull
bool setNewSource(StreamBundle* source, bool sendHeader, bool sendEnd) override;
bool setNewTarget(StreamBundle* target) override;
bool play(const bool playFast, const std::function<bool(CTime)>& quitCB) override { return play(playFast, quitCB, nullptr); }
// @param nextTrackFun Callback to request more data (used for catenate mode)
bool play(const bool playFast, const std::function<bool(CTime)>& quitCB, const std::function<bool()>& nextTrackFun);
// bool play(const bool playFast) override;
bool stop() override;
bool setProcessorPorts(const uint32_t sendPort, const uint32_t recvPort) override
{
m_sendPort = sendPort;
m_recvPort = recvPort;
return true;
}
bool getProcessorPorts(uint32_t& sendPort, uint32_t& recvPort) const override
{
sendPort = m_sendPort;
recvPort = m_recvPort;
return true;
}
bool setProcessorFlags(const bool noGUI, const bool doSend, const bool doReceive)
{
m_noGUI = noGUI;
m_doSend = doSend;
m_doReceive = doReceive;
return true;
}
bool getProcessorFlags(bool& noGUI, bool& doSend, bool& doReceive) const
{
noGUI = m_noGUI;
doSend = m_doSend;
doReceive = m_doReceive;
return true;
}
bool canPull() const { return m_doReceive; }
bool canPush() const { return m_doSend; }
CTime getCurrentTime() const override;
bool isSynced() const
{
return (m_doSend ? (m_pushStartTime != BufferedClient::CLIENT_NOT_STARTED) : true) &&
(m_doReceive ? (m_pullStartTime != BufferedClient::CLIENT_NOT_STARTED) : true);
}
bool isRunning() const override { return m_isRunning; }
// Serialize state to configuration manager
bool save() override;
bool load() override;
protected:
//bool connectClient(Communication::MessagingClient& client, uint32_t port);
// Send data to External Processing box
bool push();
// Receive data from External Processing box
bool pop();
// n.b. for each external processing box, we need to have a separate thread since we don't know for
// sure in which order OpenViBE will schedule their execution.
PushClient* m_pushClient = nullptr;
std::thread* m_pushClientThread = nullptr;
CTime m_pushStartTime = BufferedClient::CLIENT_NOT_STARTED;
PullClient* m_pullClient = nullptr;
std::thread* m_pullClientThread = nullptr;
CTime m_pullStartTime = BufferedClient::CLIENT_NOT_STARTED;
std::thread* m_playerThread = nullptr;
bool m_sentSomething = false;
bool m_isRunning = false;
uint64_t m_chunksSent = 0;
CTime m_pushLastTime = CTime::min();
CTime m_pullLastTime = CTime::min();
// if either port is zero, then the communication from the scenario is expected to flow to one direction only
uint32_t m_sendPort = 50011;
uint32_t m_recvPort = 50012;
bool m_doSend = true;
bool m_doReceive = true;
// Used in continuous playing mode to get the start offset for the next stream
CTime m_previousEnd = CTime::min();
bool m_requestNewOffset = false;
bool m_sendHeader = true;
bool m_sendEnd = true;
bool m_noGUI = true;
StreamBundle* m_src = nullptr;
StreamBundle* m_dst = nullptr;
std::vector<EncoderBase*> m_encoders;
std::vector<DecoderBase*> m_decoders;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,122 @@
#pragma once
#include <communication/ovCMessagingClient.h>
#include <deque>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include "EncodedChunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class BufferedClient
* \brief A messaging client base class thats associated with a buffer of chunks.
* \author J. T. Lindgren
* \details
*
* The class is threaded. The thread connects to a single instance of OpenViBE's
* External Processing Box that is assumed to be monitoring a specific port.
*
* The derived clients are one-directional: they only send or receive data.
*
*/
class BufferedClient : protected Communication::MessagingClient
{
public:
explicit BufferedClient(const uint32_t port) : m_port(port) { }
void requestQuit();
bool hasQuit();
void start();
CTime getStartTime()
{
// @todo might be a bit inefficient to have mutex here
std::lock_guard<std::mutex> oLock(m_threadMutex);
return m_startTime;
}
uint64_t getTime() override { return MessagingClient::getTime(); }
static const CTime CLIENT_NOT_STARTED;
protected:
bool connectClient();
// Derived classes implement this to do either push or pull
virtual bool step() = 0;
uint32_t m_port = 0;
// The mutex is used with the variables declared after it
std::mutex m_threadMutex;
std::condition_variable m_bufferCondition;
std::deque<EncodedChunk*> m_buffer;
bool m_pleaseQuit = false;
bool m_hasQuit = false;
// Time when the client has connected and synced, CTime(-1) if not yet
CTime m_startTime = CLIENT_NOT_STARTED;
};
/**
* \class PushClient
* \brief A client dedicated to pushing data towards an External Processing Box
* \author J. T. Lindgren
*
*/
class PushClient final : public BufferedClient
{
public:
explicit PushClient(const uint32_t port) : BufferedClient(port) { }
// Append a chunk to be sent out
bool pushBuffer(const EncodedChunk& encodedChunk);
// Force all pushed chunks to be sent
void requestFlush();
protected:
// implements push
bool step() override;
// Under the base class' mutex
bool m_pleaseFlush = false;
};
/**
* \class PullClient
* \brief A class dedicated to pulling data from an External Processing Box
* \author J. T. Lindgren
*
*/
class PullClient final : public BufferedClient
{
public:
explicit PullClient(const uint32_t port) : BufferedClient(port) { }
// Get the oldest chunk received. Returns false if none.
bool pullBuffer(EncodedChunk& chunk);
bool isEndReceived() override { return BufferedClient::isEndReceived(); }
protected:
// implements pull
bool step() override;
// Keep polling the sender for chunks as long as there is one available
bool popMessagesToBuffer();
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,76 @@
#pragma once
#include <openvibe/ov_all.h>
#include <functional>
#include "StreamBundle.h"
#include "Contexted.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class Processor
* \author J. T. Lindgren
* \brief A signal processing component that can receive/return data
* \details
*
* Processor in OpenViBE tracker is a kind of bridge to Designer that is
* used to send data in, do some processing with a scenario, and get the processed
* data back. A processor can also be one-directional.
*
*
*/
class Processor : protected Contexted
{
public:
explicit Processor(const Kernel::IKernelContext& ctx) : Contexted(ctx) {}
// Set the processor XML file
virtual bool initialize(const std::string& xmlFile) = 0;
virtual bool uninitialize() = 0;
// Get the processor XML filename
virtual const std::string& getFilename() const { return m_xmlFilename; }
// Connect the processor to a specific StreamBundle to read from
virtual bool setNewSource(StreamBundle* source, bool sendHeader, bool sendEnd) = 0;
// Connect the processor to a specific StreamBundle to write to
virtual bool setNewTarget(StreamBundle* target) = 0;
// Launch Designer to configure the processor
virtual bool configure(const char* filename); // If filename is NULL, use internal
// Launch the Player
virtual bool play(const bool playFast, const std::function<bool(CTime)>& quitCallback) = 0;
// Stop the Player
virtual bool stop() = 0;
virtual bool isRunning() const = 0;
virtual CTime getCurrentTime() const = 0;
// Communication port tcp/ip port IDs
virtual bool setProcessorPorts(const uint32_t sendPort, const uint32_t recvPort) = 0;
virtual bool getProcessorPorts(uint32_t& sendPort, uint32_t& recvPort) const = 0;
// Command line arguments passed to the processor (Designer) launch call
bool setArguments(const char* args)
{
m_arguments = std::string(args);
return true;
}
const char* getArguments() const { return m_arguments.c_str(); }
// Serialize state to configuration manager
virtual bool save() = 0;
virtual bool load() = 0;
protected:
std::string m_xmlFilename;
std::string m_arguments;
bool m_playFast = false;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,51 @@
//
// OpenViBE Tracker
//
#pragma once
#include <string>
#include <vector>
#include "StreamBundle.h"
#include "Contexted.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class Selection
* \brief Some convenience functions for Track/Stream selections
* \author J. T. Lindgren
*
*/
class Selection final : protected Contexted
{
public:
Selection(const Kernel::IKernelContext& ctx, const std::vector<StreamBundle*>& tracks) : Contexted(ctx), m_tracks(tracks) { }
// Set the selection status of all streams on all tracks to the given state
bool reset(bool state) const;
// Is any stream selected on the track?
bool isTrackSelected(size_t track) const;
// Nothing selectable?
bool isEmpty() const;
// Is something currently selected?
bool isSomethingSelected() const;
// Are selections of different tracks compatible?
bool isSelectionConsistent() const;
// How many tracks have at least one stream selected?
size_t countSelectedTracks() const;
// How many streams does a current track have selected?
size_t countSelectedStreams(size_t trackIndex) const;
// Serialize state to ConfigurationManager
bool save(const char* prefix) const;
bool load(const char* prefix) const;
protected:
const std::vector<StreamBundle*>& m_tracks;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,20 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#pragma once
#include <memory>
#include "StreamBase.h"
namespace OpenViBE {
namespace Tracker {
// Returns a copy of a stream with stimulations suggesting end of stream dropped
std::shared_ptr<StreamBase> filterStimulationStreamEndPoints(const std::shared_ptr<const StreamBase>& src, const Kernel::IKernelContext& ctx);
// Returns a copy of a stream with given stimulations dropped
std::shared_ptr<StreamBase> filterStimulationStream(const std::shared_ptr<const StreamBase>& src, const Kernel::IKernelContext& ctx,
const std::vector<uint64_t>& stimsToFilter);
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,215 @@
#pragma once
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <ebml/IWriterHelper.h>
#include <ebml/IWriter.h>
#include <ebml/TWriterCallbackProxy.h>
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "StreamBase.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class Stream
* \brief A container class representing a stream of OpenViBE
* \author J. T. Lindgren
* \details
*
* Stream is basically a specific kind of time-ordered container of timestamped, typed elements.
*
* Unlike OpenViBE streams in general, the Stream content in Tracker is stored in memory unencoded,
* and hence if its content is written to an .ov file, passed to a box, or Designer, it must be encoded first.
*
* Stream must have a 'header' representing the stream parameters, a sequence of 'buffers'
* containing the data, and an 'end' indicating the end of the stream.
*
* Time ----------------------->
* 0
* "front" [header][chk1][chk2]....[end] "tail"
*
* The stream additionally has a 'position' that always points to one of the above elements
* and starts from front. Stepping the stream moves the stream position +1 chunk forward towards the tail.
*
* The stream API can be used in a way that chunks can be pushed
* to the tail while the position counter is advancing from the head towards
* the tail. Although this would allow interleaving pushes
* and reads, currently the Tracker works in a way that when a track is loaded
* or recorded, only push() operations are done until the finish. On the
* other hand, when the track is sent out, only peek() and step() are used to
* retrieve the chunks.
*
* Currently Tracker Plugins are allowed to access the tracks in a random access fashion,
* as well as the GUI.
*
*
*/
template <class T>
class Stream final : public StreamBase
{
public:
explicit Stream(const Kernel::IKernelContext& ctx) : StreamBase(ctx) { m_end.m_StartTime = m_end.m_EndTime = CTime::max(); }
~Stream() override
{
/* if(m_Header) { delete m_Header; } */
clear();
}
CIdentifier getTypeIdentifier() const override { return T::getTypeIdentifier(); }
// Note: There is no corresponding setters, use the non-const versions to modify the header
const typename T::Header& getHeader() const { return m_header; }
const typename T::End& getEnd() const { return m_end; }
typename T::Header& getHeader() { return m_header; }
typename T::End& getEnd() { return m_end; }
bool push(typename T::Buffer* chunk)
{
m_chunks.push_back(chunk);
return true;
}
// Return the timestamps of the current chunk
bool peek(CTime& startTime, CTime& endTime) const override { return peek(m_position, startTime, endTime); }
// Return timestamps of a specific chunk
bool peek(size_t index, CTime& startTime, CTime& endTime) const override
{
if (index == size_t(-1)) { startTime = endTime = 0; }
else if (index < m_chunks.size())
{
startTime = m_chunks[index]->m_StartTime;
endTime = m_chunks[index]->m_EndTime;
}
else if (index == m_chunks.size() && m_chunks.size() > 0)
{
startTime = m_chunks[index - 1]->m_EndTime;
endTime = m_chunks[index - 1]->m_EndTime;
}
else { return false; }
return true;
}
size_t getChunkCount() const override { return m_chunks.size(); }
bool getChunk(size_t idx, typename T::Buffer** ptr) const
{
if (idx < m_chunks.size())
{
*ptr = m_chunks[idx];
return true;
}
*ptr = nullptr;
return false;
}
const typename T::Buffer* getChunk(size_t idx) const
{
if (idx < m_chunks.size()) { return m_chunks[idx]; }
return nullptr;
}
bool clear() override
{
std::for_each(m_chunks.begin(), m_chunks.end(), [](typename T::Buffer* ptr) { delete ptr; });
m_chunks.clear();
m_position = size_t(-1);
return true;
}
CTime getDuration() const override
{
const size_t chunkCount = getChunkCount();
if (chunkCount == 0) { return CTime::min(); }
return m_chunks[chunkCount - 1]->m_EndTime;
}
CTime getStartTime() const override
{
const size_t chunkCount = getChunkCount();
if (chunkCount == 0) { return CTime::min(); }
return m_chunks[0]->m_StartTime;
}
// @fixme efficiency
bool getOverlapping() const override
{
for (size_t i = 1; i < m_chunks.size(); ++i) { if (m_chunks[i]->m_StartTime < m_chunks[i - 1]->m_EndTime) { return true; } }
return false;
}
// @fixme efficiency
bool getNoncontinuous() const override
{
for (size_t i = 1; i < m_chunks.size(); ++i) { if (m_chunks[i]->m_StartTime > m_chunks[i - 1]->m_EndTime) { return true; } }
return false;
}
// @fixme efficiency
uint64_t countChunks(CTime startTime, CTime endTime) const
{
// Count the chunks @fixme not very efficient
uint64_t chunkCount = 0;
for (size_t i = 0; i < m_chunks.size(); ++i)
{
if (m_chunks[i]->m_StartTime >= startTime)
{
if (m_chunks[i]->m_EndTime <= endTime) { chunkCount++; }
else { break; }
}
}
return chunkCount;
}
// Iterators and operators
typename std::vector<typename T::Buffer*>::iterator begin() { return m_chunks.begin(); }
typename std::vector<typename T::Buffer*>::iterator end() { return m_chunks.end(); }
typename T::Buffer& operator[](int idx) { return m_chunks[idx]; }
// @fixme
// bool setHeader(TypeError::Header *) { return true; };
// bool setBuffer(TypeError::Buffer *) { return true; };
// @fixme doesn't work yet
bool copy(const StreamBase& other) override
{
if (T::getTypeIdentifier() != other.getTypeIdentifier()) { return false; }
const Stream<T>& otherStream = reinterpret_cast<const Stream<T>&>(other);
m_chunks.resize(otherStream.getChunkCount(), nullptr);
// @fixme this can be implemented when CMatrix has a working copy/assignment operators
/*
m_Header = otherStream.getHeader();
for (size_t c = 0; c < otherStream.getChunkCount(); ++c)
{
m_Chunks[c] = new T::Buffer(otherStream.getChunk(c));
}
m_End = otherStream.getEnd();
*/
log() << Kernel::LogLevel_Error << "Unimplemented method\n";
return false;
}
protected:
typename T::Header m_header; // Header of a stream
std::vector<typename T::Buffer*> m_chunks; // Buffers
typename T::End m_end; // End of a stream
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,95 @@
#pragma once
#include <string>
#include <vector>
#include <memory> // std::shared_ptr
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "Contexted.h"
// #include "ovkTAttributable.h"
// class StreamBase : public OpenViBE::Kernel::TAttributable<Contexted> {
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamBase
* \brief Abstract, non-typed base class for Streams.
* \details For details, please see the type-specific derived class Stream<T>.
* \author J. T. Lindgren
*
*/
class StreamBase : protected Contexted
{
public:
explicit StreamBase(const Kernel::IKernelContext& ctx) : Contexted(ctx) { }
~StreamBase() override { }
virtual CIdentifier getTypeIdentifier() const = 0;
virtual bool peek(CTime& startTime, CTime& endTime) const = 0;
virtual bool peek(size_t index, CTime& startTime, CTime& endTime) const = 0;
virtual bool step()
{
if (m_position <= getChunkCount() || m_position == size_t(-1))
{
m_position++;
return true;
}
return false;
}
virtual bool reset()
{
m_position = size_t(-1);
return true;
}
virtual bool clear() = 0;
virtual size_t getChunkCount() const = 0;
virtual CTime getDuration() const = 0;
virtual CTime getStartTime() const = 0;
// Current play position
size_t getPosition() const { return m_position; }
bool setPosition(const size_t position)
{
m_position = position;
return true;
}
// Is the stream currently selected? (nb. this is not in the .ov file)
bool getSelected() const { return m_selected; }
bool setSelected(const bool newState)
{
m_selected = newState;
return true;
}
// Stream characteristics
virtual bool getOverlapping() const = 0;
virtual bool getNoncontinuous() const = 0;
virtual bool copy(const StreamBase& /*other*/) { return false; }
protected:
size_t m_position = size_t(-1); // -1 == beginning of the stream, header
// Is the stream currently selected?
bool m_selected = true;
};
typedef std::shared_ptr<StreamBase> StreamPtr;
typedef std::shared_ptr<const StreamBase> StreamPtrConst;
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,96 @@
//
// OpenViBE Tracker
//
#pragma once
#include <string>
#include <vector>
#include <memory> // shared_ptr
#include <openvibe/ov_all.h>
#include "StreamBase.h"
#include "Contexted.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamBundle
* \brief StreamBundle is a container of one or more typed streams. It corresponds to a 'track' in Tracker and can represent an .ov file.
* \details
*
* StreamBundle can be queried for streams in time order: getNextStream() call can be used to find out
* the stream which has the earliest chunk position pointer (in time). Stepping the stream may make some
* other stream to be returned on the next get call.
*
* \author J. T. Lindgren
*
*/
class StreamBundle final : protected Contexted
{
public:
explicit StreamBundle(const Kernel::IKernelContext& ctx) : Contexted(ctx) {}
// Copy everything, allocate new memory for content
bool deepCopy(const StreamBundle& other);
// Copy selected subset of streams from "other".
// Other will retain ownership of any internal pointers.
// Since the copy gets write access to content of other, "other" is not const.
bool copyFrom(StreamBundle& other);
bool initialize();
bool uninitialize();
// Rewind all streams
bool rewind();
// Returns the stream which has a position with the earliest beginning timestamp
StreamPtr getNextStream(size_t& index);
bool getNextStreamIndex(size_t& index) const;
size_t getNumStreams() const { return m_streams.size(); }
StreamPtrConst getStream(const size_t idx) const { return (idx < m_streams.size()) ? m_streams[idx] : nullptr; }
StreamPtr getStream(const size_t idx) { return (idx < m_streams.size()) ? m_streams[idx] : nullptr; }
std::vector<StreamPtr>& getAllStreams() { return m_streams; }
// A factory method that creates a stream of a specific type into the slot index
bool createStream(size_t index, const CIdentifier& typeID);
bool deleteStream(size_t index);
bool setStream(size_t index, const std::shared_ptr<StreamBase>& ptr);
bool swapStreams(size_t idx1, size_t idx2);
bool moveStream(size_t srcIdx, size_t dstIdx);
// Returns the duration of the longest stream in the bundle
CTime getMaxDuration() const;
// Have all streams ended in the bundle?
bool isFinished() const
{
size_t dummy;
return !getNextStreamIndex(dummy);
}
// The name of the .ov file this bundle corresponds to
const std::string& getSource() const { return m_source; }
void setSource(const std::string& src) { m_source = src; }
bool getDirtyBit() const { return m_dirty; }
void setDirtyBit(const bool newState) { m_dirty = newState; }
protected:
// As StreamBundles can consist of streams from diverse origins where some of them would
// like to pass the ownership and some to keep it, instead of writing messy ownership tracking code,
// we use shared pointers for streams.
std::vector<StreamPtr> m_streams;
std::string m_source; // Identifies the .ov file of the bundle on disk
bool m_dirty = true; // True if the stream has not been saved to disk after last modification
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,20 @@
//
// OpenViBE Tracker
//
//
// n.b. having this as a separate file so EBML/Demuxer dependencies do not get pulled into StreamBundle
//
#pragma once
#include <openvibe/ov_all.h>
#include "StreamBundle.h"
namespace OpenViBE {
namespace Tracker {
// These functions import/export stream bundles from .ov files
StreamBundle* readStreamBundleFromFile(const Kernel::IKernelContext& ctx, const char* filename, bool memorySaveMode);
bool saveStreamBundleToFile(const Kernel::IKernelContext& ctx, StreamBundle* track, const char* filename);
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,54 @@
//
// OpenViBE Tracker
//
#pragma once
#include "CodecsAll.h"
#include "Stream.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamFactory
* \brief A factory method returning an empty Stream object of the requested type
* \author J. T. Lindgren
*
*/
class StreamFactory
{
public:
// Factory
static StreamPtr getStream(const Kernel::IKernelContext& ctx, const CIdentifier& typeID)
{
std::shared_ptr<StreamBase> stream;
// @todo others
if (typeID == OVTK_TypeId_StreamedMatrix) { stream = std::make_shared<Stream<TypeMatrix>>(ctx); }
else if (typeID == OVTK_TypeId_Signal) { stream = std::make_shared<Stream<TypeSignal>>(ctx); }
else if (typeID == OVTK_TypeId_Stimulations) { stream = std::make_shared<Stream<TypeStimulation>>(ctx); }
else if (typeID == OVTK_TypeId_ExperimentInfo) { stream = std::make_shared<Stream<TypeExperimentInfo>>(ctx); }
else if (typeID == OV_TypeId_ChannelLocalisation
) { stream = std::make_shared<Stream<TypeChannelLocalization>>(ctx); } // bug in toolkit atm with OVTK_ define
else if (typeID == OV_TypeId_ChannelUnits) { stream = std::make_shared<Stream<TypeChannelUnits>>(ctx); } // bug in toolkit atm with OVTK_ define
else if (typeID == OVTK_TypeId_Spectrum) { stream = std::make_shared<Stream<TypeSpectrum>>(ctx); }
else if (typeID == OVTK_TypeId_FeatureVector) { stream = std::make_shared<Stream<TypeFeatureVector>>(ctx); }
else
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Warning: Unknown stream type "
<< ctx.getTypeManager().getTypeName(typeID) << " " << typeID.str() << "\n";
stream = std::make_shared<Stream<TypeError>>(ctx);
}
return stream;
}
// Prevent constructing
StreamFactory() = delete;
StreamFactory(const StreamFactory&) = delete;
StreamFactory(StreamFactory&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,248 @@
//
// OpenViBE Tracker
//
#pragma once
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <memory> // shared_ptr
#include <mensia/advanced-visualization.hpp>
#include <mTGtkGLWidget.hpp>
#include <m_GtkGL.hpp>
#include <mIRuler.hpp>
#include "Stream.h"
#include "Contexted.h"
typedef struct _GtkWidget GtkWidget;
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererBase
* \brief Abstract, non-typed class for visually rendering the Streams in the Tracker GUI
* \details See the derived, typed versions of the class
*
* \author J. T. Lindgren
*
*/
class StreamRendererBase : protected Contexted
{
public:
explicit StreamRendererBase(const Kernel::IKernelContext& ctx) : Contexted(ctx) { }
~StreamRendererBase() override { }
// Calls for API users
// Initialize the renderer
virtual bool initialize();
virtual bool uninitialize();
// Show the windows
virtual bool realize();
virtual bool setTitle(const char* title);
// Draw chunks in range [startTime,endTime]
virtual bool spool(const CTime startTime, const CTime endTime) = 0;
virtual CString renderAsText(const size_t indent = 0) const;
virtual bool showChunkList()
{
log() << Kernel::LogLevel_Warning << "Chunk listing not implemented for stream type (or unavailable in current Tracker mode)\n";
return true;
}
virtual CTime getChunkDuration() const { return m_chunkDuration; }
virtual bool setRulerVisibility(bool isVisible);
// Save/load settings as configuration manager tokens
// The tokens loaded/saved will have the given prefix
virtual bool restoreSettings(const std::string& prefix);
virtual bool storeSettings(const std::string& prefix);
GtkWidget* getWidget() const { return m_main; }
// GtkGlWidget calls the following
virtual bool redraw(bool bImmediate = false);
virtual bool reshape(uint32_t width, uint32_t height);
virtual bool draw();
virtual bool preDraw();
virtual bool postDraw();
virtual void drawLeft();
virtual void drawRight();
virtual void drawBottom();
virtual bool mouseButton(int x, int y, int button, int status);
virtual bool mouseMotion(int x, int y);
virtual bool keyboard(uint32_t /*width*/, uint32_t /*height*/, uint32_t /*value*/, bool /*unused*/)
{
std::cout << "keyb requested\n";
return true;
}
protected:
// Resets the renderer aperture (number of samples/chunks)
virtual bool reset(CTime startTime, CTime endTime) = 0;
virtual bool updateRulerVisibility();
// After pushing samples, request rebuild & redraw
virtual bool finalize();
CTime m_startTime = CTime::min();
CTime m_endTime = CTime::min();
CTime m_chunkDuration = CTime::min();
uint32_t m_width = 640;
uint32_t m_height = 480;
uint32_t m_textureID = 0;
bool m_rotate = false;
bool m_isScaleVisible = true;
typedef struct
{
float r = 0, g = 0, b = 0;
} color_t;
color_t m_color;
GtkWidget* m_viewport = nullptr;
GtkWidget* m_main = nullptr;
GtkWidget* m_top = nullptr;
GtkWidget* m_left = nullptr;
GtkWidget* m_right = nullptr;
GtkWidget* m_bottom = nullptr;
GtkWidget* m_cornerLeft = nullptr;
GtkWidget* m_cornerRight = nullptr;
std::string m_title;
std::vector<AdvancedVisualization::IRenderer*> m_renderers;
AdvancedVisualization::CRendererContext* m_rendererCtx = nullptr;
AdvancedVisualization::CRendererContext* m_subRendererCtx = nullptr;
AdvancedVisualization::TGtkGLWidget<StreamRendererBase> m_gtkGLWidget;
AdvancedVisualization::IRuler* m_ruler = nullptr;
// mouse related
std::map<int, int> m_buttons;
int m_mouseX = 0;
int m_mouseY = 0;
bool m_mouseInitialized = false;
};
/**
* \class spoolImpl
* \brief Push chunks in a specified interval to a renderer
* \details
*
* A generic template that is used internally by the classes derived from StreamRendererBase
* to implement spool(t1,t2) : pushing a [t1,t2] interval of chunks to the renderer.
*
* \author J. T. Lindgren
*
*/
template <typename T, typename TRenderer>
bool spoolImpl(std::shared_ptr<const Stream<T>> stream, TRenderer& renderer, CTime startTime, CTime endTime)
{
renderer.reset(startTime, endTime);
typename T::Buffer* buf;
// Push the visible chunks to renderer
uint64_t chunksSent = 0;
for (size_t i = 0; i < stream->getChunkCount(); ++i)
{
if (stream->getChunk(i, &buf) && buf->m_StartTime >= startTime && buf->m_EndTime <= endTime)
{
renderer.push(*buf);
chunksSent++;
}
}
renderer.finalize();
return true;
}
// Helper function for showMatrixList(), adds a column to a tree view, @fixme move somewhere else
void add_column(GtkTreeView* treeView, const char* name, uint32_t id, uint32_t minWidth);
/**
* \class showMatrixList
* \brief Generic template that can be used to draw lists of any stream that derives from the matrix type.
* @fixme might refactor somewhere else
* \author J. T. Lindgren
*
*/
template <typename T>
bool showMatrixList(std::shared_ptr<const Stream<T>> stream, GtkWidget** listWindow, const char* title)
{
if (*listWindow)
{
gtk_window_present(GTK_WINDOW(*listWindow));
return true;
}
GtkBuilder* builder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/tracker.ui";
if (!gtk_builder_add_from_file(builder, filename, nullptr))
{
std::cout << "Problem loading [" << filename << "]\n";
return false;
}
*listWindow = GTK_WIDGET(gtk_builder_get_object(builder, "tracker-stimulation_list"));
GtkTreeView* treeView = GTK_TREE_VIEW(gtk_builder_get_object(builder, "tracker-stimulation_list-treeview"));
// GtkListStore* listStore = GTK_LIST_STORE(gtk_builder_get_object(pBuilder, "liststore_select"));
GtkTreeStore* treeStore = gtk_tree_store_new(4, G_TYPE_UINT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_STRING);
gtk_window_set_title(GTK_WINDOW(*listWindow), title);
add_column(treeView, "Chunk#", 0, 10);
add_column(treeView, "ChunkStart (s)", 1, 5);
add_column(treeView, "ChunkEnd (s)", 2, 5);
add_column(treeView, "Chunk dims", 3, 10);
gtk_tree_view_set_model(treeView, GTK_TREE_MODEL(treeStore));
GtkTreeIter it;
gtk_tree_store_clear(treeStore);
// ::gtk_tree_view_set_model(m_pChannelTreeView, nullptr);
for (size_t i = 0; i < stream->getChunkCount(); ++i)
{
const typename T::Buffer* ptr = stream->getChunk(i);
if (!ptr) { break; }
std::stringstream ss;
ss << ptr->m_buffer.getDimensionCount() << " : ";
for (uint32_t j = 0; j < ptr->m_buffer.getDimensionCount(); ++j) { ss << (j > 0 ? " x " : "") << ptr->m_buffer.getDimensionSize(j); }
gtk_tree_store_append(treeStore, &it, nullptr);
::gtk_tree_store_set(treeStore, &it, 0, i, 1, ptr->m_StartTime.toSeconds(), 2, ptr->m_EndTime.toSeconds(), 3, ss.str().c_str(), -1);
}
// Hide instead of destroy on closing the window
g_signal_connect(*listWindow, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
gtk_widget_show_all(GTK_WIDGET(*listWindow));
g_object_unref(builder);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,39 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererLabel.h"
#include "TypeChannelLocalization.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererChannelLocalization
* \brief Renderer for Channel Localization streams
* \author J. T. Lindgren
*
* @ fixme this class inherits a lot of junk from the root class which is not initialized properly and
* hence cannot be used. but if just the interface below is used, its alright. Can't make base class
* private as we store the renderer as a pointer to the base.
*
*/
class StreamRendererChannelLocalization final : public StreamRendererLabel
{
public:
StreamRendererChannelLocalization(const Kernel::IKernelContext& ctx, const std::shared_ptr<const Stream<TypeChannelLocalization>>& stream)
: StreamRendererLabel(ctx), m_Stream(stream) { }
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
std::shared_ptr<const Stream<TypeChannelLocalization>> m_Stream;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,37 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererLabel.h"
#include "TypeChannelUnits.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererChannelUnits
* \brief Renderer for Channel Units streams
* \author J. T. Lindgren
*
* @ fixme this class inherits a lot of junk from the root class which is not initialized properly and
* hence cannot be used. but if just the interface below is used, its alright. Can't make base class
* private as we store the renderer as a pointer to the base.
*
*/
class StreamRendererChannelUnits final : public StreamRendererLabel
{
public:
StreamRendererChannelUnits(const Kernel::IKernelContext& ctx, const std::shared_ptr<const Stream<TypeChannelUnits>>& stream)
: StreamRendererLabel(ctx), m_stream(stream) { }
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
std::shared_ptr<const Stream<TypeChannelUnits>> m_stream;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,38 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererLabel.h"
#include "TypeExperimentInfo.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererExperimentInfo
* \brief Renderer for Experiment Information streams
* \author J. T. Lindgren
*
* @ fixme this class inherits a lot of junk from the root class which is not initialized properly and
* hence cannot be used. but if just the interface below is used, its alright. Can't make base class
* private as we store the renderer as a pointer to the base.
*
*/
class StreamRendererExperimentInfo final : public StreamRendererLabel
{
public:
StreamRendererExperimentInfo(const Kernel::IKernelContext& ctx, const std::shared_ptr<const Stream<TypeExperimentInfo>>& stream)
: StreamRendererLabel(ctx), m_stream(stream) { }
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
std::shared_ptr<const Stream<TypeExperimentInfo>> m_stream;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,88 @@
//
// OpenViBE Tracker
//
#pragma once
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "StreamRendererMatrix.h"
#include "StreamRendererSignal.h"
#include "StreamRendererStimulation.h"
#include "StreamRendererSpectrum.h"
#include "StreamRendererExperimentInfo.h"
#include "StreamRendererLabel.h"
#include "StreamRendererChannelUnits.h"
#include "StreamRendererChannelLocalization.h"
#include "StreamRendererNothing.h"
#include "Stream.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererFactory
* \brief Factory method for getting Stream Renderers of different types
* \author J. T. Lindgren
*
*/
class StreamRendererFactory
{
public:
// Factory special case
static StreamRendererBase* getDummyRenderer(const Kernel::IKernelContext& ctx) { return new StreamRendererNothing(ctx); }
// Generic factory
static StreamRendererBase* getRenderer(const Kernel::IKernelContext& ctx, const StreamPtrConst& stream)
{
if (!stream) { ctx.getLogManager() << Kernel::LogLevel_Warning << "Nullptr stream, using label renderer\n"; }
const CIdentifier typeID = (stream ? stream->getTypeIdentifier() : CIdentifier::undefined());
StreamRendererBase* renderer;
if (typeID == OV_TypeId_Signal) { renderer = new StreamRendererSignal(ctx, std::static_pointer_cast<const Stream<TypeSignal>>(stream)); }
else if (typeID == OV_TypeId_Spectrum) { renderer = new StreamRendererSpectrum(ctx, std::static_pointer_cast<const Stream<TypeSpectrum>>(stream)); }
else if (typeID == OV_TypeId_Stimulations)
{
// @todo should be pushed to all other renderers
renderer = new StreamRendererStimulation(ctx, std::static_pointer_cast<const Stream<TypeStimulation>>(stream));
}
else if (typeID == OV_TypeId_StreamedMatrix) { renderer = new StreamRendererMatrix(ctx, std::static_pointer_cast<const Stream<TypeMatrix>>(stream)); }
else if (typeID == OV_TypeId_FeatureVector)
{
// Since featurevector is just a constrained matrix, we use the matrix renderer
renderer = new StreamRendererMatrix(ctx, std::static_pointer_cast<const Stream<TypeMatrix>>(stream));
}
else if (typeID == OV_TypeId_ExperimentInfo)
{
// Since featurevector is just a constrained matrix, we use the matrix renderer
renderer = new StreamRendererExperimentInfo(ctx, std::static_pointer_cast<const Stream<TypeExperimentInfo>>(stream));
}
else if (typeID == OV_TypeId_ChannelUnits)
{
renderer = new StreamRendererChannelUnits(ctx, std::static_pointer_cast<const Stream<TypeChannelUnits>>(stream));
}
else if (typeID == OV_TypeId_ChannelLocalisation)
{
renderer = new StreamRendererChannelLocalization(ctx, std::static_pointer_cast<const Stream<TypeChannelLocalization>>(stream));
}
else
{
ctx.getLogManager() << Kernel::LogLevel_Trace << "Using label renderer for stream of type " << ctx.getTypeManager().getTypeName(typeID) << "\n";
renderer = new StreamRendererLabel(ctx);
}
return renderer;
}
// Prevent constructing
StreamRendererFactory() = delete;
StreamRendererFactory(const StreamRendererFactory&) = delete;
StreamRendererFactory(StreamRendererFactory&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,45 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererBase.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererLabel
* \brief Renderer for any stream that is simply visualized as a labeled widget
* \author J. T. Lindgren
*
* @ fixme this class inherits a lot of junk from the base class which is not initialized properly and
* hence cannot be used. but if just the interface below is used, its alright. Can't make base class
* private as we store the renderer as a pointer to the base.
*
*/
class StreamRendererLabel : public StreamRendererBase
{
public:
explicit StreamRendererLabel(const Kernel::IKernelContext& ctx) : StreamRendererBase(ctx) { }
bool initialize() override;
bool uninitialize() override { return true; }
bool setTitle(const char* title) override;
bool spool(const CTime /*startTime*/, const CTime /*endTime*/) override { return true; }
bool setRulerVisibility(bool /*isVisible*/) override { return true; }
virtual bool showChunkList(const char* title);
protected:
bool reset(CTime /*startTime*/, CTime /*endTime*/) override { return true; }
bool realize() override { return true; }
GtkWidget* m_label = nullptr;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,56 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererBase.h"
#include "TypeMatrix.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererMatrix
* \brief Renderer for Matrix streams
* \author J. T. Lindgren
*
*/
class StreamRendererMatrix final : public StreamRendererBase
{
public:
StreamRendererMatrix(const Kernel::IKernelContext& ctx, std::shared_ptr<const Stream<TypeMatrix>> stream)
: StreamRendererBase(ctx), m_stream(stream) { }
bool initialize() override;
bool spool(const CTime startTime, const CTime endTime) override { return spoolImpl<TypeMatrix, StreamRendererMatrix>(m_stream, *this, startTime, endTime); }
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
friend bool spoolImpl<TypeMatrix, StreamRendererMatrix>(std::shared_ptr<const Stream<TypeMatrix>> stream, StreamRendererMatrix& renderer, CTime startTime,
CTime endTime);
bool finalize() override;
bool reset(CTime startTime, CTime endTime) override;
bool push(const TypeMatrix::Buffer& chunk, bool zeroInput = false);
bool mouseButton(int x, int y, int button, int status) override;
bool preDraw() override;
bool draw() override;
size_t m_nRows = 0;
size_t m_nCols = 0;
std::shared_ptr<const Stream<TypeMatrix>> m_stream;
GtkWidget* m_streamListWindow = nullptr;
std::vector<float> m_swap;
StreamRendererMatrix() = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,48 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererBase.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererNothing
* \brief Renders nothing, to avoid null ptrs when we don't want even a label rendered
* \author J. T. Lindgren
*
* @ fixme this class inherits a lot of junk from the base class which is not initialized properly and
* hence cannot be used. but if just the interface below is used, its alright. Can't make base class
* private as we store the renderer as a pointer to the base.
*
*/
class StreamRendererNothing final : public StreamRendererBase
{
public:
explicit StreamRendererNothing(const Kernel::IKernelContext& ctx) : StreamRendererBase(ctx) { }
bool initialize() override { return true; }
bool uninitialize() override { return true; }
bool setTitle(const char* /*title*/) override { return true; }
bool spool(const CTime /*startTime*/, const CTime /*endTime*/) override { return true; }
bool setRulerVisibility(bool /*isVisible*/) override { return true; }
bool showChunkList() override { return true; }
CString renderAsText(const size_t indent) const override
{
return (std::string(indent, ' ') + "Stream not selected and hence has no renderer in current mode\n").c_str();
}
protected:
bool reset(CTime /*startTime*/, CTime /*endTime*/) override { return true; }
bool realize() override { return true; }
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,50 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererBase.h"
#include "TypeSignal.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererSignal
* \brief Renderer for Signal streams
* \author J. T. Lindgren
*
*/
class StreamRendererSignal final : public StreamRendererBase
{
public:
StreamRendererSignal(const Kernel::IKernelContext& ctx, std::shared_ptr<const Stream<TypeSignal>> stream)
: StreamRendererBase(ctx), m_stream(stream) { }
bool initialize() override;
bool spool(const CTime startTime, const CTime endTime) override { return spoolImpl<TypeSignal, StreamRendererSignal>(m_stream, *this, startTime, endTime); }
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
friend bool spoolImpl<TypeSignal, StreamRendererSignal>(std::shared_ptr<const Stream<TypeSignal>> stream, StreamRendererSignal& renderer,
CTime startTime, CTime endTime);
bool push(const TypeSignal::Buffer& chunk, bool zeroInput = false);
bool reset(CTime startTime, CTime endTime) override;
bool mouseButton(int x, int y, int button, int status) override;
size_t m_nChannel = 0;
size_t m_samplesPerChunk = 0;
std::shared_ptr<const Stream<TypeSignal>> m_stream;
GtkWidget* m_streamListWindow = nullptr;
StreamRendererSignal() = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,59 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererBase.h"
#include "TypeSpectrum.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererSpectrum
* \brief Renderer for Spectrum streams
* \author J. T. Lindgren
*
*/
class StreamRendererSpectrum final : public StreamRendererBase
{
public:
StreamRendererSpectrum(const Kernel::IKernelContext& ctx, std::shared_ptr<const Stream<TypeSpectrum>> stream)
: StreamRendererBase(ctx), m_stream(stream) { }
bool initialize() override;
bool spool(const CTime startTime, const CTime endTime) override
{
return spoolImpl<TypeSpectrum, StreamRendererSpectrum>(m_stream, *this, startTime, endTime);
}
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
friend bool spoolImpl<TypeSpectrum, StreamRendererSpectrum>(std::shared_ptr<const Stream<TypeSpectrum>> stream, StreamRendererSpectrum& renderer,
CTime startTime, CTime endTime);
bool finalize() override;
bool reset(CTime startTime, CTime endTime) override;
bool push(const TypeSpectrum::Buffer& chunk, bool zeroInput = false);
bool mouseButton(int x, int y, int button, int status) override;
bool preDraw() override;
bool draw() override;
size_t m_nChannel = 0;
size_t m_spectrumElements = 0;
std::vector<float> m_swaps;
std::shared_ptr<const Stream<TypeSpectrum>> m_stream;
GtkWidget* m_streamListWindow = nullptr;
StreamRendererSpectrum() = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,57 @@
//
// OpenViBE Tracker
//
#pragma once
#include "StreamRendererBase.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class StreamRendererStimulation
* \brief Renderer for Stimulation streams
* \author J. T. Lindgren
*
*/
class StreamRendererStimulation final : public StreamRendererBase
{
public:
StreamRendererStimulation(const Kernel::IKernelContext& ctx, std::shared_ptr<const Stream<TypeStimulation>> stream)
: StreamRendererBase(ctx), m_stream(stream) { }
bool initialize() override;
bool spool(const CTime startTime, const CTime endTime) override
{
return spoolImpl<TypeStimulation, StreamRendererStimulation>(m_stream, *this, startTime, endTime);
}
CString renderAsText(const size_t indent) const override;
bool showChunkList() override;
protected:
friend bool spoolImpl<TypeStimulation, StreamRendererStimulation>(std::shared_ptr<const Stream<TypeStimulation>> stream,
StreamRendererStimulation& renderer, CTime startTime, CTime endTime);
bool push(const TypeStimulation::Buffer& chunk, bool zeroInput = false);
bool reset(CTime startTime, CTime endTime) override;
bool mouseButton(int x, int y, int button, int status) override;
size_t m_nChannel = 0;
size_t m_samplesPerChunk = 0;
uint32_t m_sampling = 0;
GtkWidget* m_stimulationListWindow = nullptr;
std::shared_ptr<const Stream<TypeStimulation>> m_stream;
StreamRendererStimulation() = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,23 @@
#pragma once
#include <openvibe/ov_all.h>
namespace OpenViBE {
namespace Tracker {
/**
* \class TestClass
* \brief Class to try out stuff in the Tracker
* \author J. T. Lindgren
*
*/
class TestClass
{
public:
explicit TestClass(Kernel::IKernelContext& ctx);
Kernel::IKernelContext& m_Ctx;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,23 @@
#pragma once
#include "ITrackerPlugin.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TrackerPluginChannelCheck
* \brief Example of a Tracker plugin processing given Workspaces. It checks if all selected signal streams in all tracks have the same number of channels.
* \author J. T. Lindgren
*
*/
class TrackerPluginChannelCheck final : public ITrackerPlugin
{
public:
explicit TrackerPluginChannelCheck(const Kernel::IKernelContext& ctx) : ITrackerPlugin(ctx) { }
bool process(Workspace& wp, ParallelExecutor& exec) override;
bool hasCapability(const ECapabilities capability) override { return (capability == ECapabilities::Workspace); }
std::string getName() override { return std::string("Example: Channel check"); }
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,22 @@
#pragma once
#include "ITrackerPlugin.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TrackerPluginCountStimulations
* \brief Example of a Tracker plugin that processes given tracks. It counts the occurrences of different stimulations in each stimulation stream.
* \author J. T. Lindgren
*
*/
class TrackerPluginCountStimulations final : public ITrackerPlugin
{
public:
explicit TrackerPluginCountStimulations(const Kernel::IKernelContext& ctx) : ITrackerPlugin(ctx) { }
bool process(StreamBundle& track) override;
std::string getName() override { return std::string("Example: Count stimulations"); }
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,42 @@
#pragma once
#include <openvibe/ov_all.h>
#include "ITrackerPlugin.h"
#include "Contexted.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TrackerPlugins
* \author J. T. Lindgren
* \brief Container class for currently registered Tracker plugins. These plugins get direct access to StreamBundles (i.e. full track content).
* \details See ITrackerPlugin.h
*
New Tracker Plugins can be added to the Tracker by registering them in the constructor of this class
*
*/
class TrackerPlugins final : protected Contexted
{
public:
explicit TrackerPlugins(const Kernel::IKernelContext& ctx);
~TrackerPlugins() override;
// Get a list of all registered plugins
const std::vector<ITrackerPlugin*>& getTrackerPlugins() const { return m_trackerPlugins; }
// Get a fresh plugin. The indexing is the same as in getTrackerPlugins() output
ITrackerPlugin* getPluginCopy(size_t index) const;
protected:
// List of currently registered plugins
std::vector<ITrackerPlugin*> m_trackerPlugins;
// List of calls to create new copies of the plugins
std::vector<std::function<ITrackerPlugin*()>> m_pluginCreateCalls;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,37 @@
#pragma once
#include <openvibe/ov_all.h>
#include "TypeMatrix.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeChannelLocalization
* \brief Abstact class defining chunk types for Channel Localization streams
* \author J. T. Lindgren
*
*/
class TypeChannelLocalization
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_ChannelLocalisation; }
class Header : public TypeMatrix::Header
{
public:
bool m_Dynamic = false;
};
class Buffer : public TypeMatrix::Buffer { }; // Payload
class End : public TypeMatrix::End { };
// Prevent constructing
TypeChannelLocalization() = delete;
TypeChannelLocalization(const TypeChannelLocalization&) = delete;
TypeChannelLocalization(TypeChannelLocalization&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,37 @@
#pragma once
#include <openvibe/ov_all.h>
#include "TypeMatrix.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeChannelUnits
* \brief Abstact class defining chunk types for Channel Units streams
* \author J. T. Lindgren
*
*/
class TypeChannelUnits
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_ChannelUnits; }
class Header : public TypeMatrix::Header
{
public:
bool m_Dynamic = false;
};
class Buffer : public TypeMatrix::Buffer { }; // Payload
class End : public TypeMatrix::End { };
// Prevent constructing
TypeChannelUnits() = delete;
TypeChannelUnits(const TypeChannelUnits&) = delete;
TypeChannelUnits(TypeChannelUnits&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,36 @@
#pragma once
#include <openvibe/ov_all.h>
#include "Chunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeError
* \brief Abstract class for error situations
* \details
*
* \author J. T. Lindgren
*
*/
class TypeError
{
public:
static CIdentifier getTypeIdentifier() { return CIdentifier::undefined(); }
class Header : public Chunk { };
class Buffer : public Chunk { };
class End : public Chunk { };
// Prevent constructing
TypeError() = delete;
TypeError(const TypeError&) = delete;
TypeError(TypeError&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,51 @@
#pragma once
#include <openvibe/ov_all.h>
#include "Chunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeExperimentInfo
* \brief Abstact class defining chunk types for Experiment Information streams
* \author J. T. Lindgren
*
*/
class TypeExperimentInfo
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_ExperimentInfo; }
class Header : public Chunk
{
public:
// Experiment
uint64_t m_ExperimentID = 0;
std::string m_ExperimentDate;
// Subject
uint64_t m_SubjectID = 0;
std::string m_SubjectName;
uint64_t m_SubjectAge = 0;
uint64_t m_SubjectGender = 0;
// Context
uint64_t m_LaboratoryID = 0;
std::string m_LaboratoryName;
uint64_t m_TechnicianID = 0;
std::string m_TechnicianName;
};
class Buffer : public Chunk { }; // Payload
class End : public Chunk { };
// Prevent constructing
TypeExperimentInfo() = delete;
TypeExperimentInfo(const TypeExperimentInfo&) = delete;
TypeExperimentInfo(TypeExperimentInfo&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,33 @@
#pragma once
#include <openvibe/ov_all.h>
#include "TypeMatrix.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeFeatureVector
* \brief Abstact class defining chunk types for Feature Vector streams
* \author J. T. Lindgren
*
*/
class TypeFeatureVector
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_FeatureVector; }
class Header : public TypeMatrix::Header { };
class Buffer : public TypeMatrix::Buffer { };
class End : public TypeMatrix::End { };
// Prevent constructing
TypeFeatureVector() = delete;
TypeFeatureVector(const TypeFeatureVector&) = delete;
TypeFeatureVector(TypeFeatureVector&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,53 @@
#pragma once
#include <openvibe/ov_all.h>
#include "Chunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeMatrix
* \brief Abstact class defining chunk types for Matrix streams
* \author J. T. Lindgren
*
*/
class TypeMatrix
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_StreamedMatrix; }
class Header : public Chunk
{
public:
// Header
CMatrix m_Header;
// Make sure not copied until we have proper implementations
Header() { }
Header& operator=(const Header& in) = delete;
Header(const Header& in) = delete;
};
class Buffer : public Chunk
{
public:
// Payload
CMatrix m_buffer;
// Make sure not copied until we have proper implementations
Buffer() { }
Buffer& operator=(const Buffer& in) = delete;
Buffer(const Buffer& in) = delete;
};
class End : public Chunk { };
// Prevent constructing
TypeMatrix() = delete;
TypeMatrix(const TypeMatrix&) = delete;
TypeMatrix(TypeMatrix&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,40 @@
#pragma once
#include <openvibe/ov_all.h>
#include "TypeMatrix.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeSignal
* \brief Abstact class defining chunk types for Signal streams
* \author J. T. Lindgren
*
*/
class TypeSignal
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_Signal; }
class Header : public TypeMatrix::Header
{
public:
// Header
uint64_t m_Sampling = 0;
};
class Buffer : public TypeMatrix::Buffer { };
class End : public TypeMatrix::End { };
// Prevent constructing
TypeSignal() = delete;
TypeSignal(const TypeSignal&) = delete;
TypeSignal(TypeSignal&&) = delete;
// class Decoder : public Toolkit::TSignalDecoder<BoxAlgorithmProxy> { };
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,43 @@
#pragma once
#include <openvibe/ov_all.h>
#include "TypeMatrix.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeSpectrum
* \brief Abstact class defining chunk types for Spectrum streams
* \author J. T. Lindgren
*
*/
class TypeSpectrum
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_Spectrum; }
class Header : public TypeMatrix::Header
{
public:
uint64_t m_Sampling = 0;
CMatrix m_Abscissas;
// Make sure not copied until we have proper implementations
Header() { }
Header& operator=(const Header& in) = delete;
Header(const Header& in) = delete;
};
class Buffer : public TypeMatrix::Buffer { };
class End : public TypeMatrix::End { };
// Prevent constructing
TypeSpectrum() = delete;
TypeSpectrum(const TypeSpectrum&) = delete;
TypeSpectrum(TypeSpectrum&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,43 @@
#pragma once
#include <openvibe/ov_all.h>
#include "Chunk.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class TypeStimulation
* \brief Abstact class defining chunk types for Stimulation streams
* \author J. T. Lindgren
*
*/
class TypeStimulation
{
public:
static CIdentifier getTypeIdentifier() { return OV_TypeId_Stimulations; }
class Header : public Chunk { };
class Buffer : public Chunk
{
public:
// Payload
CStimulationSet m_buffer;
// Make sure not copied until we have proper implementations
Buffer() { }
Buffer& operator=(const Buffer& in) = delete;
Buffer(const Buffer& in) = delete;
};
class End : public Chunk { };
// Prevent constructing
TypeStimulation() = delete;
TypeStimulation(const TypeStimulation&) = delete;
TypeStimulation(TypeStimulation&&) = delete;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,247 @@
#pragma once
#include "StreamBundle.h"
#include "ProcExternalProcessing.h"
#include <openvibe/ov_all.h>
#include "WorkspaceNotes.h"
#include "Selection.h"
#include "ParallelExecutor.h"
namespace OpenViBE {
namespace Tracker {
/**
* \class Workspace
* \brief Workspace is a set of Tracks (StreamBundles) that contain the .ov file content that the user wants to work with
* \author J. T. Lindgren
*
*/
class Workspace final : protected Contexted
{
public:
explicit Workspace(const Kernel::IKernelContext& ctx)
: Contexted(ctx), m_workspaceFile(""), m_workspacePath(""), m_processor(ctx), m_selection(ctx, m_tracks) { setUniqueWorkingPath(); }
~Workspace() override;
// Reset workspace to the state like it was after construction
bool clear();
// Manipulating tracks and streams currently in the workspace
bool addTrack(const char* filename); // Add an .ov file as a track
bool moveTrack(size_t sourceIdx, size_t targetIdx);
bool moveStream(size_t sourceTrack, size_t sourceStream, size_t targetTrack, size_t targetStream);
bool clearTracks();
bool removeTrack(size_t idx);
bool removeStream(size_t track, size_t stream);
size_t getNumTracks() const { return m_tracks.size(); }
size_t getNumTracksOnPlaylist() const { return m_playlist.size(); }
size_t getNumTracksDone() const { return m_tracksDone; }
bool reloadTrack(size_t index);
// Does not transfer pointer ownership
StreamBundle* getTrack(const size_t index)
{
std::unique_lock<std::mutex> (m_om_Mutex);
if (index >= m_tracks.size()) { return nullptr; }
return m_tracks[index];
}
// Transfers ownership of the pointer. Returns the index inserted to.
bool setTrack(const size_t index, StreamBundle* track)
{
std::unique_lock<std::mutex> (m_om_Mutex);
if (index >= m_tracks.size()) { m_tracks.resize(index + 1, nullptr); }
delete m_tracks[index];
m_tracks[index] = track;
return true;
}
bool play(const bool playFast);
bool stop(bool stopProcessor = true);
// Push the selected part of the tracks to processor
bool step();
// get selection. Since it returns a reference, to set notes, simply modify the ref'd obj content.
Selection& getSelection() { return m_selection; }
// Max duration of all tracks in the workspace, in fixed point seconds
CTime getMaxDuration() const;
CTime getProcessedTime() const;
CTime getPlaylistDuration() const { return m_playlistDuration; }
// Set output for chunks
// @todo refactor these to a getter/setter of a processor object?
bool setProcessor(const char* scenarioXml);
bool setProcessorFlags(bool noGUI, bool doSend, bool doReceive);
bool getProcessorFlags(bool& noGUI, bool& doSend, bool& doReceive) const;
const char* getProcessorFile() const { return m_processor.getFilename().c_str(); }
bool configureProcessor(const char* filename) { return m_processor.configure(filename); }
bool setProcessorPorts(uint32_t sendPort, uint32_t recvPort);
bool getProcessorPorts(uint32_t& sendPort, uint32_t& recvPort) const { return m_processor.getProcessorPorts(sendPort, recvPort); }
bool hasProcessor() const
{
const char* fn = getProcessorFile();
return (fn && fn[0] != 0);
}
CString getProcessorArguments() const { return m_processorArguments; }
bool setProcessorArguments(const CString& args)
{
m_processorArguments = args;
return true;
}
CString getWorkingPath() const { return m_workspacePath; }
bool setWorkingPath(const CString& path)
{
if (path.length() == 0)
{
log() << Kernel::LogLevel_Error << "Empty workspace path not allowed\n";
return false;
}
m_workspacePath = path;
return true;
}
// Creates a 'unique' working path and assigns it, but does not create the folder
bool setUniqueWorkingPath();
CString getFilename() const { return m_workspaceFile; }
bool setFilename(const CString& filename)
{
if (filename.length() == 0)
{
// unset
m_workspaceFile = CString("");
return true;
}
return this->save(filename);
}
// get workspace notes. Since it returns a reference, to set notes, simply modify the ref'd obj content.
Kernel::IComment& getNotes() { return m_notes; }
// @todo make generic property getter/setter?
bool getCatenateMode() const { return m_catenateMode; }
bool setCatenateMode(const bool newState)
{
m_catenateMode = newState;
return true;
}
// @fixme rework the setter to spool files to/from disk when mode changes
bool getMemorySaveMode() const { return m_memorySaveMode; }
bool setMemorySaveMode(bool active);
// Modify tracks in place?
bool getInplaceMode() const { return m_inplaceMode; }
bool setInplaceMode(const bool newState)
{
m_inplaceMode = newState;
return true;
}
uint64_t getRevision() const { return m_revision; }
//uint64_t getNumRevisions() const { return m_NumRevisions; }
// The processors can poll this now and then to find out if they should quit ahead of the file end
bool isQuitRequested() const
{
std::unique_lock<std::mutex> m_Mutex;
return m_pleaseQuit;
}
std::mutex& getMutex() const { return m_Mutex; }
// Clear configuration tokens with a given prefix
bool wipeConfigurationTokens(const std::string& prefix) const;
bool setParallelExecutor(ParallelExecutor* ptr)
{
m_executor = ptr;
return true;
}
// Save and load workspace
bool save(const CString& filename);
bool load(const CString& filename);
bool incrementRevisionAndSave(const CString& filename);
// get sorted workspace specific tokens
std::vector<std::pair<std::string, std::string>> getConfigurationTokens() const;
protected:
bool saveAll();
bool getNextTrack(size_t& nextTrack) const;
bool assemblePlaylist();
bool spoolRecordingToDisk(size_t trackIndex);
bool playReceiveOnly();
bool playCatenate();
bool playNormal();
// Assemble user-specified and generated arguments to the processor
std::string getProcessorArguments(size_t index);
// Generic mutex to protect access to certain variables from multiple threads
mutable std::mutex m_Mutex;
std::vector<StreamBundle*> m_tracks;
bool m_playFast = false;
bool m_catenateMode = false;
bool m_inplaceMode = false;
bool m_memorySaveMode = false;
bool m_pleaseQuit = false;
uint32_t m_tracksDone = 0;
CString m_workspaceFile;
CString m_workspacePath;
// Component to send and receive data to/from, actually just a store for settings
ProcExternalProcessing m_processor;
std::string m_processorFilename;
CString m_processorArguments;
typedef std::pair<StreamBundle*, CTime> SourceTimePair;
std::vector<SourceTimePair> m_playlist;
CTime m_playlistDuration = CTime::min();
// Text notes about the current workspace; defined as a class to be able to reuse Designer's CCommentEditorDialog
WorkspaceNotes m_notes;
// Which tracks/streams are currently selected to be processed?
Selection m_selection;
ParallelExecutor* m_executor = nullptr;
// uint64_t m_NumRevisions = 1;
uint64_t m_revision = 1;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,89 @@
#pragma once
#include <openvibe/ov_all.h>
#include <fs/Files.h>
namespace OpenViBE {
namespace Tracker {
/**
* \class WorkspaceNotes
* \brief An overly complicated wrapper for a text string.
* \author J. T. Lindgren
*
* This class is needed in order to use CCommentEditorDialog from Designer. The
* implementation is a quick hack. IDs and Attributable features are not supported at all.
*
*/
class WorkspaceNotes final : public Kernel::IComment
{
public:
CIdentifier getIdentifier() const override { return 0; }
CString getText() const override { return m_notes; }
bool setIdentifier(const CIdentifier& /*identifier*/) override { return true; }
bool setText(const CString& sText) override
{
m_notes = sText;
return true;
}
bool initializeFromExistingComment(const IComment& exisitingComment) override
{
m_notes = exisitingComment.getText();
return true;
}
// Attributable
bool addAttribute(const CIdentifier& /*attributeID*/, const CString& /*sAttributeValue*/) override { return true; }
bool removeAttribute(const CIdentifier& /*attributeID*/) override { return true; }
bool removeAllAttributes() override { return true; }
CString getAttributeValue(const CIdentifier& /*attributeID*/) const override { return CString(""); }
bool setAttributeValue(const CIdentifier& /*attributeID*/, const CString& /*sAttributeValue*/) override { return true; }
bool hasAttribute(const CIdentifier& /*attributeID*/) const override { return false; }
bool hasAttributes() const override { return false; }
CIdentifier getNextAttributeIdentifier(const CIdentifier& /*previousID*/) const override { return CIdentifier(); }
CIdentifier getClassIdentifier() const override { return CIdentifier(); }
// Methods specific to this derived class
// Save the notes
bool save(const CString& notesFile) const
{
std::ofstream output;
FS::Files::openOFStream(output, notesFile.toASCIIString());
if (!output.is_open() || output.bad()) { return false; }
output << m_notes.toASCIIString();
output.close();
return true;
}
// Load the notes
bool load(const CString& notesFile)
{
if (FS::Files::fileExists(notesFile.toASCIIString()))
{
std::ifstream input;
FS::Files::openIFStream(input, notesFile.toASCIIString());
if (input.is_open() && !input.bad())
{
const std::string str((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
m_notes = str.c_str();
input.close();
return true;
}
}
return false;
}
protected:
CString m_notes;
};
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,933 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.18"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="adjustment_data_scale">
<property name="upper">100000</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment_element_count">
<property name="lower">2</property>
<property name="upper">1024</property>
<property name="step_increment">1</property>
<property name="page_increment">25</property>
</object>
<object class="GtkAdjustment" id="adjustment_erp">
<property name="upper">1</property>
</object>
<object class="GtkAdjustment" id="adjustment_freq_band_max">
<property name="upper">8192</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment_freq_band_min">
<property name="upper">8192</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment_time_scale">
<property name="upper">10000000</property>
<property name="value">10</property>
<property name="step_increment">0.10000000000000001</property>
<property name="page_increment">1</property>
</object>
<object class="GtkImage" id="expander_sort_image_alpha">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-spell-check</property>
</object>
<object class="GtkImage" id="expander_sort_image_alpha1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-spell-check</property>
</object>
<object class="GtkImage" id="expander_sort_image_default">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-close</property>
</object>
<object class="GtkImage" id="expander_sort_image_default1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-close</property>
</object>
<object class="GtkImage" id="expander_sort_image_front_back">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-go-down</property>
</object>
<object class="GtkImage" id="expander_sort_image_front_back1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-go-down</property>
</object>
<object class="GtkImage" id="expander_sort_image_left_right">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-go-forward</property>
</object>
<object class="GtkImage" id="expander_sort_image_left_right1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-go-forward</property>
</object>
<object class="GtkImage" id="expander_sort_image_reversed">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-undo</property>
</object>
<object class="GtkImage" id="expander_sort_image_reversed1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-undo</property>
</object>
<object class="GtkListStore" id="liststore_select">
<columns>
<!-- column-name index -->
<column type="guint"/>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0">1</col>
<col id="1" translatable="yes">dummy</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore_select_broadmann">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">dummy</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore_select_neuro_1">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">dummy</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore_select_neuro_2">
<columns>
<!-- column-name name -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">dummy</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore_sort">
<columns>
<!-- column-name text -->
<column type="gchararray"/>
<!-- column-name icon -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Sort Channels...</col>
<col id="1" translatable="yes"></col>
</row>
<row>
<col id="0" translatable="yes">Default</col>
<col id="1" translatable="yes">gtk-close</col>
</row>
<row>
<col id="0" translatable="yes">Alphabetically</col>
<col id="1" translatable="yes">gtk-spell-check</col>
</row>
<row>
<col id="0" translatable="yes">Reversed</col>
<col id="1" translatable="yes">gtk-undo</col>
</row>
<row>
<col id="0" translatable="yes">Left to Right</col>
<col id="1" translatable="yes">gtk-go-back</col>
</row>
<row>
<col id="0" translatable="yes">Front to Bottom</col>
<col id="1" translatable="yes">gtk-go-down</col>
</row>
</data>
</object>
<object class="GtkWindow" id="toolbar-window">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Configure Settings...</property>
<property name="window_position">mouse</property>
<property name="type_hint">dialog</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">4</property>
<property name="spacing">4</property>
<child>
<object class="GtkExpander" id="expander_sort">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkTable" id="table_sort">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_columns">5</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="expander_sort_button_default">
<property name="label" translatable="yes">Default</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="image">expander_sort_image_default</property>
</object>
</child>
<child>
<object class="GtkButton" id="expander_sort_button_alpha">
<property name="label" translatable="yes">Alphabetically</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="image">expander_sort_image_alpha</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="expander_sort_button_reversed">
<property name="label" translatable="yes">Reversed</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="image">expander_sort_image_reversed</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="expander_sort_button_left_right">
<property name="label" translatable="yes">Left to Right</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="image">expander_sort_image_left_right</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="expander_sort_button_front_back">
<property name="label" translatable="yes">Front to Back</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="image">expander_sort_image_front_back</property>
</object>
<packing>
<property name="left_attach">4</property>
<property name="right_attach">5</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_sort_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sort Channels...</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander_select">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow_select">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="expander_select_treeview">
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">liststore_select</property>
<property name="rules_hint">True</property>
<property name="search_column">1</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn_channel">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Channel</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_channel_index">
<property name="width">32</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_channel_name"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_select_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Select Channels...</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander_select_sLORETA">
<property name="can_focus">True</property>
<child>
<object class="GtkHBox" id="hbox_select_region">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">4</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow_select_broadmann">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="expander_select_broadmann_treeview">
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">liststore_select_broadmann</property>
<property name="rules_hint">True</property>
<property name="search_column">1</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn_broadmann">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Broadmann Area</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_broadmann_name"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow_select_neuro_1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="expander_select_neuro_1_treeview">
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">liststore_select_neuro_1</property>
<property name="rules_hint">True</property>
<property name="search_column">1</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn_neuro_1">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Neuro Anatomical Region 1</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_neuro_1_name"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow_select_neuro_2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="expander_select_neuro_2_treeview">
<property name="height_request">240</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">liststore_select_neuro_2</property>
<property name="rules_hint">True</property>
<property name="search_column">1</property>
<property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn_neuro_2">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Neuro Anatomical Region 2</property>
<property name="clickable">True</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_neuro_2_name"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="expander_select_region_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Select Regions...</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkExpander" id="expander_freq_band">
<property name="can_focus">True</property>
<child>
<object class="GtkTable" id="table_freq_band">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="label_freq_band_min">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Lower Boundary :</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_freq_band_max">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Higher Boundary :</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_freq_band_min">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment_freq_band_min</property>
<property name="digits">3</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_freq_band_max">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment_freq_band_max</property>
<property name="digits">3</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label_freq_band">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Select Spectrum Boundaries...</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table_scales">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">2</property>
<property name="n_columns">3</property>
<child>
<object class="GtkVBox" id="vbox_time_scale">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label_time_scale">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Time Scale :</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_time_scale">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment_time_scale</property>
<property name="digits">3</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_show_scale">
<property name="label" translatable="yes">Show Scales</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_positive">
<property name="label" translatable="yes">Positive Data</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox_element_count">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label_element_count">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Element Count :</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_element_count">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment_element_count</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table_erp">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_columns">4</property>
<property name="column_spacing">4</property>
<child>
<object class="GtkButton" id="button_erp_play_pause">
<property name="label">gtk-media-play</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="x_options"></property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_erp_progress">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">0%</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkHScale" id="range_erp">
<property name="width_request">250</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment_erp</property>
<property name="digits">3</property>
<property name="draw_value">False</property>
<property name="value_pos">right</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_erp_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">ERP player :</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox_video_recording">
<property name="can_focus">False</property>
<property name="spacing">4</property>
<child>
<object class="GtkLabel" id="label_video_recording_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Video will save to :</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_video_recording_filename">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_video_recording">
<property name="label">gtk-media-record</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="window">
<property name="width_request">640</property>
<property name="height_request">480</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Test window</property>
<child>
<object class="GtkTable" id="table">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">3</property>
<child>
<object class="GtkDrawingArea" id="drawingarea_bottom">
<property name="height_request">32</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_corner_left">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options"></property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_corner_right">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options"></property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="drawingarea_right">
<property name="width_request">64</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="drawingarea_left">
<property name="width_request">64</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_top">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="right_attach">3</property>
<property name="x_options"></property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="viewport">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_SUBSTRUCTURE_MASK</property>
<property name="double_buffered">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

@@ -0,0 +1,858 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.1.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x000013ae, 0x000053d6)</Identifier>
<Name>Temporal Filter</Name>
<AlgorithmClassIdentifier>(0xb4f9d042, 0x9d79f2e5)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Input signal</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2f2c606c, 0x8512ed68)</TypeIdentifier>
<Name>Filter Method</Name>
<DefaultValue>Butterworth</DefaultValue>
<Value>Butterworth</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xfa20178e, 0x4cba62e9)</TypeIdentifier>
<Name>Filter Type</Name>
<DefaultValue>Band Pass</DefaultValue>
<Value>Band Pass</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Filter Order</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Low Cut-off Frequency (Hz)</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>High Cut-off Frequency (Hz)</Name>
<DefaultValue>40</DefaultValue>
<Value>2</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Band Pass Ripple (dB)</Name>
<DefaultValue>0.5</DefaultValue>
<Value>0.5</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>112</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>288</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x27a4ceec, 0x876d6384)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>6</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000020f0, 0x00006a2e)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>336</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000039e9, 0x000043de)</Identifier>
<Name>Timeout</Name>
<AlgorithmClassIdentifier>(0x24fcd292, 0x5c8f6aa8)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>Input Stream</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output Stimulations</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Timeout delay</Name>
<DefaultValue>5</DefaultValue>
<Value>5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Output Stimulation</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>272</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x1eaee00e, 0xdb05d34e)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000043a8, 0x000051c6)</Identifier>
<Name>Debug: Stims</Name>
<AlgorithmClassIdentifier>(0x65731e1d, 0x47de5276)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulation stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Information</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>112</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>608</Value>
</Attribute>
<Attribute>
<Identifier>(0x341d3912, 0x1478de86)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xf451ad91, 0x14c75f86)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005438, 0x0000400f)</Identifier>
<Name>To Tracker</Name>
<AlgorithmClassIdentifier>(0x15422959, 0x16304449)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>New input</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>New input(1)</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Launch third party program</Name>
<DefaultValue>true</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Executable path</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Arguments</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Port</Name>
<DefaultValue>0</DefaultValue>
<Value>${Tracker_Port_Receive}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Automatic connection identifier</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Custom connection identifier</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Incoming connection timeout</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Generator</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x17ee7c08, 0x94c14893)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>192</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>336</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x6410533a, 0x11ff9fa8)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>8</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005438, 0x00004010)</Identifier>
<Name>From Tracker</Name>
<AlgorithmClassIdentifier>(0x15422959, 0x16304449)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>New output</Name>
</Output>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>New output(1)</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Launch third party program</Name>
<DefaultValue>true</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Executable path</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Arguments</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Port</Name>
<DefaultValue>0</DefaultValue>
<Value>${Tracker_Port_Send}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Automatic connection identifier</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Custom connection identifier</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Incoming connection timeout</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Generator</Name>
<DefaultValue>false</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x17ee7c08, 0x94c14893)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>16</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>304</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x6410533a, 0x11ff9fa8)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>8</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005c5f, 0x0000103e)</Identifier>
<Name>Filtered signal</Name>
<AlgorithmClassIdentifier>(0x0055be5f, 0x087bdd12)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Data</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
<Input>
<TypeIdentifier>(0x6ab26b81, 0x0f8c02f3)</TypeIdentifier>
<Name>Channel Units</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x5de046a6, 0x086340aa)</TypeIdentifier>
<Name>Display Mode</Name>
<DefaultValue>Scan</DefaultValue>
<Value>Scan</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x33a30739, 0x00d5299b)</TypeIdentifier>
<Name>Auto vertical scale</Name>
<DefaultValue>Per channel</DefaultValue>
<Value>Global</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Scale refresh interval (secs)</Name>
<DefaultValue>5</DefaultValue>
<Value>5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Vertical Scale</Name>
<DefaultValue>100</DefaultValue>
<Value>2</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Vertical Offset</Name>
<DefaultValue>0</DefaultValue>
<Value>0</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Time Scale</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Bottom ruler</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Left ruler</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Multiview</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>192</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>192</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x92c056a7, 0x2dc71aff)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>3</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005dd2, 0x00007417)</Identifier>
<Name>Debug: Signals</Name>
<AlgorithmClassIdentifier>(0x0ed76695, 0x01a69cc3)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x434f6587, 0x2efd2b7e)</TypeIdentifier>
<Name>Spied EBML stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>EBML nodes description</Name>
<DefaultValue>${Path_Data}/plugins/tools/config-ebml-stream-spy.txt</DefaultValue>
<Value>${Path_Data}/plugins/tools/config-ebml-stream-spy.txt</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Information</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Expand binary blocks</Name>
<DefaultValue>false</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Number of values in expanded blocks</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>112</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>480</Value>
</Attribute>
<Attribute>
<Identifier>(0x341d3912, 0x1478de86)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x23ce0a10, 0xc2eabb47)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>4</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x000013b9, 0x0000017a)</Identifier>
<Source>
<BoxIdentifier>(0x000039e9, 0x000043de)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000020f0, 0x00006a2e)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00002203, 0x000031ea)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x00004010)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000043a8, 0x000051c6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00003091, 0x000011d5)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x00004010)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005c5f, 0x0000103e)</BoxIdentifier>
<BoxInputIndex>1</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x000042ec, 0x0000374f)</Identifier>
<Source>
<BoxIdentifier>(0x000013ae, 0x000053d6)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x000054e6, 0x00000d08)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x00004010)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxInputIndex>1</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x000057ce, 0x00000c0b)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x00004010)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000039e9, 0x000043de)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00005a57, 0x00005592)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x00004010)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000013ae, 0x000053d6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00005bab, 0x000064b9)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x00004010)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005dd2, 0x00007417)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00007a40, 0x00004a8b)</Identifier>
<Source>
<BoxIdentifier>(0x000013ae, 0x000053d6)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005c5f, 0x0000103e)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments>
<Comment>
<Identifier>(0x000021fb, 0x00001fe1)</Identifier>
<Text>This scenario illustrates receiving a signal
from the Tracker, filtering and displaying it,
and sending the filtered signal back
to the Tracker.
The filtered result should appear as a
new track in the Tracker workspace.</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>-16</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>64</Value>
</Attribute>
</Attributes>
</Comment>
<Comment>
<Identifier>(0x000021fb, 0x00001fe2)</Identifier>
<Text>It is important that the Tracker workspace
is configured to both send and receive when
this scenario is used as its processor.
Note that the number of output streams and their
order in the 'From Tracker' box must exactly
match the selected streams in the Tracker.</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>-16</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>256</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<Metadata>
<Entry>
<Identifier>(0x000000c2, 0x000011b4)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0x00005c5f, 0x0000103e)","childCount":0,"identifier":"(0x00001f6f, 0x000016eb)","parentIdentifier":"(0xffffffff, 0xffffffff)","type":3},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":320,"identifier":"(0x00003cf2, 0x00002d05)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":480},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x00007351, 0x00002b8e)","index":0,"name":"Default tab","parentIdentifier":"(0x00003cf2, 0x00002d05)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x0000355e, 0x00004491)","index":0,"name":"Empty","parentIdentifier":"(0x00007351, 0x00002b8e)","type":0}]</Data>
</Entry>
</Metadata>
<Attributes>
<Attribute>
<Identifier>(0x079d1f09, 0x667c7871)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x60bc58ab, 0x7a1b4d83)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x790d75b8, 0x3bb90c33)</Identifier>
<Value>Jussi T. Lindgren</Value>
</Attribute>
<Attribute>
<Identifier>(0x7a3a1558, 0xf12c63c2)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x84009d7c, 0x3c4e763b)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x8c1fc55b, 0x7b433dc2)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x9f5c4075, 0x4a0d3666)</Identifier>
<Value>Tracker bidir example</Value>
</Attribute>
<Attribute>
<Identifier>(0xf36a1567, 0xd13c53da)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xf6b2e3fa, 0x7bd43926)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xf8034a49, 0x8b3f37cc)</Identifier>
<Value>Inria</Value>
</Attribute>
</Attributes>
</OpenViBE-Scenario>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,701 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.1.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x000013ae, 0x000053d6)</Identifier>
<Name>Temporal Filter</Name>
<AlgorithmClassIdentifier>(0xb4f9d042, 0x9d79f2e5)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Input signal</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Output signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2f2c606c, 0x8512ed68)</TypeIdentifier>
<Name>Filter Method</Name>
<DefaultValue>Butterworth</DefaultValue>
<Value>Butterworth</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xfa20178e, 0x4cba62e9)</TypeIdentifier>
<Name>Filter Type</Name>
<DefaultValue>Band Pass</DefaultValue>
<Value>Band Pass</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Filter Order</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Low Cut-off Frequency (Hz)</Name>
<DefaultValue>1</DefaultValue>
<Value>1</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>High Cut-off Frequency (Hz)</Name>
<DefaultValue>40</DefaultValue>
<Value>40</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Band Pass Ripple (dB)</Name>
<DefaultValue>0.5</DefaultValue>
<Value>0.5</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>128</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>144</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x27a4ceec, 0x876d6384)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>6</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000020f0, 0x00006a2e)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>240</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>528</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000043a8, 0x000051c6)</Identifier>
<Name>Stims</Name>
<AlgorithmClassIdentifier>(0x65731e1d, 0x47de5276)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulation stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Information</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>224</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>352</Value>
</Attribute>
<Attribute>
<Identifier>(0x341d3912, 0x1478de86)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xf451ad91, 0x14c75f86)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005438, 0x0000400f)</Identifier>
<Name>External Processing</Name>
<AlgorithmClassIdentifier>(0x15422959, 0x16304449)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>New output</Name>
</Output>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>New output(1)</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Launch third party program</Name>
<DefaultValue>true</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Executable path</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Arguments</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Port</Name>
<DefaultValue>0</DefaultValue>
<Value>${Tracker_Port_Send}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Automatic connection identifier</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Custom connection identifier</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Incoming connection timeout</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Generator</Name>
<DefaultValue>false</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x17ee7c08, 0x94c14893)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>288</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x6410533a, 0x11ff9fa8)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>8</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005ae7, 0x00003098)</Identifier>
<Name>Timeout</Name>
<AlgorithmClassIdentifier>(0x24fcd292, 0x5c8f6aa8)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>Input Stream</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output Stimulations</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Timeout delay</Name>
<DefaultValue>5</DefaultValue>
<Value>3</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Output Stimulation</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>176</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>512</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x1eaee00e, 0xdb05d34e)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005c5f, 0x0000103e)</Identifier>
<Name>Signal display</Name>
<AlgorithmClassIdentifier>(0x0055be5f, 0x087bdd12)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Data</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
<Input>
<TypeIdentifier>(0x6ab26b81, 0x0f8c02f3)</TypeIdentifier>
<Name>Channel Units</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x5de046a6, 0x086340aa)</TypeIdentifier>
<Name>Display Mode</Name>
<DefaultValue>Scan</DefaultValue>
<Value>Scan</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x33a30739, 0x00d5299b)</TypeIdentifier>
<Name>Auto vertical scale</Name>
<DefaultValue>Per channel</DefaultValue>
<Value>Global</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Scale refresh interval (secs)</Name>
<DefaultValue>5</DefaultValue>
<Value>5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Vertical Scale</Name>
<DefaultValue>100</DefaultValue>
<Value>2</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Vertical Offset</Name>
<DefaultValue>0</DefaultValue>
<Value>0</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Time Scale</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Bottom ruler</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Left ruler</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Multiview</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>224</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>96</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x92c056a7, 0x2dc71aff)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>3</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005dd2, 0x00007417)</Identifier>
<Name>Signals</Name>
<AlgorithmClassIdentifier>(0x0ed76695, 0x01a69cc3)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x434f6587, 0x2efd2b7e)</TypeIdentifier>
<Name>Spied EBML stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>EBML nodes description</Name>
<DefaultValue>${Path_Data}/plugins/tools/config-ebml-stream-spy.txt</DefaultValue>
<Value>${Path_Data}/plugins/tools/config-ebml-stream-spy.txt</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Information</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Expand binary blocks</Name>
<DefaultValue>false</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Number of values in expanded blocks</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>224</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>224</Value>
</Attribute>
<Attribute>
<Identifier>(0x341d3912, 0x1478de86)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x23ce0a10, 0xc2eabb47)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>4</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00002634, 0x000066c2)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005dd2, 0x00007417)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00002895, 0x00007435)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000013ae, 0x000053d6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00003d33, 0x00004bba)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005c5f, 0x0000103e)</BoxIdentifier>
<BoxInputIndex>1</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x000046c0, 0x00004d90)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxOutputIndex>1</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000043a8, 0x000051c6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x000050d8, 0x000049e5)</Identifier>
<Source>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005ae7, 0x00003098)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00007a40, 0x00004a8b)</Identifier>
<Source>
<BoxIdentifier>(0x000013ae, 0x000053d6)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005c5f, 0x0000103e)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00007aee, 0x00005c93)</Identifier>
<Source>
<BoxIdentifier>(0x00005ae7, 0x00003098)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000020f0, 0x00006a2e)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments>
<Comment>
<Identifier>(0x00001912, 0x0000689c)</Identifier>
<Text>Example of a processor that receives data from the Tracker,
but does not send anything to it:
the processor works as a &lt;b&gt;Data Sink&lt;/b&gt;.
In this case, the tracker Workspace must be
configured to Send but not to Receive.
</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>-144</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>160</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<Metadata>
<Entry>
<Identifier>(0x000000c2, 0x000011b4)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0x00005c5f, 0x0000103e)","childCount":0,"identifier":"(0x00001f6f, 0x000016eb)","parentIdentifier":"(0xffffffff, 0xffffffff)","type":3},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":320,"identifier":"(0x00003cf2, 0x00002d05)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":480},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x00007351, 0x00002b8e)","index":0,"name":"Default tab","parentIdentifier":"(0x00003cf2, 0x00002d05)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x0000355e, 0x00004491)","index":0,"name":"Empty","parentIdentifier":"(0x00007351, 0x00002b8e)","type":0}]</Data>
</Entry>
</Metadata>
<Attributes>
<Attribute>
<Identifier>(0x079d1f09, 0x667c7871)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x60bc58ab, 0x7a1b4d83)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x790d75b8, 0x3bb90c33)</Identifier>
<Value>Jussi T. Lindgren</Value>
</Attribute>
<Attribute>
<Identifier>(0x7a3a1558, 0xf12c63c2)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x84009d7c, 0x3c4e763b)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x8c1fc55b, 0x7b433dc2)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x9f5c4075, 0x4a0d3666)</Identifier>
<Value>Tracker Sink example</Value>
</Attribute>
<Attribute>
<Identifier>(0xf36a1567, 0xd13c53da)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xf6b2e3fa, 0x7bd43926)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xf8034a49, 0x8b3f37cc)</Identifier>
<Value>Inria</Value>
</Attribute>
</Attributes>
</OpenViBE-Scenario>
@@ -0,0 +1,729 @@
<OpenViBE-Scenario>
<FormatVersion>2</FormatVersion>
<Creator>OpenViBE Designer</Creator>
<CreatorVersion>2.1.0</CreatorVersion>
<Settings></Settings>
<Inputs></Inputs>
<Outputs></Outputs>
<Boxes>
<Box>
<Identifier>(0x000020f0, 0x00006a2e)</Identifier>
<Name>Player Controller</Name>
<AlgorithmClassIdentifier>(0x5f426dce, 0x08456e13)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation name</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xcc14d8d6, 0xf27ecb73)</TypeIdentifier>
<Name>Action to perform</Name>
<DefaultValue>Pause</DefaultValue>
<Value>Stop</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>224</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>624</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x568d148e, 0x650792b3)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000039e9, 0x000043de)</Identifier>
<Name>Timeout</Name>
<AlgorithmClassIdentifier>(0x24fcd292, 0x5c8f6aa8)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x544a003e, 0x6dcba5f6)</TypeIdentifier>
<Name>Input Stream</Name>
</Input>
</Inputs>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Output Stimulations</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Timeout delay</Name>
<DefaultValue>5</DefaultValue>
<Value>5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Output Stimulation</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>160</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>624</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x1eaee00e, 0xdb05d34e)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x000043a8, 0x000051c6)</Identifier>
<Name>Stims</Name>
<AlgorithmClassIdentifier>(0x65731e1d, 0x47de5276)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulation stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Information</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>208</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>432</Value>
</Attribute>
<Attribute>
<Identifier>(0x341d3912, 0x1478de86)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0xf451ad91, 0x14c75f86)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005438, 0x0000400f)</Identifier>
<Name>External Processing</Name>
<AlgorithmClassIdentifier>(0x15422959, 0x16304449)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>New input</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>New input(1)</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Launch third party program</Name>
<DefaultValue>true</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>Executable path</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Arguments</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Port</Name>
<DefaultValue>0</DefaultValue>
<Value>${Tracker_Port_Receive}</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Automatic connection identifier</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x79a9edeb, 0x245d83fc)</TypeIdentifier>
<Name>Custom connection identifier</Name>
<DefaultValue></DefaultValue>
<Value></Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Incoming connection timeout</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Generator</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x17ee7c08, 0x94c14893)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>128</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>304</Value>
</Attribute>
<Attribute>
<Identifier>(0x30a4e5c9, 0x83502953)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x6410533a, 0x11ff9fa8)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x61d11811, 0x71e65362)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>8</Value>
</Attribute>
<Attribute>
<Identifier>(0xf191c1c8, 0xa0123976)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005c5f, 0x0000103e)</Identifier>
<Name>Signal display</Name>
<AlgorithmClassIdentifier>(0x0055be5f, 0x087bdd12)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Data</Name>
</Input>
<Input>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Stimulations</Name>
</Input>
<Input>
<TypeIdentifier>(0x6ab26b81, 0x0f8c02f3)</TypeIdentifier>
<Name>Channel Units</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x5de046a6, 0x086340aa)</TypeIdentifier>
<Name>Display Mode</Name>
<DefaultValue>Scan</DefaultValue>
<Value>Scan</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x33a30739, 0x00d5299b)</TypeIdentifier>
<Name>Auto vertical scale</Name>
<DefaultValue>Per channel</DefaultValue>
<Value>Global</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Scale refresh interval (secs)</Name>
<DefaultValue>5</DefaultValue>
<Value>5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Vertical Scale</Name>
<DefaultValue>100</DefaultValue>
<Value>2</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Vertical Offset</Name>
<DefaultValue>0</DefaultValue>
<Value>0</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Time Scale</Name>
<DefaultValue>10</DefaultValue>
<Value>10</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Bottom ruler</Name>
<DefaultValue>true</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Left ruler</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Multiview</Name>
<DefaultValue>false</DefaultValue>
<Value>false</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>128</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>464</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x92c056a7, 0x2dc71aff)</Value>
</Attribute>
<Attribute>
<Identifier>(0x527ad68d, 0x16d746a0)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>9</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>3</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005dd2, 0x00007417)</Identifier>
<Name>Signals</Name>
<AlgorithmClassIdentifier>(0x0ed76695, 0x01a69cc3)</AlgorithmClassIdentifier>
<Inputs>
<Input>
<TypeIdentifier>(0x434f6587, 0x2efd2b7e)</TypeIdentifier>
<Name>Spied EBML stream 1</Name>
</Input>
</Inputs>
<Settings>
<Setting>
<TypeIdentifier>(0x330306dd, 0x74a95f98)</TypeIdentifier>
<Name>EBML nodes description</Name>
<DefaultValue>${Path_Data}/plugins/tools/config-ebml-stream-spy.txt</DefaultValue>
<Value>${Path_Data}/plugins/tools/config-ebml-stream-spy.txt</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0xa88b3667, 0x0871638c)</TypeIdentifier>
<Name>Log level to use</Name>
<DefaultValue>Information</DefaultValue>
<Value>Information</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2cdb2f0b, 0x12f231ea)</TypeIdentifier>
<Name>Expand binary blocks</Name>
<DefaultValue>false</DefaultValue>
<Value>true</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Number of values in expanded blocks</Name>
<DefaultValue>4</DefaultValue>
<Value>4</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>208</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>304</Value>
</Attribute>
<Attribute>
<Identifier>(0x341d3912, 0x1478de86)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x23ce0a10, 0xc2eabb47)</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>4</Value>
</Attribute>
<Attribute>
<Identifier>(0xcfad85b0, 0x7c6d841c)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xfba64161, 0x65304e21)</Identifier>
<Value></Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00005f1a, 0x000008fb)</Identifier>
<Name>Noise generator</Name>
<AlgorithmClassIdentifier>(0x0e3929f1, 0x15af76b9)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x5ba36127, 0x195feae1)</TypeIdentifier>
<Name>Generated signal</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Channel count</Name>
<DefaultValue>4</DefaultValue>
<Value>5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Sampling frequency</Name>
<DefaultValue>512</DefaultValue>
<Value>256</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x007deef9, 0x2f3e95c6)</TypeIdentifier>
<Name>Generated epoch sample count</Name>
<DefaultValue>32</DefaultValue>
<Value>32</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2e85e95e, 0x8a1a8365)</TypeIdentifier>
<Name>Noise type</Name>
<DefaultValue>Uniform</DefaultValue>
<Value>Gaussian</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>256</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x0b214ed8, 0x1f9ad83a)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>4</Value>
</Attribute>
</Attributes>
</Box>
<Box>
<Identifier>(0x00006300, 0x00004eef)</Identifier>
<Name>Clock stimulator</Name>
<AlgorithmClassIdentifier>(0x4f756d3f, 0x29ff0b96)</AlgorithmClassIdentifier>
<Outputs>
<Output>
<TypeIdentifier>(0x6f752dd0, 0x082a321e)</TypeIdentifier>
<Name>Generated stimulations</Name>
</Output>
</Outputs>
<Settings>
<Setting>
<TypeIdentifier>(0x512a166f, 0x5c3ef83f)</TypeIdentifier>
<Name>Interstimulation interval (in sec)</Name>
<DefaultValue>1.0</DefaultValue>
<Value>0.5</Value>
<Modifiability>false</Modifiability>
</Setting>
<Setting>
<TypeIdentifier>(0x2c132d6e, 0x44ab0d97)</TypeIdentifier>
<Name>Stimulation</Name>
<DefaultValue>OVTK_StimulationId_Label_00</DefaultValue>
<Value>OVTK_StimulationId_Label_00</Value>
<Modifiability>false</Modifiability>
</Setting>
</Settings>
<Attributes>
<Attribute>
<Identifier>(0x1fa7a38f, 0x54edbe0b)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x207c9054, 0x3c841b63)</Identifier>
<Value>368</Value>
</Attribute>
<Attribute>
<Identifier>(0x4e7b798a, 0x183beafb)</Identifier>
<Value>(0x27b3ee3c, 0xc50527e6)</Value>
</Attribute>
<Attribute>
<Identifier>(0xc80ce8af, 0xf699f813)</Identifier>
<Value>1</Value>
</Attribute>
<Attribute>
<Identifier>(0xce18836a, 0x9c0eb403)</Identifier>
<Value>2</Value>
</Attribute>
</Attributes>
</Box>
</Boxes>
<Links>
<Link>
<Identifier>(0x00000ffc, 0x0000151f)</Identifier>
<Source>
<BoxIdentifier>(0x00006300, 0x00004eef)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005c5f, 0x0000103e)</BoxIdentifier>
<BoxInputIndex>1</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x000013b9, 0x0000017a)</Identifier>
<Source>
<BoxIdentifier>(0x000039e9, 0x000043de)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000020f0, 0x00006a2e)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00001de3, 0x00006da2)</Identifier>
<Source>
<BoxIdentifier>(0x00005f1a, 0x000008fb)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005dd2, 0x00007417)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00001eb4, 0x000017bd)</Identifier>
<Source>
<BoxIdentifier>(0x00006300, 0x00004eef)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxInputIndex>1</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x0000438a, 0x000026c2)</Identifier>
<Source>
<BoxIdentifier>(0x00005f1a, 0x000008fb)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005438, 0x0000400f)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x00005738, 0x00003141)</Identifier>
<Source>
<BoxIdentifier>(0x00005f1a, 0x000008fb)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x00005c5f, 0x0000103e)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
<Link>
<Identifier>(0x0000742a, 0x0000602a)</Identifier>
<Source>
<BoxIdentifier>(0x00006300, 0x00004eef)</BoxIdentifier>
<BoxOutputIndex>0</BoxOutputIndex>
</Source>
<Target>
<BoxIdentifier>(0x000043a8, 0x000051c6)</BoxIdentifier>
<BoxInputIndex>0</BoxInputIndex>
</Target>
</Link>
</Links>
<Comments>
<Comment>
<Identifier>(0x00001912, 0x0000689c)</Identifier>
<Text>Example of a processor that sends data to the Tracker,
but does not receive anything from it: the processor
works as a &lt;b&gt;Data Source&lt;/b&gt;.
The generated data should appear in the Tracker
as a new track.
In this case, the tracker Workspace must be
configured to Receive but not to Send.
</Text>
<Attributes>
<Attribute>
<Identifier>(0x473d9a43, 0x97fc0a97)</Identifier>
<Value>32</Value>
</Attribute>
<Attribute>
<Identifier>(0x7234b86b, 0x2b8651a5)</Identifier>
<Value>144</Value>
</Attribute>
</Attributes>
</Comment>
</Comments>
<Metadata>
<Entry>
<Identifier>(0x000000c2, 0x000011b4)</Identifier>
<Type>(0x3bcce5d2, 0x43f2d968)</Type>
<Data>[{"boxIdentifier":"(0x00005c5f, 0x0000103e)","childCount":0,"identifier":"(0x00001f6f, 0x000016eb)","parentIdentifier":"(0xffffffff, 0xffffffff)","type":3},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"height":320,"identifier":"(0x00003cf2, 0x00002d05)","name":"Default window","parentIdentifier":"(0xffffffff, 0xffffffff)","type":1,"width":480},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":1,"identifier":"(0x00007351, 0x00002b8e)","index":0,"name":"Default tab","parentIdentifier":"(0x00003cf2, 0x00002d05)","type":2},{"boxIdentifier":"(0xffffffff, 0xffffffff)","childCount":0,"identifier":"(0x0000355e, 0x00004491)","index":0,"name":"Empty","parentIdentifier":"(0x00007351, 0x00002b8e)","type":0}]</Data>
</Entry>
</Metadata>
<Attributes>
<Attribute>
<Identifier>(0x079d1f09, 0x667c7871)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x60bc58ab, 0x7a1b4d83)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x790d75b8, 0x3bb90c33)</Identifier>
<Value>Jussi T. Lindgren</Value>
</Attribute>
<Attribute>
<Identifier>(0x7a3a1558, 0xf12c63c2)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x84009d7c, 0x3c4e763b)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x8c1fc55b, 0x7b433dc2)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0x9f5c4075, 0x4a0d3666)</Identifier>
<Value>Tracker Source example</Value>
</Attribute>
<Attribute>
<Identifier>(0xf36a1567, 0xd13c53da)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xf6b2e3fa, 0x7bd43926)</Identifier>
<Value></Value>
</Attribute>
<Attribute>
<Identifier>(0xf8034a49, 0x8b3f37cc)</Identifier>
<Value>Inria</Value>
</Attribute>
</Attributes>
</OpenViBE-Scenario>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,213 @@
#include <openvibe/ov_all.h>
#include <fs/Files.h>
#include "BoxAdapter.h"
#include "CodecFactory.h"
namespace OpenViBE {
namespace Tracker {
bool BoxAdapter::initialize()
{
m_boxAlgorithm = m_kernelCtx.getPluginManager().createBoxAlgorithm(m_algorithmID, nullptr);
if (!m_boxAlgorithm)
{
log() << Kernel::LogLevel_Error << "Error: failed to create algorithm for id " << m_algorithmID.str() << "\n";
return false;
}
m_boxAlgorithmCtx.getTrackerBoxIO()->initialize(m_boxAlgorithmCtx.getStaticBoxContext());
return true;
}
bool BoxAdapter::uninitialize()
{
if (m_boxAlgorithm)
{
m_boxAlgorithm->uninitialize(m_boxAlgorithmCtx);
m_kernelCtx.getPluginManager().releasePluginObject(m_boxAlgorithm);
m_boxAlgorithm = nullptr;
}
return true;
}
BoxAdapterBundle::~BoxAdapterBundle()
{
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
}
bool BoxAdapterBundle::initialize()
{
if (!BoxAdapter::initialize()) { return false; }
Kernel::IBox* staticBoxContext = const_cast<Kernel::IBox*>(m_boxAlgorithmCtx.getStaticBoxContext());
if (m_src)
{
for (size_t i = 0; i < m_src->getNumStreams(); ++i)
{
// @fixme test boxes input support here
std::stringstream ss;
ss << "Stream" << i; // not really used since there's no GUI for the box
staticBoxContext->addInput(ss.str().c_str(), m_src->getStream(i)->getTypeIdentifier());
m_encoders.push_back(CodecFactory::getEncoder(m_kernelCtx, *m_src->getStream(i)));
if (m_dst)
{
m_dst->createStream(i, m_src->getStream(i)->getTypeIdentifier());
m_decoders.push_back(CodecFactory::getDecoder(m_kernelCtx, *m_dst->getStream(i)));
staticBoxContext->addOutput(ss.str().c_str(), m_dst->getStream(i)->getTypeIdentifier());
}
}
}
m_boxAlgorithm->initialize(m_boxAlgorithmCtx);
return true;
}
bool BoxAdapterBundle::spool(const bool /* verbose */)
{
Kernel::IBoxIO* boxCtx = const_cast<Kernel::IBoxIO*>(m_boxAlgorithmCtx.getDynamicBoxContext());
TrackerBoxIO* ioCtx = static_cast<TrackerBoxIO*>(boxCtx);
if (!m_src)
{
log() << Kernel::LogLevel_Error << "Error: box wrapper doesn't yet support processing without source\n";
return false;
}
m_src->rewind();
const uint32_t nOutput = m_boxAlgorithmCtx.getStaticBoxContext()->getOutputCount();
while (true)
{
size_t index;
StreamPtr stream = m_src->getNextStream(index);
if (!stream) { break; }
EncodedChunk chk;
EChunkType outputType;
m_encoders[index]->encode(chk, outputType);
ioCtx->addInputChunk(index, chk);
m_boxAlgorithm->process(m_boxAlgorithmCtx);
ioCtx->clearInputChunks();
if (m_dst)
{
for (size_t j = 0; j < nOutput; ++j)
{
if (ioCtx->isReadyToSend(j))
{
ioCtx->getOutputChunk(j, chk);
m_decoders[j]->decode(chk);
ioCtx->deprecateOutput(j);
}
}
}
stream->step();
}
m_src->rewind();
return true;
}
bool BoxAdapterStream::initialize()
{
if (!BoxAdapter::initialize()) { return false; }
Kernel::IBox* boxCtx = const_cast<Kernel::IBox*>(m_boxAlgorithmCtx.getStaticBoxContext());
CIdentifier typeID;
boxCtx->getInputType(0, typeID);
if (typeID != m_src->getTypeIdentifier())
{
log() << Kernel::LogLevel_Error << "Error: Box alg first input stream is wrong type\n";
return false;
}
boxCtx->getOutputType(0, typeID);
if (typeID != m_dst->getTypeIdentifier())
{
log() << Kernel::LogLevel_Error << "Error: Box alg first output stream is wrong type\n";
return false;
}
m_boxAlgorithm->initialize(m_boxAlgorithmCtx);
return true;
}
bool BoxAdapterStream::spool(const bool verbose)
{
Kernel::IBoxIO* boxCtx = const_cast<Kernel::IBoxIO*>(m_boxAlgorithmCtx.getDynamicBoxContext());
TrackerBoxIO* ioCtx = static_cast<TrackerBoxIO*>(boxCtx);
if (!m_src)
{
log() << Kernel::LogLevel_Error << "Error: box wrapper doesn't yet support processing without source\n";
return false;
}
m_src->reset();
EncoderBase* encoder = CodecFactory::getEncoder(m_kernelCtx, *m_src);
DecoderBase* decoder = CodecFactory::getDecoder(m_kernelCtx, *m_dst);
if (verbose) { log() << Kernel::LogLevel_Info; }
bool finished = false;
uint64_t cnt = 0;
while (!finished)
{
if (m_src)
{
EncodedChunk chk;
EChunkType outputType;
if (encoder->encode(chk, outputType))
{
// @fixme here we assume the box takes data in at the first slot
ioCtx->clearInputChunks();
ioCtx->addInputChunk(0, chk);
m_src->step();
}
else { finished = true; }
}
m_boxAlgorithm->process(m_boxAlgorithmCtx);
if (m_dst)
{
if (ioCtx->isReadyToSend(0))
{
EncodedChunk chk;
ioCtx->getOutputChunk(0, chk);
decoder->decode(chk);
ioCtx->deprecateOutput(0);
}
}
if (verbose && cnt++ % 100 == 0) { log() << "."; }
}
if (verbose) { log() << "\n"; }
m_src->reset();
ioCtx->clearInputChunks();
delete encoder;
delete decoder;
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,97 @@
#include "BoxPlugins.h"
#include "ovkCBoxProto.h"
namespace OpenViBE {
namespace Tracker {
#define OVP_ClassId_BoxAlgorithm_TemporalFilter CIdentifier(0xB4F9D042, 0x9D79F2E5)
#define OVP_ClassId_BoxAlgorithm_TemporalFilterDesc CIdentifier(0x7BF6BA62, 0xAF829A37)
#define OVP_ClassId_BoxAlgorithm_SignalResampling CIdentifier(0x0E923A5E, 0xDA474058)
#define OVP_ClassId_BoxAlgorithm_SignalResamplingDesc CIdentifier(0xA675A433, 0xC6690920)
#define OVP_ClassId_BoxAlgorithm_SimpleDSP CIdentifier(0x00E26FA1, 0x1DBAB1B2)
#define OVP_ClassId_BoxAlgorithm_SimpleDSPDesc CIdentifier(0x00C44BFE, 0x76C9269E)
#define OVP_ClassId_BoxAlgorithm_Crop CIdentifier(0x7F1A3002, 0x358117BA)
#define OVP_ClassId_BoxAlgorithm_CropDesc CIdentifier(0x64D619D7, 0x26CC42C9)
#define OVP_ClassId_BoxAlgorithm_SpatialFilter CIdentifier(0xDD332C6C, 0x195B4FD4)
#define OVP_ClassId_BoxAlgorithm_SpatialFilterDesc CIdentifier(0x72A01C92, 0xF8C1FA24)
#define OVP_ClassId_BoxAlgorithm_TimeBasedEpoching CIdentifier(0x00777FA0, 0x5DC3F560)
#define OVP_ClassId_BoxAlgorithm_TimeBasedEpochingDesc CIdentifier(0x00ABDABE, 0x41381683)
#define OVP_ClassId_BoxAlgorithm_StimulationFilter CIdentifier(0x02F96101, 0x5E647CB8)
#define OVP_ClassId_BoxAlgorithm_StimulationFilterDesc CIdentifier(0x4D2A23FC, 0x28191E18)
#define OVP_ClassId_FastICA CIdentifier(0x00649B6E, 0x6C88CD17)
#define OVP_ClassId_FastICADesc CIdentifier(0x00E9436C, 0x41C904CA)
#define OVP_ClassId_BoxAlgorithm_CommonAverageReference CIdentifier(0x009C0CE3, 0x6BDF71C3)
#define OVP_ClassId_BoxAlgorithm_CommonAverageReferenceDesc CIdentifier(0x0033EAF8, 0x09C65E4E)
#define OVP_ClassId_BoxAlgorithm_ChannelSelector CIdentifier(0x361722E8, 0x311574E8)
#define OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc CIdentifier(0x67633C1C, 0x0D610CD8)
#define OVP_ClassId_BoxAlgorithm_ChannelRename CIdentifier(0x1FE50479, 0x39040F40)
#define OVP_ClassId_BoxAlgorithm_ChannelRenameDesc CIdentifier(0x20EA1F00, 0x7AED5645)
#define OVP_ClassId_BoxAlgorithm_ReferenceChannel CIdentifier(0x444721AD, 0x78342CF5)
#define OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc CIdentifier(0x42856103, 0x45B125AD)
#define OVP_ClassId_BoxAlgorithm_FrequencyBandSelector CIdentifier(0x140C19C6, 0x4E6E187B)
#define OVP_ClassId_BoxAlgorithm_FrequencyBandSelectorDesc CIdentifier(0x13462C56, 0x794E3C07)
#include <algorithm>
BoxPlugins::BoxPlugins(const Kernel::IKernelContext& ctx) : Contexted(ctx)
{
// Register some boxes that can be used as filters
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_TemporalFilter, OVP_ClassId_BoxAlgorithm_TemporalFilterDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_SimpleDSP, OVP_ClassId_BoxAlgorithm_SimpleDSPDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_TimeBasedEpoching, OVP_ClassId_BoxAlgorithm_TimeBasedEpochingDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_Crop, OVP_ClassId_BoxAlgorithm_CropDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_SpatialFilter, OVP_ClassId_BoxAlgorithm_SpatialFilterDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_SignalResampling, OVP_ClassId_BoxAlgorithm_SignalResamplingDesc);
create(OV_TypeId_Signal, OVP_ClassId_FastICA, OVP_ClassId_FastICADesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_CommonAverageReference, OVP_ClassId_BoxAlgorithm_CommonAverageReferenceDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_ChannelSelector, OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_ChannelRename, OVP_ClassId_BoxAlgorithm_ChannelRenameDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_ReferenceChannel, OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc);
create(OV_TypeId_Spectrum, OVP_ClassId_BoxAlgorithm_FrequencyBandSelector, OVP_ClassId_BoxAlgorithm_FrequencyBandSelectorDesc);
create(OV_TypeId_Stimulations, OVP_ClassId_BoxAlgorithm_StimulationFilter, OVP_ClassId_BoxAlgorithm_StimulationFilterDesc);
std::sort(m_boxPlugins.begin(), m_boxPlugins.end(), [](BoxAdapterStream* a, BoxAdapterStream* b)
{
return (a->getBox().getName()) < (b->getBox().getName());
});
}
bool BoxPlugins::create(const CIdentifier& streamType, const CIdentifier& alg, const CIdentifier& desc)
{
BoxAdapterStream* ptr = new BoxAdapterStream(m_kernelCtx, alg);
// Initialize the context by polling the descriptor
const Plugins::IPluginObjectDesc* pod = getKernelContext().getPluginManager().getPluginObjectDesc(desc);
if (!pod)
{
log() << Kernel::LogLevel_Error << "Unable to load box algorithm " << alg.str() << "\n";
return false;
}
const Plugins::IBoxAlgorithmDesc* pBoxAlgorithmDescriptor = dynamic_cast<const Plugins::IBoxAlgorithmDesc*>(pod);
Kernel::CBoxProto boxProto(m_kernelCtx, ptr->getBox());
pBoxAlgorithmDescriptor->getBoxPrototype(boxProto);
// We need to force this so we don't launch the plugin on streams of other types, and not all boxes declare
// their capabilities. Here we assume that if the box is created for this stream type, it can support it.
boxProto.addInputSupport(streamType);
CIdentifier typeID;
ptr->getBox().getInputType(0, typeID);
if (typeID != streamType) { ptr->getBox().setInputType(0, streamType); }
ptr->getBox().getOutputType(0, typeID);
if (typeID != streamType) { ptr->getBox().setOutputType(0, streamType); }
const CString boxName = m_kernelCtx.getTypeManager().getTypeName(streamType) + CString(" : ") + pBoxAlgorithmDescriptor->getName();
ptr->getBox().setName(boxName);
m_boxPlugins.push_back(ptr);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,454 @@
//
// OpenViBE Tracker
//
// @todo catch way more errors, currently most return values are ignored by callers
// @todo Change error handling to use the macros introduced in certivibe
// @todo clean the code to be more conforming to coding rules
// @todo Implement vertical slider / scale
// @todo Fix time units
// @todo Add track renderer only when the track appears
//
// @todo add dataset manager (workspace == datasets)
// @todo enable a view where only 'current track' is shown and the rest are minimized (unloaded?)
// @todo add option to collapse tracks to take less visual space
// @todo add record button
// @todo implement undo
// @todo fix issues with empty tracks/streams and if the processing fails (e.g. incompatible processor)
// @todo fix creation of noncontinuous streams in the catenate mode e.g. by padding all source streams to equal length?
// @todo write some tutorial processors, e.g. erp analysis, file export ... ?
// @todo allow running several scenarios sequentially
// @todo selection could be saved in the .ovw file simply as track/stream tokens (small perf hit)?
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "CTracker.h"
#include "StreamBundleImportExport.h"
#include "Workspace.h"
namespace OpenViBE {
namespace Tracker {
CTracker::CTracker(const Kernel::IKernelContext& ctx)
: Contexted(ctx), m_workspace(ctx), m_boxPlugins(ctx), m_trackerPlugins(ctx)
{
// Default config load deferred until initialize() which can be called after booting the GUI
// --> so we can get the possible log messages to the GUI
}
CTracker::~CTracker()
{
const CString configFile = m_kernelCtx.getConfigurationManager().expand("${Path_UserData}/openvibe-tracker.conf");
saveConfig(configFile);
m_executor.uninitialize();
}
bool CTracker::initialize()
{
const CString configFile = m_kernelCtx.getConfigurationManager().expand("${Path_UserData}/openvibe-tracker.conf");
// n.b. Initing the executor is in loadConfig so its not done twice
m_workspace.setParallelExecutor(&m_executor);
return loadConfig(configFile);
}
bool CTracker::play(const bool playFast)
{
switch (m_state)
{
case EStates::Stopped:
if (!m_workspace.play(playFast))
{
log() << Kernel::LogLevel_Error << "Error: play failed\n";
return false;
}
m_state = EStates::Playing;
break;
case EStates::Playing:
m_state = EStates::Paused;
break;
case EStates::Paused:
m_state = EStates::Playing;
break;
default:
break;
}
return true;
}
bool CTracker::stop()
{
// Request workspace to stop, do not change state until it has stopped
return m_workspace.stop();
}
CTracker::EStates CTracker::step()
{
if (m_state == EStates::Playing) { if (!m_workspace.step()) { m_state = EStates::Stopping; } }
else if (m_state == EStates::Stopping) { if (m_executor.isIdle()) { m_state = EStates::Stopped; } }
else { System::Time::sleep(1); }
return m_state;
}
struct Workpackage
{
StreamBundle* source = nullptr;
std::mutex oMutex;
size_t trackIndex;
std::deque<size_t> streamsToProcess;
};
bool CTracker::applyBoxPlugin(const size_t index)
{
auto& plugins = getBoxPlugins().getBoxPlugins();
if (index >= plugins.size())
{
log() << Kernel::LogLevel_Error << "Plugin index exceeds plugin array size\n";
return false;
}
if (getWorkspace().getMemorySaveMode() && getWorkspace().getWorkingPath().length() == 0)
{
log() << Kernel::LogLevel_Error << "Memory save mode requires a workspace path\n";
return false;
}
const CTime startTime = CTime(System::Time::zgetTime());
BoxAdapterStream* box = plugins[index];
const CString name = box->getBox().getName();
log() << Kernel::LogLevel_Info << "Applying method " << name << " (" << (m_workspace.getInplaceMode() ? "Inplace" : "Normal") << " mode)\n";
for (size_t trackIndex = 0; trackIndex < getWorkspace().getNumTracks(); ++trackIndex)
{
Workpackage* wptr = new Workpackage;
Workspace& ws = getWorkspace();
StreamBundle* sourceTrack = ws.getTrack(trackIndex);
// Count
const size_t numStreams = ws.getTrack(trackIndex)->getNumStreams();
for (size_t j = 0; j < numStreams; ++j)
{
if (sourceTrack->getStream(j)->getSelected() && box->getBox().hasInputSupport(sourceTrack->getStream(j)->getTypeIdentifier()))
{
wptr->streamsToProcess.push_back(j);
}
}
const size_t nToProcess = wptr->streamsToProcess.size();
if (nToProcess == 0) { continue; }
wptr->trackIndex = trackIndex;
wptr->source = (m_workspace.getMemorySaveMode() ? nullptr : sourceTrack);
for (size_t j = 0; j < nToProcess; ++j)
{
const bool inplaceMode = m_workspace.getInplaceMode();
const bool multithread = (m_executor.getNumThreads() > 1);
const bool memorySave = m_workspace.getMemorySaveMode();
const Kernel::IKernelContext& ctx = getKernelContext();
auto job = [&ws, wptr, &ctx, box, multithread, inplaceMode, memorySave](uint32_t /*threadNumber*/)
{
size_t sourceStreamIndex;
{
std::unique_lock<std::mutex>(wptr->oMutex);
if (memorySave && !wptr->source)
{
wptr->source = readStreamBundleFromFile(ctx,
ws.getTrack(wptr->trackIndex)->getSource().c_str(), false);
}
sourceStreamIndex = wptr->streamsToProcess.front();
wptr->streamsToProcess.pop_front();
}
StreamBundle* sourceTrack = wptr->source;
const StreamPtr sourceStream = sourceTrack->getStream(sourceStreamIndex);
// process
if (!multithread)
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Processing track " << (wptr->trackIndex + 1) << " stream " << (sourceStreamIndex + 1) << "\n";
}
std::unique_lock<std::mutex>(wptr->oMutex);
const size_t targetStreamIndex = sourceTrack->getNumStreams();
sourceTrack->createStream(targetStreamIndex, sourceStream->getTypeIdentifier());
StreamPtr targetStream = sourceTrack->getStream(targetStreamIndex);
// We need a copy of the box since otherwise different threads init the same box differently
BoxAdapterStream* boxCopy = new BoxAdapterStream(ctx, box->getAlgorithmId());
boxCopy->getBox().initializeFromExistingBox(box->getBox());
boxCopy->setSource(sourceStream);
boxCopy->setTarget(targetStream);
if (!boxCopy->initialize())
{
std::unique_lock<std::mutex>(wptr->oMutex);
sourceTrack->deleteStream(targetStreamIndex);
delete boxCopy;
return;
}
boxCopy->spool(!multithread);
boxCopy->uninitialize();
delete boxCopy;
if (inplaceMode)
{
std::unique_lock<std::mutex>(wptr->oMutex);
sourceTrack->swapStreams(sourceStreamIndex, targetStreamIndex);
sourceTrack->deleteStream(targetStreamIndex);
}
else { targetStream->setSelected(false); }
// Spool resources to disk and free memory
{
std::unique_lock<std::mutex>(wptr->oMutex);
if (memorySave)
{
// Free resources if this thread is the last
if (wptr->streamsToProcess.empty())
{
if (wptr->source->getDirtyBit())
{
// @fixme not a good solution with the filenaming; rethink the whole thing
std::stringstream ss;
ss << ws.getWorkingPath() << "/workspace-track-" << (wptr->trackIndex + 1) << ".ov";
// In this mode, sourceTrack is not from the track array. We spool it to disk, then
// set the filename of the track in the array, and reload it back.
saveStreamBundleToFile(ctx, wptr->source, ss.str().c_str());
ws.getTrack(wptr->trackIndex)->setSource(ss.str());
ws.reloadTrack(wptr->trackIndex);
}
delete wptr->source;
}
}
if (wptr->streamsToProcess.empty()) { delete wptr; }
}
};
m_executor.pushJob(job);
if (!multithread) { m_executor.waitForAll(); }
}
}
m_executor.waitForAll();
// pBoxAlgorithmDescriptor->release();
const CTime elapsed = CTime(System::Time::zgetTime()) - startTime;
log() << Kernel::LogLevel_Info << "Applying plugin took " << elapsed.toSeconds() << " sec.\n";
return true;
}
bool CTracker::applyTrackerPlugin(const size_t index)
{
auto& plugins = getTrackerPlugins().getTrackerPlugins();
if (index >= plugins.size())
{
log() << Kernel::LogLevel_Error << "Plugin index exceeds plugin array size\n";
return false;
}
if (getWorkspace().getMemorySaveMode() && getWorkspace().getWorkingPath().length() == 0)
{
log() << Kernel::LogLevel_Error << "Memory save mode requires a workspace path\n";
return false;
}
ITrackerPlugin* plugin = plugins[index];
const CString name(plugin->getName().c_str());
log() << Kernel::LogLevel_Info << "Applying method " << name << " (" << (m_workspace.getInplaceMode() ? "Inplace" : "Normal") << " mode)\n";
bool retVal = true;
if (plugin->hasCapability(ITrackerPlugin::ECapabilities::Workspace))
{
retVal = plugin->process(getWorkspace(), m_executor);
if (!retVal) { log() << Kernel::LogLevel_Error << "Error processing workspace with the plugin\n"; }
}
else if (plugin->hasCapability(ITrackerPlugin::ECapabilities::Tracks))
{
// @note since the different jobs handle different tracks, we don't do locking here.
for (size_t trackIndex = 0; trackIndex < getWorkspace().getNumTracks(); ++trackIndex)
{
ITrackerPlugin* pluginCopy = getTrackerPlugins().getPluginCopy(index);
Workspace& ws = getWorkspace();
const Kernel::IKernelContext& ctx = getKernelContext();
auto job = [pluginCopy,&ws,&ctx,trackIndex](uint32_t /*threadNumber*/)
{
StreamBundle* sourceTrack;
if (ws.getMemorySaveMode())
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Loading " << ws.getTrack(trackIndex)->getSource().c_str()
<< " from file\n";
sourceTrack = readStreamBundleFromFile(ctx,
ws.getTrack(trackIndex)->getSource().c_str(), false);
}
else { sourceTrack = ws.getTrack(trackIndex); }
if (!pluginCopy->process(*sourceTrack))
{
// log() << Kernel::LogLevel_Error << "Error processing track << " << (i + 1) << " with the plugin\n";
// retVal = false;
}
delete pluginCopy;
if (ws.getMemorySaveMode())
{
if (sourceTrack->getDirtyBit())
{
// @fixme not a good solution with the filenaming; rethink the whole thing
std::stringstream ss;
ss << ws.getWorkingPath() << "/workspace-track-" << (trackIndex + 1) << ".ov";
// In this mode, sourceTrack is not from the track array. We spool it to disk, then
// set the filename of the track in the array, and reload it back.
saveStreamBundleToFile(ctx, sourceTrack, ss.str().c_str());
ws.getTrack(trackIndex)->setSource(ss.str());
ws.reloadTrack(trackIndex);
}
delete sourceTrack;
}
};
m_executor.pushJob(job);
// @todo copy sourcetrack, process the copy, if the processing is
// successful then replace the original (or not) depending on inplacemode setting
if (m_executor.getNumThreads() == 1) { m_executor.waitForAll(); }
}
// @todo in principle there's no need to freeze the GUI meanwhile, but allowing it to run in the bg would require locking
m_executor.waitForAll();
}
else { log() << Kernel::LogLevel_Error << "Plugin does not have any known capabilities and cannot be run.\n"; }
// pBoxAlgorithmDescriptor->release();
return retVal;
}
bool CTracker::setNumThreads(uint32_t numThreads)
{
if (numThreads != m_executor.getNumThreads())
{
if (numThreads < 1)
{
log() << Kernel::LogLevel_Warning << "Minimum number of threads is 1, setting that.\n";
numThreads = 1;
}
else if (numThreads > 1)
{
log() << Kernel::LogLevel_Info << "Using " << numThreads <<
" threads. Concurrency control has not been carefully tested. If you notice issues, switch to 1 thread.\n";
}
m_executor.uninitialize();
return m_executor.initialize(numThreads);
}
return true;
}
bool CTracker::loadConfig(const CString& filename)
{
if (!m_kernelCtx.getConfigurationManager().addConfigurationFromFile(filename))
{
m_executor.initialize(1);
return false;
}
const uint32_t numThreads = uint32_t(m_kernelCtx.getConfigurationManager().expandAsUInteger("${Tracker_NumThreads}", 1));
m_executor.initialize(numThreads);
// m_Executor.launchTest();
const CString workspaceFile = m_kernelCtx.getConfigurationManager().expand("${Tracker_Last_Workspace}");
//if (m_kernelCtx.getConfigurationManager().lookUpConfigurationTokenIdentifier("Tracker_Last_Workspace"))
// != CIdentifier::undefined())
//{
// m_Workspace.load(m_kernelCtx.getConfigurationManager().expand("${Tracker_Last_Workspace}");
// Tracker_Last_Workspace
// )
if (workspaceFile.length() != 0) { return m_workspace.load(workspaceFile.toASCIIString()); }
return false;
}
// @note : does not save the workspace itself
bool CTracker::saveConfig(const CString& filename) const
{
FILE* file = fopen(filename, "wt");
if (file)
{
fprintf(file, "# Configuration file for OpenViBE Tracker, autosaved on Tracker exit\n");
fprintf(file, "#\n");
fprintf(file, "\n");
fprintf(file, "# Last settings used in the Tracker\n");
fprintf(file, "Tracker_Last_Workspace = %s\n", m_workspace.getFilename().toASCIIString());
fprintf(file, "Tracker_NumThreads = %d\n", m_executor.getNumThreads());
fclose(file);
}
else { return false; }
return true;
}
#if 0
void testCode()
{
Workspace wp(*kernelWrapper.m_kernelCtx);
// TestClass tmp(*kernelWrapper.m_kernelCtx);
/*
const CString eegFile = Directories::getDataDir() + CString("/scenarios/signals/bci-motor-imagery.ov");
// const CString eegFile = CString("E:/jl/noise-test.ov");
const CString scenarioFile = Directories::getDataDir() + CString("/applications/tracker/tracker-debug-display.xml");
if(!wp.setTrack(eegFile.toASCIIString())) { return 2; }
if(!wp.setprocessor(scenarioFile.toASCIIString())) { return 3; }
// Push some chunks to selection
Selection& selection = wp.m_track.m_Selection;
selection.addRange(Range(3,5));
selection.addRange(Range(9,11));
if(!wp.play()) { return 4; }
*/
}
#endif
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,55 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeChannelLocalization.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationDecoder<BoxAlgorithmProxy>>::getHeaderImpl(
TypeChannelLocalization::Header& h)
{
h.m_Dynamic = m_impl.getOutputDynamic();
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationDecoder<BoxAlgorithmProxy>>::getBufferImpl(
TypeChannelLocalization::Buffer& b)
{
CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(
const TypeChannelLocalization::Header& hdr)
{
m_impl.getInputDynamic() = hdr.m_Dynamic;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(
const TypeChannelLocalization::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,51 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeChannelUnits.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeChannelUnits::Header& h)
{
h.m_Dynamic = m_impl.getOutputDynamic();
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeChannelUnits::Buffer& b)
{
CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeChannelUnits::Header& hdr)
{
m_impl.getInputDynamic() = hdr.m_Dynamic;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeChannelUnits::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,68 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeExperimentInfo.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoDecoder<BoxAlgorithmProxy>>::getHeaderImpl(
TypeExperimentInfo::Header& h)
{
h.m_ExperimentID = m_impl.getOutputExperimentID();
h.m_ExperimentDate = (*m_impl.getOutputExperimentDate()).toASCIIString();
h.m_SubjectID = m_impl.getOutputSubjectID();
h.m_SubjectName = (*m_impl.getOutputSubjectName()).toASCIIString();
h.m_SubjectAge = m_impl.getOutputSubjectAge();
h.m_SubjectGender = m_impl.getOutputSubjectGender();
h.m_LaboratoryID = m_impl.getOutputLaboratoryID();
h.m_LaboratoryName = (*m_impl.getOutputLaboratoryName()).toASCIIString();
h.m_TechnicianID = m_impl.getOutputTechnicianID();
h.m_TechnicianName = (*m_impl.getOutputTechnicianName()).toASCIIString();
return true;
}
template <>
bool DecoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeExperimentInfo::Buffer& /*b*/)
{
// Should be no buffer in the experiment stream
return true;
}
template <>
bool EncoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(
const TypeExperimentInfo::Header& hdr)
{
// @fixme the new() calls may imply memory leaks, a bit odd the codec takes pointers
m_impl.getInputExperimentID() = hdr.m_ExperimentID;
m_impl.getInputExperimentDate() = new CString(hdr.m_ExperimentDate.c_str());
m_impl.getInputSubjectID() = hdr.m_SubjectID;
m_impl.getInputSubjectName() = new CString(hdr.m_SubjectName.c_str());
m_impl.getInputSubjectAge() = hdr.m_SubjectAge;
m_impl.getInputSubjectGender() = hdr.m_SubjectGender;
m_impl.getInputLaboratoryID() = hdr.m_LaboratoryID;
m_impl.getInputLaboratoryName() = new CString(hdr.m_LaboratoryName.c_str());
m_impl.getInputTechnicianID() = hdr.m_TechnicianID;
m_impl.getInputTechnicianName() = new CString(hdr.m_TechnicianName.c_str());
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(
const TypeExperimentInfo::Buffer& /*buf*/)
{
// Should be no buffer in the experiment stream
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,52 @@
// @todo this is identical to CodecFeatureMatrix. Refactor?
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeFeatureVector.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeFeatureVector::Header& h)
{
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeFeatureVector::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(
const TypeFeatureVector::Header& hdr)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(
const TypeFeatureVector::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,48 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeMatrix.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeMatrix::Header& h)
{
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeMatrix::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeMatrix::Header& hdr)
{
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeMatrix::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,51 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeSignal.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeSignal, Toolkit::TSignalDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeSignal::Header& h)
{
h.m_Sampling = m_impl.getOutputSamplingRate();
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeSignal, Toolkit::TSignalDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeSignal::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeSignal, Toolkit::TSignalEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeSignal::Header& hdr)
{
m_impl.getInputSamplingRate() = hdr.m_Sampling;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeSignal, Toolkit::TSignalEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeSignal::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,58 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeSpectrum.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeSpectrum, Toolkit::TSpectrumDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeSpectrum::Header& h)
{
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
h.m_Sampling = m_impl.getOutputSamplingRate();
CMatrix* abscissas = m_impl.getOutputFrequencyAbscissa();
h.m_Abscissas.copy(*abscissas);
return true;
}
template <>
bool DecoderAdapter<TypeSpectrum, Toolkit::TSpectrumDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeSpectrum::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeSpectrum, Toolkit::TSpectrumEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeSpectrum::Header& hdr)
{
m_impl.getInputSamplingRate() = hdr.m_Sampling;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
CMatrix* abscissas = m_impl.getInputFrequencyAbscissa();
abscissas->copy(hdr.m_Abscissas);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeSpectrum, Toolkit::TSpectrumEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeSpectrum::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,50 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeStimulation.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeStimulation, Toolkit::TStimulationDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeStimulation::Header& /*target*/) { return true; }
template <>
bool DecoderAdapter<TypeStimulation, Toolkit::TStimulationDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeStimulation::Buffer& b)
{
IStimulationSet* decoded = m_impl.getOutputStimulationSet();
b.m_buffer.clear();
for (size_t i = 0; i < decoded->getStimulationCount(); ++i)
{
b.m_buffer.appendStimulation(decoded->getStimulationIdentifier(i), decoded->getStimulationDate(i), decoded->getStimulationDuration(i));
}
return true;
}
template <>
bool EncoderAdapter<TypeStimulation, Toolkit::TStimulationEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeStimulation::Header& /*hdr*/)
{
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeStimulation, Toolkit::TStimulationEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeStimulation::Buffer& buf)
{
IStimulationSet* inputSet = m_impl.getInputStimulationSet();
inputSet->clear();
for (size_t i = 0; i < buf.m_buffer.getStimulationCount(); ++i)
{
inputSet->appendStimulation(buf.m_buffer.getStimulationIdentifier(i), buf.m_buffer.getStimulationDate(i) + m_offset.time(),
buf.m_buffer.getStimulationDuration(i));
}
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,184 @@
//
// @note some ov files which have lots of stimulation chunks take long time to import
// when launching tracker from visual studio. this is probably due to memory allocation, similar to slow simple dsp grammar parsing.
//
#include "Demuxer.h"
#include <iostream>
#include <thread>
#include <deque>
#include <vector>
#include "../../../../plugins/processing/file-io/src/ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include "Stream.h"
#include "CodecFactory.h"
namespace OpenViBE {
namespace Tracker {
bool Demuxer::initialize()
{
// log() << Kernel::LogLevel_Info << "Source: Initializing with " << signalFile << "\n";
m_chunksSent = 0;
m_pending = false;
m_target.setSource(m_origin.getSource());
return true;
}
bool Demuxer::uninitialize() const
{
log() << Kernel::LogLevel_Info << "Source: Uninitializing\n";
m_origin.uninitialize();
return true;
}
// bool Demuxer::pullChunk(MemoryBufferWithType& output)
bool Demuxer::step()
{
// log() << Kernel::LogLevel_Info << "Source: Trying to pull a chunk\n";
while (!m_origin.isEOF())
{
while (!m_origin.isEOF() && m_reader.getCurrentNodeID() == EBML::CIdentifier())
{
std::vector<uint8_t> bytes;
m_origin.read(bytes, 1);
//OV_ERROR_UNLESS_KRF(s == 1 || justStarted, "Unexpected EOF in " << m_filename, Kernel::ErrorType::BadParsing);
if (!bytes.empty()) { m_reader.processData(&bytes[0], bytes.size()); }
}
if (!m_origin.isEOF() && m_reader.getCurrentNodeSize() != 0)
{
std::vector<uint8_t> bytes;
m_origin.read(bytes, size_t(m_reader.getCurrentNodeSize()));
//OV_ERROR_UNLESS_KRF(s == m_swap.getSize(), "Unexpected EOF in " << m_filename, Kernel::ErrorType::BadParsing);
m_pendingChunk.m_Buffer.resize(0);
m_pendingChunk.m_StartTime = CTime::max();
m_pendingChunk.m_EndTime = CTime::max();
m_pendingChunk.m_StreamIndex = std::numeric_limits<uint32_t>::max();
m_reader.processData(&bytes[0], bytes.size());
}
if (m_pending)
{
// We have dada
// log() << Kernel::LogLevel_Info << "Source: Found a chunk, queueing\n";
const size_t streamIndex = m_pendingChunk.m_StreamIndex;
m_pending = false;
const StreamPtr stream = m_target.getStream(streamIndex);
if (!stream)
{
log() << Kernel::LogLevel_Error << "Error: Trying to decode stream without creating it first (buffer before header in EBML?)\n";
return false;
}
m_decoders[streamIndex]->decode(m_pendingChunk);
return true;
}
}
if (m_origin.isEOF()) { log() << Kernel::LogLevel_Trace << "Source file EOF reached\n"; }
else { log() << Kernel::LogLevel_Warning << "Issue with source file\n"; }
return false;
}
bool Demuxer::isMasterChild(const EBML::CIdentifier& identifier)
{
if (identifier == EBML_Identifier_Header) { return true; }
if (identifier == OVP_NodeId_OpenViBEStream_Header) { return true; }
if (identifier == OVP_NodeId_OpenViBEStream_Header_Compression) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Header_StreamType) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer) { return true; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_StreamIndex) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_StartTime) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_EndTime) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_Content) { return false; }
return false;
}
void Demuxer::openChild(const EBML::CIdentifier& identifier)
{
m_nodes.push(identifier);
EBML::CIdentifier& top = m_nodes.top();
if (top == EBML_Identifier_Header) { m_hasEBMLHeader = true; }
if (top == OVP_NodeId_OpenViBEStream_Header)
{
if (!m_hasEBMLHeader)
{
//this->getLogManager() << Kernel::LogLevel_Info << "The file " << m_filename << " uses an outdated (but still compatible) version of the .ov file format\n";
}
}
if (top == OVP_NodeId_OpenViBEStream_Header)
{
m_streamIdxToOutputIdxs.clear();
m_streamIdxToTypeIDs.clear();
}
}
void Demuxer::processChildData(const void* buffer, const size_t size)
{
EBML::CIdentifier& top = m_nodes.top();
// Uncomment this when ebml version will be used
//if(top == EBML_Identifier_EBMLVersion) { const uint64_t versionNumber=(uint64_t)m_readerHelper.getUInt(buffer, size); }
if (top == OVP_NodeId_OpenViBEStream_Header_Compression)
{
//if (m_readerHelper.getUInt(buffer, size) != 0) { OV_WARNING_K("Impossible to use compression as it is not yet implemented"); }
}
else if (top == OVP_NodeId_OpenViBEStream_Header_StreamType)
{
const uint64_t typeID = m_readerHelper.getUInt(buffer, size);
const size_t index = m_target.getNumStreams();
m_target.createStream(index, typeID);
DecoderBase* decoder = CodecFactory::getDecoder(m_kernelCtx, *m_target.getStream(index));
m_decoders.push_back(decoder);
}
else if (top == OVP_NodeId_OpenViBEStream_Buffer_StreamIndex)
{
// @note if trying to do -1 to map to [0,...] convention, something breaks
m_pendingChunk.m_StreamIndex = size_t(m_readerHelper.getUInt(buffer, size));
//log() << Kernel::LogLevel_Info << "Run into index " << m_pendingChunk.streamIndex << "\n";
}
else if (top == OVP_NodeId_OpenViBEStream_Buffer_StartTime) { m_pendingChunk.m_StartTime = m_readerHelper.getUInt(buffer, size); }
else if (top == OVP_NodeId_OpenViBEStream_Buffer_EndTime) { m_pendingChunk.m_EndTime = m_readerHelper.getUInt(buffer, size); }
else if (top == OVP_NodeId_OpenViBEStream_Buffer_Content)
{
m_pendingChunk.m_Buffer.resize(size_t(size));
memcpy(&m_pendingChunk.m_Buffer[0], reinterpret_cast<const uint8_t*>(buffer), size_t(size));
}
}
void Demuxer::closeChild()
{
EBML::CIdentifier& top = m_nodes.top();
if (top == OVP_NodeId_OpenViBEStream_Header) { } // Assign file streams to outputs here
if (top == OVP_NodeId_OpenViBEStream_Buffer)
{
m_pending = ((m_pendingChunk.m_StreamIndex != std::numeric_limits<size_t>::max()) &&
(m_pendingChunk.m_StartTime != CTime::max()) && (m_pendingChunk.m_EndTime != CTime::max()));
}
m_nodes.pop();
}
} // namespace Tracker
} // namespace OpenViBE
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,186 @@
#include <iostream>
#include <system/ovCTime.h>
#include "ParallelExecutor.h"
#include <array>
namespace OpenViBE {
namespace Tracker {
bool ParallelExecutor::initialize(const uint32_t nThreads)
{
m_quit = false;
m_nJobsRunning = 0;
m_jobList.clear();
// ExecutorView passes the working threads a few function handles to the executor that holds the state
ExecutorView ctx(*this);
for (uint32_t i = 0; i < nThreads; ++i)
{
m_workerThreads.push_back(new CWorkerThread());
m_threads.push_back(new std::thread(std::bind(&CWorkerThread::startWorkerThread, m_workerThreads[i], ctx, i)));
}
return true;
}
bool ParallelExecutor::uninitialize()
{
// Tell the threads waiting in the cond its time to quit, don't care of pending jobs
{ // scope for lock
std::unique_lock<std::mutex> lock(m_jobMutex);
m_jobList.clear();
m_quit = true;
}
m_haveWork.notify_all();
for (uint32_t i = 0; i < m_threads.size(); ++i)
{
m_threads[i]->join();
delete m_threads[i];
delete m_workerThreads[i];
}
m_threads.clear();
m_workerThreads.clear();
return true;
}
bool ParallelExecutor::pushJob(const jobCall& someJob)
{
// @fixme to add better concurrency, push a list instead; lock();add list;unlock();notify_all();
{ // lock scope
std::lock_guard<std::mutex> lock(m_jobMutex);
m_jobList.push_back(someJob);
}
m_haveWork.notify_one();
return true;
}
bool ParallelExecutor::pushJobList(const std::deque<jobCall>& vJobList)
{
{ // lock scope
std::lock_guard<std::mutex> lock(m_jobMutex);
if (!m_jobList.empty())
{
std::cout << "Error, trying to push list with old jobs pending\n";
return false;
}
m_jobList = vJobList;
}
m_haveWork.notify_all();
return true;
}
bool ParallelExecutor::waitForAll()
{
std::unique_lock<std::mutex> lock(m_jobMutex);
while (!m_jobList.empty()) { m_jobDone.wait(lock); }
return true;
}
bool ParallelExecutor::getJob(jobCall& job)
{
// Wait until we get a job or are told to quit
std::unique_lock<std::mutex> lock(m_jobMutex);
m_haveWork.wait(lock, [this]() { return (this->m_quit || !this->m_jobList.empty()); });
if (m_quit) { return false; }
// Ok, we have a job
job = m_jobList.front();
m_jobList.pop_front();
m_nJobsRunning++;
return true;
}
bool ParallelExecutor::declareDone()
{
std::unique_lock<std::mutex> lock(m_jobMutex);
m_nJobsRunning--;
m_jobDone.notify_one();
return true;
}
bool ParallelExecutor::clearPendingJobs()
{
std::unique_lock<std::mutex> lock(m_jobMutex);
m_jobList.clear();
return true;
}
size_t ParallelExecutor::getJobCount() const
{
std::unique_lock<std::mutex> lock(m_jobMutex);
return m_jobList.size();
}
bool ParallelExecutor::isIdle() const
{
std::unique_lock<std::mutex> lock(m_jobMutex);
return (m_jobList.empty() && m_nJobsRunning == 0);
}
//___________________________________________________________________//
// //
void testFunction(void* data)
{
for (uint32_t i = 0; i < 10; ++i)
{
std::cout << "Fun: " << *static_cast<uint32_t*>(data) << "\n";
System::Time::sleep(*static_cast<uint32_t*>(data));
}
// return true;
}
bool ParallelExecutor::launchTest()
{
std::array<int, 6> stuff = { 500, 666, 50, 1000, 300, 100 };
std::cout << "Push test\n";
this->pushJob(std::bind(testFunction, &stuff[0]));
this->waitForAll();
this->pushJob(std::bind(testFunction, &stuff[1]));
this->pushJob(std::bind(testFunction, &stuff[2]));
this->pushJob(std::bind(testFunction, &stuff[3]));
this->pushJob(std::bind(testFunction, &stuff[4]));
this->pushJob(std::bind(testFunction, &stuff[2]));
this->waitForAll();
this->waitForAll();
std::cout << "Pushlist test\n";
std::deque<jobCall> jobList;
jobList.push_back(std::bind(testFunction, &stuff[0]));
jobList.push_back(std::bind(testFunction, &stuff[1]));
this->pushJobList(jobList);
this->waitForAll();
std::cout << "Done\n";
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,512 @@
#include "ProcExternalProcessing.h"
#include <iostream>
#include <thread>
#include <deque>
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include <algorithm> // std::max
#include "CodecFactory.h"
// thread::
// wait for connection;
// while(connected ||!quit)
// {
// waitForChunk();
// pushChunk();
// }
// if(quit) {
// pushEndStim(); // XML have player controller that will quit
// }
// exit;
// #include "StreamChunk.h"
// #include "StreamSignalChunk.h"
// #include "StreamStimulationChunk.h"
#include "Stream.h"
#include "TypeSignal.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
void playerLaunch(const char* xmlFile, const char* args, bool playFast, bool noGUI, uint32_t identifier); // In processor.cpp
ProcExternalProcessing::ProcExternalProcessing(const Kernel::IKernelContext& ctx, const ProcExternalProcessing& other) : Processor(ctx)
{
uint32_t sendPort, recvPort;
other.getProcessorPorts(sendPort, recvPort);
setProcessorPorts(sendPort, recvPort);
bool noGui, doSend, doReceive;
other.getProcessorFlags(noGui, doSend, doReceive);
setProcessorFlags(noGui, doSend, doReceive);
setArguments(other.getArguments());
initialize(other.getFilename());
}
bool ProcExternalProcessing::initialize(const std::string& xmlFile)
{
m_pushStartTime = BufferedClient::CLIENT_NOT_STARTED;
m_pullStartTime = BufferedClient::CLIENT_NOT_STARTED;
if (xmlFile.length() == 0)
{
// log() << Kernel::LogLevel_Trace << "No processor configured\n";
m_xmlFilename.clear();
return true;
}
// log() << Kernel::LogLevel_Trace << "Processor: Initializing with " << xmlFile << "\n";
m_xmlFilename = xmlFile;
return true;
}
bool ProcExternalProcessing::uninitialize()
{
// log() << Kernel::LogLevel_Trace << "Processor: Uninitializing\n";
stop();
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
return true;
}
CTime ProcExternalProcessing::getCurrentTime() const
{
const CTime startTime = (m_doSend ? m_pushStartTime : m_pullStartTime);
if (startTime == BufferedClient::CLIENT_NOT_STARTED) { return CTime::min(); }
const CTime refTime = CTime(m_pushClient ? m_pushClient->getTime() : m_pullClient->getTime());
const CTime currentTime = refTime - startTime;
// log() << Kernel::LogLevel_Info << "Its " << CTime(currentTime).toSeconds() << "\n";
return currentTime;
}
// If source track changes, we need a new codec since they are bound to the streams
// @todo make streamless?
bool ProcExternalProcessing::setNewSource(StreamBundle* source, const bool sendHeader, const bool sendEnd)
{
m_sendHeader = sendHeader;
m_sendEnd = sendEnd;
m_src = source;
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
return true;
}
bool ProcExternalProcessing::setNewTarget(StreamBundle* target)
{
m_dst = target;
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
return true;
}
// Sends all chunks up to the current time point t
bool ProcExternalProcessing::push()
{
if (m_xmlFilename.length() == 0) { return false; }
if (!m_doSend)
{
// This processor is configured to receive, so we do nothing on send
return true;
}
if (!m_pushClient || m_pushClient->hasQuit())
{
this->stop();
return false;
}
if (!m_src) { return false; }
if (m_pushStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
m_pushStartTime = m_pushClient->getStartTime();
if (m_pushStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
// Not yet synced
return true;
}
}
bool foundSomething = false;
m_sentSomething = false;
uint64_t chunksSent = 0;
while (true)
{
size_t streamIndex;
StreamPtr stream = m_src->getNextStream(streamIndex);
if (!stream) { break; }
if (m_encoders.size() <= streamIndex)
{
m_encoders.resize(streamIndex + 1, nullptr);
m_encoders[streamIndex] = CodecFactory::getEncoder(m_kernelCtx, *stream);
}
CTime chunkStartTime = CTime::min(), chunkEndTime = CTime::min();
if (!stream->peek(chunkStartTime, chunkEndTime))
{
// if peek fails maybe the stream has ended?
break;
}
// There is something to send, now or later
foundSomething = true;
const CTime elapsedTime = getCurrentTime();
// Send if its time
if (elapsedTime >= chunkStartTime + m_previousEnd)
{
EncodedChunk chunk;
EChunkType outputType;
m_encoders[streamIndex]->setEncodeOffset(m_previousEnd);
if (m_encoders[streamIndex]->encode(chunk, outputType))
{
const bool dontPush = (!m_sendHeader && outputType == EChunkType::Header) || (!m_sendEnd && outputType == EChunkType::End);
if (!dontPush)
{
chunk.m_StreamIndex = streamIndex;
m_pushClient->pushBuffer(chunk);
m_pushLastTime = chunk.m_EndTime;
/*
log() << Kernel::LogLevel_Info << "Enc str " << streamIndex << " at " << CTime(elapsedTime).toSeconds()
<< " chk [" << CTime(chunk.m_startTime).toSeconds()
<< "," << CTime(chunk.m_endTime).toSeconds()
<< "]\n";
*/
m_sentSomething = true;
chunksSent++;
}
stream->step();
}
else
{
// log() << Kernel::LogLevel_Error << "Error: Failed to encode chunk\n";
return false;
}
}
else
{
// We are early
std::this_thread::sleep_for(std::chrono::milliseconds(1));
break;
}
// } while (sentSomething || m_PlayFast); // we loop until we have sent everything up to this moment.
}
if (m_sentSomething)
{
//log() << Kernel::LogLevel_Info << "flush\n";
m_pushClient->requestFlush();
}
// @fixme note that the while loop above may take long to return the control flow for some
// bad streams
if (!foundSomething)
{
// Maybe all streams ended?
// log() << Kernel::LogLevel_Info << "Nothing to send - all streams ended?\n";
// Update time offset for 'continuous sending' mode
// n.b. we can always do this, since for noncontinuous mode, new play() will be called, resetting these
if (m_src->isFinished() && !m_requestNewOffset)
{
const CTime maxEndTime = m_src->getMaxDuration();
m_previousEnd += maxEndTime; // Incremental for more than 2 tracks
m_requestNewOffset = true;
// log() << Kernel::LogLevel_Info << "New offset at " << CTime(m_PreviousEnd).toSeconds() << "\n";
}
// We do this to keep the External Processing box running on the designer side
m_pushClient->requestFlush();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return false;
}
if (!m_playFast)
{
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
// std::this_thread::yield();
}
return true;
}
// Sends all chunks up to the current time point t
bool ProcExternalProcessing::pop()
{
if (m_xmlFilename.length() == 0) { return false; }
if (!m_doReceive)
{
// This processor is configured to send, so we do nothing on receive
return true;
}
if (!m_pullClient || !m_dst) { return false; }
if (m_pullClient->hasQuit())
{
this->stop();
return false;
}
if (m_pullStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
m_pullStartTime = m_pullClient->getStartTime();
if (m_pullStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
// Not yet synced
return true;
}
}
EncodedChunk chunk;
bool gotSomething = false;
while (m_pullClient->pullBuffer(chunk))
{
// log() << Kernel::LogLevel_Info << "Got chunk " << chunk.m_startTime << "," << chunk.m_endTime << "\n";
StreamPtr targetStream = m_dst->getStream(chunk.m_StreamIndex);
if (!targetStream)
{
if (!m_dst->createStream(chunk.m_StreamIndex, chunk.m_StreamType)) { continue; }
targetStream = m_dst->getStream(chunk.m_StreamIndex);
if (m_decoders.size() <= chunk.m_StreamIndex) { m_decoders.resize(chunk.m_StreamIndex + 1, nullptr); }
m_decoders[chunk.m_StreamIndex] = CodecFactory::getDecoder(m_kernelCtx, *targetStream);
}
// @note Decoder doesn't need to add offsets as these times are coming from the
// external processor, so they are already offset by the past length.
if (!chunk.m_Buffer.empty())
{
m_decoders[chunk.m_StreamIndex]->decode(chunk);
/*
log() << Kernel::LogLevel_Info << "Dec str " << chunk.streamIndex << " chk [" << CTime(chunk.m_startTime).toSeconds()
<< "," << CTime(chunk.m_endTime).toSeconds() << "]\n";
*/
}
m_pullLastTime = chunk.m_EndTime;
gotSomething = true;
}
if (!gotSomething) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
if (m_pullClient->hasQuit()) { return false; }
return true;
}
bool ProcExternalProcessing::play(const bool playFast, const std::function<bool(CTime)>& quitCB, const std::function<bool()>& nextTrackFun)
{
m_pushLastTime = CTime::min();
m_pullLastTime = CTime::min();
m_isRunning = false;
if (m_xmlFilename.length() == 0)
{
log() << Kernel::LogLevel_Error << "Error: No processor initialized\n";
return false;
}
m_chunksSent = 0;
// m_PreviousChunkEnd = 0;
m_playFast = playFast; // @todo To work neatly it'd be better to be able to pass in the chunk times to the designer side
m_previousEnd = 0;
m_requestNewOffset = false;
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
// log() << Kernel::LogLevel_Info << "Reset offset to " << CTime(m_PreviousEnd).toSeconds() << "\n";
const CString expandedName = m_kernelCtx.getConfigurationManager().expand(m_xmlFilename.c_str());
std::stringstream ss;
ss << "--define Tracker_Port_Send " << m_sendPort << " ";
ss << "--define Tracker_Port_Receive " << m_recvPort << " ";
const std::string allArgs = m_arguments + " " + ss.str();
m_playerThread = new std::thread(std::bind(&playerLaunch, expandedName, allArgs.c_str(), m_playFast, m_noGUI, m_sendPort));
// @fixme this ad-hoc sleep is pretty terrible, but it seems that if player launch is slow enough and
// we launch the client threads immediately below, in Windows it results in deadlocks and errors, suggesting that
// the concurrency control logic is not really correct in the communication with the external processing box.
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
if (m_doSend)
{
m_pushClient = new PushClient(m_sendPort);
m_pushClientThread = new std::thread(&PushClient::start, m_pushClient);
}
if (m_doReceive)
{
m_pullClient = new PullClient(m_recvPort);
m_pullClientThread = new std::thread(&PullClient::start, m_pullClient);
}
// log() << Kernel::LogLevel_Debug << "External processing thread(s) launched, waiting sync\n";
for (size_t retries = 0; retries < 20; ++retries)
{
if (m_doSend) { m_pushStartTime = m_pushClient->getStartTime(); }
if (m_doReceive) { m_pullStartTime = m_pullClient->getStartTime(); }
if (isSynced())
{
// log() << Kernel::LogLevel_Debug << "Clients synced after " << retries << " retries\n";
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
if (!isSynced())
{
// log() << Kernel::LogLevel_Error << "Error syncing client(s) after 10 secs of retries\n";
if (m_pushClient) { m_pushClient->requestQuit(); }
if (m_pullClient) { m_pullClient->requestQuit(); }
return false;
}
m_isRunning = true;
while (m_isRunning)
{
m_isRunning &= push();
m_isRunning &= pop();
if (!m_isRunning && nextTrackFun) { m_isRunning = nextTrackFun(); }
// @fixme the quit callback might be a bit expensive, shouldn't hammer it
if (quitCB && quitCB((m_doSend ? m_pushLastTime : m_pullLastTime))) { m_isRunning = false; }
}
stop();
return true;
}
bool ProcExternalProcessing::stop()
{
m_isRunning = false;
if (m_pushClient)
{
// log() << Kernel::LogLevel_Info << "Stopping external processing push client\n";
m_pushClient->requestQuit();
if (m_pushClientThread) { m_pushClientThread->join(); }
delete m_pushClientThread;
m_pushClientThread = nullptr;
delete m_pushClient;
m_pushClient = nullptr;
}
if (m_pullClient)
{
// log() << Kernel::LogLevel_Info << "Stopping external processing pull client\n";
m_pullClient->requestQuit();
if (m_pullClientThread) { m_pullClientThread->join(); }
delete m_pullClientThread;
m_pullClientThread = nullptr;
delete m_pullClient;
m_pullClient = nullptr;
}
// tear down the player object
if (m_playerThread)
{
// log() << Kernel::LogLevel_Trace << "Joining player thread\n";
m_playerThread->join();
delete m_playerThread;
m_playerThread = nullptr;
}
m_pushStartTime = BufferedClient::CLIENT_NOT_STARTED;
m_pullStartTime = BufferedClient::CLIENT_NOT_STARTED;
return true;
}
bool ProcExternalProcessing::save()
{
auto& mgr = m_kernelCtx.getConfigurationManager();
std::stringstream sPort;
sPort << m_sendPort;
// std::stringstream rPort; rPort << m_RecvPort;
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor", m_xmlFilename.c_str());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_FirstPort", sPort.str().c_str());
// mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_RecvPort", rPort.str().c_str());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_NoGUI", (m_noGUI ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_DoSend", (m_doSend ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_DoReceive", (m_doReceive ? "true" : "false"));
return true;
}
bool ProcExternalProcessing::load()
{
auto& mgr = m_kernelCtx.getConfigurationManager();
if (mgr.lookUpConfigurationTokenIdentifier("Tracker_Workspace_Processor") != CIdentifier::undefined())
{
initialize(mgr.lookUpConfigurationTokenValue("Tracker_Workspace_Processor").toASCIIString());
}
if (mgr.lookUpConfigurationTokenIdentifier("Tracker_Workspace_Processor_FirstPort") != CIdentifier::undefined())
{
const uint32_t port = uint32_t(mgr.expandAsUInteger("${Tracker_Workspace_Processor_FirstPort}"));
setProcessorPorts(port, port + 1);
}
m_noGUI = mgr.expandAsBoolean("${Tracker_Workspace_Processor_NoGUI}", m_noGUI);
m_doSend = mgr.expandAsBoolean("${Tracker_Workspace_Processor_DoSend}", m_doSend);
m_doReceive = mgr.expandAsBoolean("${Tracker_Workspace_Processor_DoReceive}", m_doReceive);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,262 @@
#include <iostream>
#include <thread>
#include <sstream>
#include <system/ovCTime.h>
#include "ProcExternalProcessingHelper.h"
namespace OpenViBE {
namespace Tracker {
const CTime BufferedClient::CLIENT_NOT_STARTED = CTime::max();
void BufferedClient::requestQuit()
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_pleaseQuit = true;
m_bufferCondition.notify_one();
}
bool BufferedClient::hasQuit()
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
return m_hasQuit;
}
void BufferedClient::start()
{
if (!connectClient())
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_hasQuit = true;
return;
}
while (true)
{
// Push or pull, uses lock internally
if (!step()) { break; }
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
// Exit the loop if we're told to quit or if we've lost the connection
if (m_pleaseQuit || !isConnected() || isEndReceived()) { break; }
}
}
// Shut the connection
this->close();
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_hasQuit = true;
}
// The thread will exit here and can be joined
}
bool BufferedClient::connectClient()
{
static int connectId = 0;
std::stringstream cId;
cId << std::string("tracker");
cId << connectId++;
const std::string connectionID = cId.str();
this->setConnectionID(connectionID);
int errorCount = 0;
while (!this->connect("127.0.0.1", m_port))
{
const ELibraryError error = this->getLastError();
if (error == Socket_FailedToConnect)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
if (errorCount++ > 20)
{
std::cout << "Designer not responding on port " << m_port << " retried 5 secs" << std::endl;
return false;
}
}
else
{
std::cout << "Error " << error << std::endl;
return false;
}
//if (s_DidRequestForcedQuit) { exit(EXIT_SUCCESS); }
}
// Announce to server that the box has finished initializing and wait for acknowledgement
errorCount = 0;
while (!this->waitForSyncMessage())
{
if (errorCount++ > 10)
{
std::cout << "Server not syncing in port " << m_port << std::endl;
this->close();
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
this->pushLog(Communication::ELogLevel::LogLevel_Info, "Received Ping");
this->pushSync();
this->pushLog(Communication::ELogLevel::LogLevel_Info, "Sent Pong");
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_startTime = this->getTime();
}
return true;
}
bool PushClient::pushBuffer(const EncodedChunk& encodedChunk)
{
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
if (!m_pleaseQuit)
{
EncodedChunk* buffer = new EncodedChunk(encodedChunk);
m_buffer.push_back(buffer);
}
}
// No big harm notifying in any case, though if in 'quit' state, the quit request has already notified
m_bufferCondition.notify_one();
return true;
}
bool PushClient::step()
{
std::unique_lock<std::mutex> oLock(m_threadMutex, std::defer_lock);
oLock.lock();
// Normal condition for the wait to exit is the flush request or having more data
m_bufferCondition.wait(oLock, [this]() { return (m_pleaseFlush || !m_buffer.empty() || m_pleaseQuit || !isConnected() || isEndReceived()); });
while (!m_buffer.empty())
{
EncodedChunk* chunk = m_buffer[0];
m_buffer.pop_front();
if (!this->pushEBML(chunk->m_StreamIndex, chunk->m_StartTime.time(), chunk->m_EndTime.time(),
std::make_shared<const std::vector<uint8_t>>(chunk->m_Buffer)))
{
std::cerr << "Failed to push EBML.\n";
std::cerr << "Error " << this->getLastError() << "\n";
oLock.unlock();
return false;
}
delete chunk;
}
if (m_pleaseFlush)
{
this->pushSync();
m_pleaseFlush = false;
// We don't use condition variable here as users of the client should never
// be able to interrupt this wait unless error state is reached
while (!m_pleaseQuit && isConnected() && !isEndReceived() && !this->waitForSyncMessage())
{
oLock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
oLock.lock();
}
}
oLock.unlock();
return true;
}
void PushClient::requestFlush()
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_pleaseFlush = true;
m_bufferCondition.notify_one();
}
bool PullClient::pullBuffer(EncodedChunk& chunk)
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
if (m_buffer.empty()) { return false; }
EncodedChunk* ptr = m_buffer[0];
m_buffer.pop_front();
chunk = *ptr;
delete ptr;
return true;
}
bool PullClient::popMessagesToBuffer()
{
uint64_t packetId, startTime, endTime;
size_t streamIndex;
std::shared_ptr<const std::vector<uint8_t>> ebml;
bool gotSomething = false;
while (this->popEBML(packetId, streamIndex, startTime, endTime, ebml))
{
// @todo optimize by passing in the buffer to popEBML already
EncodedChunk* chunk = new EncodedChunk();
chunk->m_StartTime = startTime;
chunk->m_EndTime = endTime;
chunk->m_StreamIndex = streamIndex;
// @fixme inefficient to query for each chunk since its stream specific
uint64_t id;
std::string streamName;
this->getInput(chunk->m_StreamIndex, id, chunk->m_StreamType, streamName);
chunk->m_Buffer.resize(ebml->size());
for (size_t i = 0; i < ebml->size(); ++i) { chunk->m_Buffer[i] = (*ebml)[i]; }
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_buffer.push_back(chunk);
}
gotSomething = true;
//std::cout << "Got pkg " << packetId << " idx " << streamIndex << " siz " << chunk.bufferData.size() << "\n";
}
return gotSomething;
}
// pull
bool PullClient::step()
{
// Pull items until we get the sync message (no more to send)
while (!this->waitForSyncMessage() && this->isConnected() && !this->isEndReceived())
{
popMessagesToBuffer();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// std::this_thread::yield();
}
// Got sync, pull whatever remaining buffered on the sender
popMessagesToBuffer();
if (!this->isConnected() || this->isEndReceived()) { return false; }
// Notify we've processed everything
this->pushSync();
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,118 @@
#include "Processor.h"
#include <iostream>
#include <fstream>
#include <chrono>
#include <ctime>
#include <fs/Files.h>
#include <system/ovCMath.h>
#include <thread>
namespace OpenViBE {
namespace Tracker {
void playerLaunch(const char* xmlFile, const char* args, bool playFast, bool noGUI, uint32_t identifier)
{
std::string designer = std::string(Directories::getBinDir().toASCIIString()) + "/openvibe-designer.exe";
std::string argsAll = std::string(" --no-session-management ") + (args ? args : "") + (noGUI ? "--no-gui " : "") + (playFast ? "--play-fast " : " --play ")
+ " " + "\"" + xmlFile + "\"";
if (!FS::Files::directoryExists(Directories::getUserDataDir().toASCIIString())) { FS::Files::createPath(Directories::getUserDataDir().toASCIIString()); }
std::stringstream ss;
ss << std::string(Directories::getLogDir().toASCIIString()) << "/tracker-processor-dump-" << identifier << ".txt";
std::string outputDump = ss.str();
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::ofstream outStream(outputDump.c_str(), std::ios::app);
outStream << std::endl << "Date of launch: " << std::ctime(&now) << std::endl;
outStream << "Trying to launch: " << designer << std::endl;
outStream << "Args: " << argsAll << std::endl;
outStream << "Logging to: " << outputDump << std::endl;
outStream << std::endl;
outStream.close();
#if TARGET_OS_Windows
STARTUPINFO si;
PROCESS_INFORMATION pi;
// set the size of the structures
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = nullptr;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(outputDump.c_str(), FILE_APPEND_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = nullptr;
si.hStdError = h;
si.hStdOutput = h;
argsAll = std::string("\"") + designer + std::string("\"") + std::string(" ") + argsAll;
LPSTR argsLp = const_cast<char*>(argsAll.c_str());
BOOL retVal = CreateProcess(designer.c_str(), // the path
argsLp, // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Set handle inheritance to FALSE
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
);
// Close process and thread handles.
if (!retVal) { std::cout << "err: " << GetLastError() << "\n"; }
else { WaitForSingleObject(pi.hProcess, INFINITE); }
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(h);
#else
auto cmd = designer + argsAll + " >" + outputDump;
// std::cout << "Call is: " << cmd << "\n";
if (system(cmd.c_str()) != 0)
{
std::cout << "Launch of [" << cmd << "] failed\n";
}
#endif
}
bool Processor::configure(const char* filename)
{
const std::string usedFilename = (filename ? filename : m_xmlFilename.c_str());
if (usedFilename.length() == 0)
{
log() << Kernel::LogLevel_Error << "Error: Please set processor filename first\n";
return false;
}
const CString expandedName = m_kernelCtx.getConfigurationManager().expand(usedFilename.c_str());
const std::string designer = std::string(Directories::getBinDir().toASCIIString()) + "/openvibe-designer --no-session-management --open ";
const std::string outputDump = std::string(Directories::getDistRootDir().toASCIIString()) + "/tracker-processor-configure-dump.txt";
const std::string cmd = designer + expandedName.toASCIIString() + " >" + outputDump;
if (system(cmd.c_str()) != 0)
{
log() << Kernel::LogLevel_Error << "Launch of [" << cmd << "] failed\n";
return false;
}
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,135 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include "Selection.h"
#include <sstream>
#include <iomanip>
namespace OpenViBE {
namespace Tracker {
bool Selection::reset(const bool state) const
{
for (auto& t : m_tracks) { for (auto& s : t->getAllStreams()) { s->setSelected(state); } }
return true;
}
bool Selection::isEmpty() const
{
for (const auto& t : m_tracks) { if (t->getNumStreams() > 0) { return false; } }
return true;
}
bool Selection::isSomethingSelected() const
{
for (const auto& t : m_tracks) { for (const auto& str : t->getAllStreams()) { if (str->getSelected()) { return true; } } }
return false;
}
bool Selection::isTrackSelected(const size_t track) const
{
if (track >= m_tracks.size()) { return false; }
for (const auto& s : m_tracks[track]->getAllStreams()) { if (s->getSelected()) { return true; } }
return false;
}
bool Selection::isSelectionConsistent() const
{
if (isEmpty() || !isSomethingSelected()) { return true; }
std::vector<CIdentifier> selectedTypesPrevious;
// Check that selection for each track is identical to selection of previous track
// (empty selections per track are ignored)
for (auto& t : m_tracks)
{
std::vector<CIdentifier> selectedTypes;
for (const auto& s : t->getAllStreams()) { if (s->getSelected()) { selectedTypes.push_back(s->getTypeIdentifier()); } }
if (!selectedTypesPrevious.empty() && !selectedTypes.empty() && selectedTypesPrevious != selectedTypes) { return false; }
if (!selectedTypes.empty()) { selectedTypesPrevious = selectedTypes; }
}
return true;
}
size_t Selection::countSelectedTracks() const
{
size_t selectedTracks = 0;
for (const auto& t : m_tracks)
{
for (const auto& s : t->getAllStreams())
{
if (s->getSelected())
{
selectedTracks++;
break;
}
}
}
return selectedTracks;
}
size_t Selection::countSelectedStreams(const size_t trackIndex) const
{
if (trackIndex >= m_tracks.size()) { return 0; }
size_t numSelected = 0;
for (const auto& str : m_tracks[trackIndex]->getAllStreams()) { if (str && str->getSelected()) { numSelected++; } }
return numSelected;
}
bool Selection::save(const char* prefix) const
{
auto& mgr = this->getKernelContext().getConfigurationManager();
// @note with really huge datasets the .ovw file handling can get slow. should be profiled.
for (size_t t = 0; t < m_tracks.size(); ++t)
{
std::stringstream token;
token << prefix << "Track_" << std::setw(3) << std::setfill('0') << (t + 1) << "_Selected";
std::stringstream value;
for (size_t s = 0; s < m_tracks[t]->getNumStreams(); ++s) { if (m_tracks[t]->getStream(s)->getSelected()) { value << (s + 1) << " "; } }
mgr.addOrReplaceConfigurationToken(token.str().c_str(), value.str().c_str());
}
return true;
}
// Relies on the current track/stream set being compatible with the tokens. no error checking
bool Selection::load(const char* prefix) const
{
auto& mgr = this->getKernelContext().getConfigurationManager();
for (size_t t = 0; t < m_tracks.size(); ++t)
{
std::stringstream token;
token << prefix << "Track_" << std::setw(3) << std::setfill('0') << (t + 1) << "_Selected";
std::stringstream value(mgr.lookUpConfigurationTokenValue(token.str().c_str()).toASCIIString());
// Mark everything as unselected by default
for (size_t s = 0; s < m_tracks[t]->getNumStreams(); ++s) { m_tracks[t]->getStream(s)->setSelected(false); }
// Load sparse selection from file
std::string selected;
while (std::getline(value, selected, ' '))
{
const uint32_t selectedIdx = atoi(selected.c_str()) - 1;
if (selectedIdx < m_tracks[t]->getNumStreams()) { m_tracks[t]->getStream(selectedIdx)->setSelected(true); }
}
}
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,70 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include "Stream.h"
#include "StimulationStreamFilter.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
// @note At some point I thought to make a derived class of stimulation stream that simply wouldn't pass through
// the filtered stimulations on calling the getChunk(), but this resulted in some issues. Since modifying
// the original stream would not have been appropriate, I'd have had to choose one of the following,
// 1) make getChunk() in general copy all data instead of returning pointers 2) drop const qualifier
// from the getChunk() to keep track of allocated memory internally in the derived class or 3) make getChunk()
// return a smart pointer. I wasn't very happy about these options, so instead we just make a modded copy of the stream.
std::shared_ptr<StreamBase> filterStimulationStreamEndPoints(const std::shared_ptr<const StreamBase>& src, const Kernel::IKernelContext& ctx)
{
const std::vector<uint64_t> stims = { OVTK_StimulationId_ExperimentStop, OVTK_StimulationId_EndOfFile, OVTK_GDF_End_Of_Session };
auto result = filterStimulationStream(src, ctx, stims);
return result;
}
std::shared_ptr<StreamBase> filterStimulationStream(const std::shared_ptr<const StreamBase>& src, const Kernel::IKernelContext& ctx,
const std::vector<uint64_t>& stimsToFilter)
{
if (src->getTypeIdentifier() != OV_TypeId_Stimulations) { return nullptr; }
const auto typedSrc = std::static_pointer_cast<const Stream<TypeStimulation>>(src);
auto target = std::make_shared<Stream<TypeStimulation>>(ctx);
target->clear();
target->getHeader().m_StartTime = typedSrc->getHeader().m_StartTime;
target->getHeader().m_EndTime = typedSrc->getHeader().m_EndTime;
for (size_t chk = 0; chk < typedSrc->getChunkCount(); ++chk)
{
const auto chunk = typedSrc->getChunk(chk);
auto newChunk = new TypeStimulation::Buffer;
for (size_t i = 0; i < chunk->m_buffer.getStimulationCount(); ++i)
{
const uint64_t id = chunk->m_buffer.getStimulationIdentifier(i);
if (std::none_of(stimsToFilter.begin(), stimsToFilter.end(), [id](const uint64_t val) { return val == id; }))
{
const uint64_t timestamp = chunk->m_buffer.getStimulationDate(i);
const uint64_t duration = chunk->m_buffer.getStimulationDuration(i);
newChunk->m_buffer.appendStimulation(id, timestamp, duration);
}
}
newChunk->m_StartTime = chunk->m_StartTime;
newChunk->m_EndTime = chunk->m_EndTime;
target->push(newChunk);
}
target->getEnd().m_StartTime = typedSrc->getEnd().m_StartTime;
target->getEnd().m_EndTime = typedSrc->getEnd().m_EndTime;
return target;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,197 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <iostream>
#include <algorithm>
#include "StreamBundle.h"
#include "Stream.h"
#include "StreamFactory.h"
namespace OpenViBE {
namespace Tracker {
bool StreamBundle::deepCopy(const StreamBundle& other)
{
bool retVal = true;
// Clear streams
initialize();
// Copy each stream
for (size_t i = 0; i < other.getNumStreams(); ++i)
{
retVal &= createStream(i, other.getStream(i)->getTypeIdentifier());
retVal &= getStream(i)->copy(*other.getStream(i));
}
m_source = other.getSource();
setDirtyBit(other.getDirtyBit());
return retVal;
}
bool StreamBundle::copyFrom(StreamBundle& other)
{
initialize();
for (const auto& str : other.getAllStreams()) { if (str->getSelected()) { this->setStream(m_streams.size(), str); } }
setSource(other.getSource());
setDirtyBit(other.getDirtyBit());
// n.b. this will affect the streams we copied
rewind();
return true;
}
bool StreamBundle::initialize()
{
//reset first
uninitialize();
m_dirty = true;
// log() << Kernel::LogLevel_Debug << "Streams initialized ok\n";
return true;
}
bool StreamBundle::uninitialize()
{
// Since m_Streams are shared pointers, no need to delete them
m_streams.clear();
return true;
}
bool StreamBundle::rewind()
{
bool returnValue = true;
std::for_each(m_streams.begin(), m_streams.end(), [&returnValue](const StreamPtr& entry) { if (entry) { returnValue &= entry->reset(); } });
return returnValue;
}
bool StreamBundle::createStream(const size_t index, const CIdentifier& typeID)
{
if (index >= m_streams.size()) { m_streams.resize(index + 1, nullptr); }
if (m_streams[index] == nullptr)
{
const StreamPtr stream = StreamFactory::getStream(m_kernelCtx, typeID);
if (!stream) { return false; }
m_streams[index] = stream;
setDirtyBit(true);
return true;
}
log() << Kernel::LogLevel_Error << "Error: Slot " << index << " is already used\n";
return false;
}
bool StreamBundle::deleteStream(const size_t index)
{
if (index > m_streams.size())
{
log() << Kernel::LogLevel_Error << "Error: Stream index exceeds array size\n";
return false;
}
// m_Streams is shared ptrs, no need to delete
m_streams.erase(m_streams.begin() + index);
setDirtyBit(true);
return true;
}
bool StreamBundle::getNextStreamIndex(size_t& index) const
{
if (m_streams.empty()) { return false; }
// Find the stream with the earliest chunk, return the stream
CTime earliestTime = CTime::max();
bool foundSomething = false;
for (size_t i = 0; i < m_streams.size(); ++i)
{
const StreamPtr ptr = m_streams[i];
CTime startTime = CTime::min(), endTime = CTime::min();
if (ptr && ptr->peek(startTime, endTime) && startTime < earliestTime)
{
earliestTime = startTime;
index = int(i);
foundSomething = true;
}
}
if (!foundSomething)
{
//log() << Kernel::LogLevel_Info << "All streams exhausted\n";
return false;
}
return true;
}
StreamPtr StreamBundle::getNextStream(size_t& index)
{
index = -1;
if (getNextStreamIndex(index)) { return m_streams[index]; }
return nullptr;
}
CTime StreamBundle::getMaxDuration() const
{
CTime maxDuration = CTime::min();
for (size_t i = 0; i < m_streams.size(); ++i)
{
if (m_streams[i])
{
CTime streamDuration = m_streams[i]->getDuration();
maxDuration = std::max<CTime>(maxDuration, streamDuration);
}
}
return maxDuration;
}
bool StreamBundle::setStream(const size_t index, const std::shared_ptr<StreamBase>& ptr)
{
if (index >= m_streams.size()) { m_streams.resize(index + 1, nullptr); }
m_streams[index] = ptr;
setDirtyBit(true);
return true;
}
bool StreamBundle::swapStreams(const size_t idx1, const size_t idx2)
{
if (idx1 >= m_streams.size() || idx2 >= m_streams.size()) { return false; }
const auto it1 = m_streams.begin() + idx1;
const auto it2 = m_streams.begin() + idx2;
std::iter_swap(it1, it2);
setDirtyBit(true);
return true;
}
bool StreamBundle::moveStream(const size_t srcIdx, const size_t dstIdx)
{
if (srcIdx >= getNumStreams() || dstIdx >= getNumStreams()) { return false; }
if (srcIdx == dstIdx) { return true; }
const auto oldPtr = m_streams[srcIdx];
m_streams.erase(m_streams.begin() + srcIdx);
m_streams.insert(m_streams.begin() + dstIdx, oldPtr);
setDirtyBit(true);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,106 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// n.b. having this as a separate file so EBML/Demuxer dependencies do not get pulled into StreamBundle
#include <iostream>
#include <algorithm>
#include "StreamBundleImportExport.h"
#include "EBMLSourceFile.h"
#include "Demuxer.h"
#include "BoxAdapterGenericStreamWriter.h"
namespace OpenViBE {
namespace Tracker {
StreamBundle* readStreamBundleFromFile(const Kernel::IKernelContext& ctx, const char* filename, const bool memorySaveMode)
{
StreamBundle* newTrack = new StreamBundle(ctx);
EBMLSourceFile origin(ctx);
if (!origin.initialize(filename))
{
ctx.getLogManager() << Kernel::LogLevel_Error << "Unable to read from file " << filename << "\n";
delete newTrack;
return nullptr;
}
// Construct a track from the origin stream
Demuxer demux(ctx, origin, *newTrack);
if (memorySaveMode)
{
// Try to get the stream types but don't pull the buffers
bool go = true;
while (go)
{
if (!demux.step())
{
ctx.getLogManager() << Kernel::LogLevel_Trace << "Demuxer EOF\n";
go = false;
}
// n.b. here we use a heuristic that if at least a single chunk has been loaded,
// then all the headers should have been demuxed before that. There is no guarantee
// that this would always be the case, but it is very likely if the stream originates from
// an .ov file created by OpenViBE
for (size_t j = 0; j < newTrack->getNumStreams(); ++j)
{
if (newTrack->getStream(j)->getDuration().time() > 0)
{
go = false;
break;
}
}
}
origin.uninitialize();
// Release memory
for (size_t i = 0; i < newTrack->getNumStreams(); ++i) { newTrack->getStream(i)->clear(); }
}
else
{
// Just read the whole file to memory
while (true)
{
if (!demux.step())
{
ctx.getLogManager() << Kernel::LogLevel_Trace << "Demuxer EOF\n";
break;
}
}
}
origin.uninitialize();
newTrack->setDirtyBit(false);
return newTrack;
}
bool saveStreamBundleToFile(const Kernel::IKernelContext& ctx, StreamBundle* track, const char* filename)
{
BoxAdapterGenericStreamWriter writer(ctx, *track, filename);
bool retval = true;
retval &= writer.initialize();
retval &= writer.spool(false);
retval &= writer.uninitialize();
if (retval)
{
track->setSource(filename);
retval &= true;
track->setDirtyBit(false);
}
return retval;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,322 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @todo add horizontal scaling support
// @todo add event handlers
// @todo add ruler, stimulations, channel names, a million of other things
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include <mensia/advanced-visualization.hpp>
#include <m_GtkGL.hpp>
#include "StreamRendererBase.h"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererBase::initialize()
{
GtkBuilder* builder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/advanced-visualization.ui";
GError* errorCode = nullptr;
if (!gtk_builder_add_from_file(builder, filename, &errorCode))
{
log() << Kernel::LogLevel_Error << "Problem loading [" << filename << "] : "
<< (errorCode ? errorCode->code : 0) << " " << (errorCode ? errorCode->message : "") << "\n";
g_object_unref(builder);
return false;
}
GtkWidget* window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
m_main = GTK_WIDGET(gtk_builder_get_object(builder, "table"));
// ::GtkWidget* toolbar = GTK_WIDGET(gtk_builder_get_object(pBuilder, "toolbar-window"));
gtk_widget_ref(m_main);
gtk_container_remove(GTK_CONTAINER(window), m_main);
// We keep one ref, the caller can unref after having assigned the widget
m_viewport = GTK_WIDGET(gtk_builder_get_object(builder, "viewport"));
m_top = GTK_WIDGET(gtk_builder_get_object(builder, "label_top")); // caption
m_left = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea_left"));
m_right = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea_right"));
m_bottom = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea_bottom"));
m_cornerLeft = GTK_WIDGET(gtk_builder_get_object(builder, "label_corner_left"));
m_cornerRight = GTK_WIDGET(gtk_builder_get_object(builder, "label_corner_right"));
// @note this or something similar is needed or otherwise the widgets will all be crammed to the
// same fixed size aperture with no scrolling
// @todo give users some scaling options
// @fixme for some reason this causes a mess on the 'message' bar below in the UI, as if it didn't redraw properly
gtk_widget_set_size_request(m_main, 640, 200);
m_color.r = 1;
m_color.g = 1;
m_color.b = 1;
g_object_unref(builder);
builder = nullptr;
return true;
}
bool StreamRendererBase::uninitialize()
{
for (size_t i = 0; i < m_renderers.size(); ++i) { AdvancedVisualization::IRenderer::release(m_renderers[i]); }
m_renderers.clear();
if (m_rendererCtx)
{
delete m_rendererCtx;
m_rendererCtx = nullptr;
}
if (m_subRendererCtx)
{
delete m_subRendererCtx;
m_subRendererCtx = nullptr;
}
if (m_ruler)
{
delete m_ruler;
m_ruler = nullptr;
}
return true;
}
bool StreamRendererBase::setTitle(const char* title)
{
if (title) { gtk_label_set_text(GTK_LABEL(m_top), title); }
else { gtk_label_set_text(GTK_LABEL(m_top), ""); }
return true;
}
bool StreamRendererBase::setRulerVisibility(const bool isVisible)
{
m_isScaleVisible = isVisible;
return updateRulerVisibility();
}
bool StreamRendererBase::updateRulerVisibility()
{
/*
if ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pScaleVisible)) ? true : false) != m_isScaleVisible)
{
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pScaleVisible), m_isScaleVisible);
}
*/
void (*action)(GtkWidget*) = m_isScaleVisible ? gtk_widget_show : gtk_widget_hide;
(*action)(this->m_top);
(*action)(this->m_left);
(*action)(this->m_right);
(*action)(this->m_bottom);
(*action)(this->m_cornerLeft);
(*action)(this->m_cornerRight);
return true;
}
bool StreamRendererBase::realize()
{
gtk_widget_realize(m_top);
gtk_widget_realize(m_left);
gtk_widget_realize(m_right);
gtk_widget_realize(m_bottom);
gtk_widget_realize(m_viewport);
gtk_widget_realize(m_main);
return true;
}
bool StreamRendererBase::redraw(const bool bImmediate /* = false */)
{
m_gtkGLWidget.redraw(bImmediate);
m_gtkGLWidget.redrawLeft(bImmediate);
m_gtkGLWidget.redrawRight(bImmediate);
m_gtkGLWidget.redrawBottom(bImmediate);
return true;
}
bool StreamRendererBase::reshape(const uint32_t width, const uint32_t height)
{
m_width = uint32_t(width);
m_height = uint32_t(height);
m_rendererCtx->setAspect(width * 1.0F / height);
return true;
}
bool StreamRendererBase::draw()
{
StreamRendererBase::preDraw();
glPushAttrib(GL_ALL_ATTRIB_BITS);
//::glClearColor(1.0,1.0,1.0,1.0);
glColor4f(m_color.r, m_color.g, m_color.b, m_rendererCtx->getTranslucency());
if (m_rotate)
{
glScalef(1, -1, 1);
glRotatef(-90, 0, 0, 1);
}
m_renderers[0]->render(*m_rendererCtx);
glPopAttrib();
StreamRendererBase::postDraw();
return true;
}
void StreamRendererBase::drawLeft() { if (m_ruler) { m_ruler->doRenderLeft(m_left); } }
void StreamRendererBase::drawRight() { if (m_ruler) { m_ruler->doRenderRight(m_right); } }
void StreamRendererBase::drawBottom() { if (m_ruler) { m_ruler->doRenderBottom(m_bottom); } }
bool StreamRendererBase::preDraw()
{
this->updateRulerVisibility();
// auto m_sColorGradient=CString("0:0,0,0; 100:100,100,100");
const char* gradient =
"0:100, 100, 100; 12:50, 100, 100; 25:0, 50, 100; 38:0, 0, 50; 50:0, 0, 0; 62:50, 0, 0; 75:100, 50, 0; 88:100, 100, 50; 100:100, 100, 100";
if (!m_textureID) { m_textureID = m_gtkGLWidget.createTexture(gradient); }
glBindTexture(GL_TEXTURE_1D, m_textureID);
m_rendererCtx->setAspect(m_viewport->allocation.width * 1.0F / m_viewport->allocation.height);
return true;
}
bool StreamRendererBase::postDraw()
{
glPushAttrib(GL_ALL_ATTRIB_BITS);
if (m_ruler) { m_ruler->doRender(); }
glPopAttrib();
return true;
}
bool StreamRendererBase::mouseButton(int /*x*/, int /*y*/, const int button, const int status)
{
m_buttons[button] = status;
/*
if (button == 1 && status == 1)
{
m_isScaleVisible = !m_isScaleVisible;
m_pRendererContext->setScaleVisibility(m_isScaleVisible);
}
*/
return true;
}
bool StreamRendererBase::mouseMotion(const int x, const int y)
{
if (!m_mouseInitialized)
{
m_mouseX = x;
m_mouseY = y;
m_mouseInitialized = true;
}
if (m_buttons[3])
{
const float value = powf(0.99F, float(y - m_mouseY));
// std::cout << "scale " << value << "\n";
m_rendererCtx->scaleBy(value);
redraw();
}
if (m_buttons[2])
{
const float value = powf(0.99F, float(y - m_mouseY));
// std::cout << "zoom " << value << "\n";
m_rendererCtx->zoomBy(value);
}
if (m_buttons[1])
{
// std::cout << "Rotate\n";
m_rendererCtx->rotateByY(float(x - m_mouseX) * 0.1F);
m_rendererCtx->rotateByX(float(y - m_mouseY) * 0.1F);
}
m_mouseX = x;
m_mouseY = y;
return true;
}
bool StreamRendererBase::finalize()
{
for (size_t i = 0; i < m_renderers.size(); ++i)
{
m_renderers[i]->rebuild(*m_rendererCtx);
m_renderers[i]->refresh(*m_rendererCtx);
}
redraw(true);
return true;
}
bool StreamRendererBase::restoreSettings(const std::string& prefix)
{
if (!m_rendererCtx) { return false; }
// Lets see if we have a scale token
const std::string token = std::string("${") + prefix + "_Scale}";
const float newScale = float(m_kernelCtx.getConfigurationManager().expandAsFloat(token.c_str(), m_rendererCtx->getScale()));
m_rendererCtx->setScale(newScale);
return true;
}
bool StreamRendererBase::storeSettings(const std::string& prefix)
{
if (!m_rendererCtx) { return false; }
const std::string token = prefix + "_Scale";
std::stringstream value;
value << m_rendererCtx->getScale();
m_kernelCtx.getConfigurationManager().addOrReplaceConfigurationToken(token.c_str(), value.str().c_str());
return true;
}
CString StreamRendererBase::renderAsText(const size_t indent) const
{
return (std::string(indent, ' ') + "Detail printing unimplemented for stream type or placeholder renderer in use\n").c_str();
}
void add_column(GtkTreeView* treeView, const char* name, const uint32_t id, const uint32_t minWidth)
{
GtkTreeViewColumn* column = gtk_tree_view_column_new();
GtkCellRenderer* cell = gtk_cell_renderer_text_new();
gtk_tree_view_column_set_title(column, name);
gtk_tree_view_column_pack_start(column, cell, TRUE);
gtk_tree_view_column_set_attributes(column, cell, "text", id, nullptr);
gtk_tree_view_column_set_sort_column_id(column, id);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_expand(column, TRUE);
gtk_tree_view_column_set_resizable(column, TRUE);
gtk_tree_view_column_set_min_width(column, minWidth);
gtk_tree_view_append_column(treeView, column);
gtk_tree_view_column_set_sort_indicator(column, TRUE);
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,47 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "StreamRendererChannelLocalization.h"
namespace OpenViBE {
namespace Tracker {
CString StreamRendererChannelLocalization::renderAsText(const size_t indent) const
{
const TypeChannelLocalization::Header& hdr = m_Stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Dynamic: " << (hdr.m_Dynamic ? "True" : "False") << std::endl;
for (size_t i = 0; i < m_Stream->getChunkCount(); ++i)
{
const TypeChannelLocalization::Buffer* buf = m_Stream->getChunk(i);
ss << std::string(indent, ' ') << "Configuration at time " << buf->m_StartTime.toSeconds() << "s:" << std::endl;
const double* ptr = buf->m_buffer.getBuffer();
for (uint32_t chn = 0; chn < buf->m_buffer.getDimensionSize(0); ++chn)
{
ss << std::string(indent, ' ') << " Channel " << chn << " (" << hdr.m_Header.getDimensionLabel(0, chn) << ") " << "x=" << ptr[chn * 3 + 0] << " "
<< "y=" << ptr[chn * 3 + 1] << " " << "z=" << ptr[chn * 3 + 2] << std::endl;
}
}
return ss.str().c_str();
}
bool StreamRendererChannelLocalization::showChunkList() { return StreamRendererLabel::showChunkList("Channel localization stream details"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,49 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include "StreamRendererChannelUnits.h"
#include <iostream>
namespace OpenViBE {
namespace Tracker {
CString StreamRendererChannelUnits::renderAsText(const size_t indent) const
{
const TypeChannelUnits::Header& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Dynamic: "
<< (hdr.m_Dynamic ? "True" : "False") << std::endl;
for (size_t i = 0; i < m_stream->getChunkCount(); ++i)
{
const TypeChannelUnits::Buffer* buf = m_stream->getChunk(i);
ss << std::string(indent, ' ') << "Configuration at time " << buf->m_StartTime.toSeconds() << "s:" << std::endl;
const double* ptr = buf->m_buffer.getBuffer();
for (uint32_t chn = 0; chn < buf->m_buffer.getDimensionSize(0); ++chn)
{
const CString unit = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_MeasurementUnit, uint64_t(ptr[chn * 2 + 0]));
const CString factor = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Factor, uint64_t(ptr[chn * 2 + 1]));
ss << std::string(indent, ' ') << " Channel " << chn << " (" << hdr.m_Header.getDimensionLabel(0, chn) << ") " << "Unit: " << unit << ", Factor: "
<< factor << std::endl;
}
}
return ss.str().c_str();
}
bool StreamRendererChannelUnits::showChunkList() { return StreamRendererLabel::showChunkList("Channel units stream details"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,45 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "StreamRendererExperimentInfo.h"
namespace OpenViBE {
namespace Tracker {
CString StreamRendererExperimentInfo::renderAsText(const size_t indent) const
{
const TypeExperimentInfo::Header& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Experiment id: " << hdr.m_ExperimentID << std::endl;
ss << std::string(indent, ' ') << "Experiment date: " << hdr.m_ExperimentDate << std::endl;
ss << std::string(indent, ' ') << "Subject id: " << hdr.m_SubjectID << std::endl;
ss << std::string(indent, ' ') << "Subject name: " << hdr.m_SubjectName << std::endl;
ss << std::string(indent, ' ') << "Subject age: " << hdr.m_SubjectAge << std::endl;
ss << std::string(indent, ' ') << "Subject gender: " << hdr.m_SubjectGender << std::endl;
ss << std::string(indent, ' ') << "Laboratory id: " << hdr.m_LaboratoryID << std::endl;
ss << std::string(indent, ' ') << "Laboratory name: " << hdr.m_LaboratoryName << std::endl;
ss << std::string(indent, ' ') << "Technician id: " << hdr.m_TechnicianID << std::endl;
ss << std::string(indent, ' ') << "Technician name: " << hdr.m_TechnicianName << std::endl;
// ss << string(indent, ' ') << "Channels: " << m_Header.m_header.getDimensionSize(0) << std::endl;
// ss << string(indent, ' ') << "Samples per chunk: " << m_Header.m_header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererExperimentInfo::showChunkList() { return StreamRendererLabel::showChunkList("Experiment information stream details"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,82 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "StreamRendererLabel.h"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererLabel::initialize()
{
m_main = gtk_frame_new("");
m_label = gtk_label_new("");
gtk_label_set_justify(GTK_LABEL(m_label), GTK_JUSTIFY_LEFT);
gtk_container_add(GTK_CONTAINER(m_main), m_label);
gtk_widget_ref(m_main);
// @fixme hardcoded numbers likely not such a great idea as different font sizes etc
gtk_widget_set_size_request(m_main, 640, 40);
gtk_widget_show(m_main);
gtk_widget_show(m_label);
return true;
}
bool StreamRendererLabel::setTitle(const char* title)
{
gtk_label_set(GTK_LABEL(m_label), title);
return true;
}
// Instead of showing chunk list, this prints the stream structure information
// in a window. How it works is that a derived class can implement a text render
// routine which this function then calls. This way each derived class
// doesn't need to copy-paste the identical window code.
bool StreamRendererLabel::showChunkList(const char* title)
{
// @fixme Not very pretty but better than nothing; here we have the benefit
// that since we make a new builder here, reusing the workspace information
// window does not mess up the real one
GtkBuilder* builder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/tracker.ui";
if (!gtk_builder_add_from_file(builder, filename, nullptr))
{
std::cout << "Problem loading [" << filename << "]\n";
return false;
}
GtkWidget* window = GTK_WIDGET(gtk_builder_get_object(builder, "tracker-workspace_information"));
GtkTextBuffer* buffer = GTK_TEXT_BUFFER(gtk_builder_get_object(builder, "tracker-textbuffer-workspace_information"));
gtk_window_set_title(GTK_WINDOW(window), title);
// Hide instead of destroy on closing the window
g_signal_connect(window, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
const CString details = renderAsText(0);
gtk_text_buffer_set_text(buffer, details.toASCIIString(), -1);
gtk_widget_show_all(window);
gtk_window_present(GTK_WINDOW(window));
g_object_unref(builder);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,235 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @todo add horizontal scaling support
// @todo add event handlers
// @todo add ruler, stimulations, channel names, a million of other things
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <m_GtkGL.hpp>
#include <system/ovCTime.h>
#include "StreamRendererMatrix.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
#include "ruler/mCRulerRightFrequency.hpp"
#include "ruler/mCRulerRightTexture.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererMatrix::initialize()
{
const TypeMatrix::Buffer* firstChunk = m_stream->getChunk(0);
if (firstChunk)
{
m_chunkDuration = firstChunk->m_EndTime - firstChunk->m_StartTime;
if (firstChunk->m_buffer.getDimensionCount() == 1)
{
// Degenerate case like a feature vector
m_nRows = firstChunk->m_buffer.getDimensionSize(0);
m_nCols = 0;
}
else if (firstChunk->m_buffer.getDimensionCount() == 2)
{
m_nRows = firstChunk->m_buffer.getDimensionSize(0);
m_nCols = firstChunk->m_buffer.getDimensionSize(1);
}
else if (firstChunk->m_buffer.getDimensionCount() > 2)
{
std::cout << "Warning: The matrix renderer does not work correctly if dims>2 (for tensors)\n";
m_nRows = firstChunk->m_buffer.getDimensionSize(0);
m_nCols = firstChunk->m_buffer.getDimensionSize(1);
}
else { log() << Kernel::LogLevel_Error << "Error: Dimension count " << firstChunk->m_buffer.getDimensionCount() << " not supported\n"; }
}
else
{
std::cout << "Stream is empty\n";
return false;
}
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setTimeLocked(true);
m_rendererCtx->setPositiveOnly(false);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Matrix);
m_rendererCtx->setSampleDuration(m_chunkDuration.time());
m_rendererCtx->setParentRendererContext(&AdvancedVisualization::getContext());
m_rendererCtx->setAxisDisplay(true);
auto& hdr = m_stream->getHeader();
for (uint32_t j = 0; j < m_nRows; ++j) { m_rendererCtx->addChannel(std::string(hdr.m_Header.getDimensionLabel(0, j))); }
m_swap.resize(m_nRows);
m_renderers.push_back(AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Bitmap, false));
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::CRulerProgressV, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::IRuler>,
AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerRightTexture>>>;
// m_pRuler = new TRulerPair < TRulerConditionalPair < CRulerBottomTime, CRulerBottomCount, CRulerConditionIsTimeLocked >, TRulerPair < TRulerAutoType < IRuler, IRuler, CRulerRightFrequency >, TRulerPair < CRulerLeftChannelNames, CRulerProgressV > > >;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(m_renderers[0]);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
return true;
}
bool StreamRendererMatrix::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
const uint32_t numBuffers = ((m_endTime - m_startTime).ceil().time() / m_chunkDuration.time());
m_rendererCtx->setElementCount(numBuffers);
m_renderers[0]->clear(0);
m_renderers[0]->setSampleCount(numBuffers); // $$$
m_renderers[0]->setChannelCount(m_nRows);
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
return true;
}
bool StreamRendererMatrix::push(const TypeMatrix::Buffer& chunk, bool /*zeroInput*/)
{
#if 0
static uint32_t pushed = 0;
std::cout << "Push spec chk " << pushed << " " << chunk.m_buffer.getDimensionSize(0)
<< " " << chunk.m_buffer.getDimensionSize(1) << " "
<< CTime(chunk.m_startTime).toSeconds() << ","
<< CTime(chunk.m_endTime).toSeconds()
<< "\n";
std::cout << pushed << " first bytes "
<< chunk.m_buffer.getBuffer()[0]
<< chunk.m_buffer.getBuffer()[1]
<< chunk.m_buffer.getBuffer()[2]
<< chunk.m_buffer.getBuffer()[3]
<< "\n";
pushed++;
#endif
m_rendererCtx->setSpectrumFrequencyRange(uint32_t((uint64_t(m_nRows) << 32) / m_chunkDuration.time()));
// Handle the degenerate case NumCols=0
const size_t actualCols = std::max<size_t>(m_nCols, 1);
// Feed renderer with actual samples
for (uint32_t j = 0; j < actualCols; ++j)
{
for (uint32_t k = 0; k < m_nRows; ++k) { m_swap[k] = float(chunk.m_buffer.getBuffer()[k * actualCols + j]); }
m_renderers[0]->feed(&m_swap[0]);
}
return true;
}
bool StreamRendererMatrix::draw()
{
StreamRendererMatrix::preDraw();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glColor4f(m_color.r, m_color.g, m_color.b, m_rendererCtx->getTranslucency());
m_renderers[0]->render(*m_rendererCtx);
glPopAttrib();
StreamRendererMatrix::postDraw();
return true;
}
bool StreamRendererMatrix::preDraw()
{
this->updateRulerVisibility();
// auto m_sColorGradient=CString("0:0,0,0; 100:100,100,100");
const std::string gradient = "0:0, 0, 50; 25:0, 100, 100; 50:0, 50, 0; 75:100, 100, 0; 100:75, 0, 0";
// auto m_sColorGradient = CString("0:100, 100, 100; 12:50, 100, 100; 25:0, 50, 100; 38:0, 0, 50; 50:0, 0, 0; 62:50, 0, 0; 75:100, 50, 0; 88:100, 100, 50; 100:100, 100, 100");
if (!m_textureID) { m_textureID = m_gtkGLWidget.createTexture(gradient); }
glBindTexture(GL_TEXTURE_1D, m_textureID);
m_rendererCtx->setAspect(m_viewport->allocation.width * 1.0F / m_viewport->allocation.height);
return true;
}
bool StreamRendererMatrix::finalize()
{
m_renderers[0]->rebuild(*m_rendererCtx);
m_renderers[0]->refresh(*m_rendererCtx);
redraw(true);
return true;
}
CString StreamRendererMatrix::renderAsText(const size_t indent) const
{
std::stringstream ss;
ss << std::string(indent, ' ') << "Rows: " << m_nRows << std::endl;
ss << std::string(indent, ' ') << "Cols: " << m_nCols << std::endl;
// ss << std::string(indent, ' ') << "Channels: " << m_Header.m_header.getDimensionSize(0) << std::endl;
// ss << std::string(indent, ' ') << "Samples per chunk: " << m_Header.m_header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererMatrix::mouseButton(const int x, const int y, const int button, const int status)
{
//if (button == 3 && status == 1) { showChunkList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
bool StreamRendererMatrix::showChunkList() { return showMatrixList<TypeMatrix>(m_stream, &m_streamListWindow, "List of chunks for Matrix stream"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,153 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <mTGtkGLWidget.hpp>
#include <m_GtkGL.hpp>
#include "StreamRendererSignal.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererSignal::initialize()
{
// TRendererStimulation < false, CRendererLine >:new CRendererLine
AdvancedVisualization::IRenderer* pRenderer = AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Line, true);
if (pRenderer == nullptr) { return false; }
const TypeSignal::Buffer* firstChunk = m_stream->getChunk(0);
if (!firstChunk) { return false; }
m_nChannel = firstChunk->m_buffer.getDimensionSize(0);
m_samplesPerChunk = firstChunk->m_buffer.getDimensionSize(1);
m_chunkDuration = firstChunk->m_EndTime - firstChunk->m_StartTime;
// Creates renderer context
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setScale(1.0);
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Signal);
const CTime sampleDuration = CTime(m_chunkDuration.time() / m_samplesPerChunk);
m_rendererCtx->setSampleDuration(sampleDuration.time());
const TypeSignal::Header& hdr = m_stream->getHeader();
for (uint32_t i = 0; i < m_nChannel; ++i)
{
const char* label = hdr.m_Header.getDimensionLabel(0, i);
m_rendererCtx->addChannel(std::string(label));
}
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::CRulerProgressV, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::IRuler>,
AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerRightScale>>>;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(pRenderer);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
m_renderers.push_back(pRenderer);
return true;
}
bool StreamRendererSignal::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
// std::cout << "Overridden signal renderer reset\n";
m_renderers[0]->clear(0);
// std::cout << "Start time is " << CTime(m_StartTime).toSeconds()
// << " end is " << CTime(m_EndTime).toSeconds() << "\n";
const uint64_t chunkCount = (m_endTime - m_startTime).ceil().time() / m_chunkDuration.time();
const uint32_t numSamples = uint32_t(m_samplesPerChunk * chunkCount);
m_renderers[0]->setSampleCount(numSamples);
m_renderers[0]->setChannelCount(m_nChannel);
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
// m_renderers[0]->setHistoryDrawIndex(samplesBeforeStart);
m_renderers[0]->rebuild(*m_rendererCtx);
return true;
}
bool StreamRendererSignal::push(const TypeSignal::Buffer& chunk, const bool zeroInput /* = false */)
{
std::vector<float> tmp;
if (!zeroInput)
{
tmp.resize(chunk.m_buffer.getBufferElementCount());
for (size_t i = 0; i < chunk.m_buffer.getBufferElementCount(); ++i) { tmp[i] = float(chunk.m_buffer.getBuffer()[i]); }
}
else { tmp.resize(chunk.m_buffer.getBufferElementCount(), 0); }
m_renderers[0]->feed(&tmp[0], chunk.m_buffer.getDimensionSize(1));
return true;
}
CString StreamRendererSignal::renderAsText(const size_t indent) const
{
auto& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Sampling rate: " << hdr.m_Sampling << "hz" << std::endl;
ss << std::string(indent, ' ') << "Channels: " << hdr.m_Header.getDimensionSize(0) << std::endl;
ss << std::string(indent, ' ') << "Samples per chunk: " << hdr.m_Header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererSignal::mouseButton(const int x, const int y, const int button, const int status)
{
//if (button == 3 && status == 1) { showChunkList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
bool StreamRendererSignal::showChunkList() { return showMatrixList<TypeSignal>(m_stream, &m_streamListWindow, "List of chunks for Signal stream"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,270 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @todo add horizontal scaling support
// @todo add event handlers
// @todo add ruler, stimulations, channel names, a million of other things
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <m_GtkGL.hpp>
#include <system/ovCTime.h>
#include "StreamRendererSpectrum.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
#include "ruler/mCRulerRightFrequency.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererSpectrum::initialize()
{
const TypeSpectrum::Buffer* firstChunk = m_stream->getChunk(0);
if (!firstChunk) { return false; }
m_nChannel = firstChunk->m_buffer.getDimensionSize(0);
m_spectrumElements = firstChunk->m_buffer.getDimensionSize(1);
m_chunkDuration = firstChunk->m_EndTime - firstChunk->m_StartTime;
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setTimeLocked(true);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Spectrum);
m_rendererCtx->setSampleDuration(m_chunkDuration.time());
const TypeSpectrum::Header& hdr = m_stream->getHeader();
for (uint32_t j = 0; j < m_nChannel; ++j)
{
const char* name = hdr.m_Header.getDimensionLabel(0, j);
m_rendererCtx->addChannel(std::string(name));
}
m_subRendererCtx = new AdvancedVisualization::CRendererContext();
m_subRendererCtx->clear();
m_subRendererCtx->setParentRendererContext(m_rendererCtx);
m_subRendererCtx->setTimeLocked(true);
m_subRendererCtx->setStackCount(m_nChannel);
m_subRendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Spectrum);
m_subRendererCtx->setSampleDuration(m_chunkDuration.time());
for (uint32_t j = 0; j < m_spectrumElements; ++j)
{
std::stringstream ss;
ss << j;
m_subRendererCtx->addChannel(ss.str());
}
m_swaps.resize(m_spectrumElements);
for (size_t i = 0; i < m_renderers.size(); ++i) { AdvancedVisualization::IRenderer::release(m_renderers[i]); }
m_renderers.clear();
m_renderers.resize(m_nChannel);
for (uint32_t j = 0; j < m_nChannel; ++j)
{
m_renderers[j] = AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Bitmap, false);
m_renderers[j]->setChannelCount(m_spectrumElements);
}
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::IRuler,
AdvancedVisualization::CRulerRightFrequency>, AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerProgressV>>>;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(m_renderers[0]);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
m_rotate = true;
return true;
}
bool StreamRendererSpectrum::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
// Each spectrum buffer has one spectrum per channel, so numBuffers is just:
// ( @todo: is this really needed in addition to setSampleCount? )
const size_t numBuffers = (m_endTime - m_startTime).ceil().time() / m_chunkDuration.time();
m_rendererCtx->setElementCount(numBuffers);
for (size_t j = 0; j < m_nChannel; ++j)
{
m_renderers[j]->clear(0);
m_renderers[j]->setSampleCount(numBuffers); // $$$
}
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
return true;
}
// Spectrum chunks are organized as [channel x freqs], so to push
// with freqs on the y axis for each subrenderer, we transpose
bool StreamRendererSpectrum::push(const TypeSpectrum::Buffer& chunk, const bool zeroInput /* = false */)
{
#if 0
std::cout << "Push spec chk " << m_Pushed << " " << chunk.m_buffer.getDimensionSize(0) << " " << chunk.m_buffer.getDimensionSize(1) << " "
<< CTime(chunk.m_startTime).toSeconds() << "," << CTime(chunk.m_endTime).toSeconds() << "\n";
std::cout << m_Pushed << " first bytes " << chunk.m_buffer.getBuffer()[0] << chunk.m_buffer.getBuffer()[1]
<< chunk.m_buffer.getBuffer()[2] << chunk.m_buffer.getBuffer()[3] << "\n";
m_Pushed++;
#endif
const size_t numFreq = chunk.m_buffer.getDimensionSize(1);
m_rendererCtx->setSpectrumFrequencyRange(uint32_t((uint64_t(numFreq) << 32) / m_chunkDuration.time()));
for (uint32_t j = 0; j < m_renderers.size(); ++j)
{
if (!zeroInput)
{
// Feed renderer with actual samples
for (uint32_t k = 0; k < numFreq; ++k) { m_swaps[numFreq - k - 1] = float(chunk.m_buffer.getBuffer()[j * numFreq + k]); }
}
else { std::fill(m_swaps.begin(), m_swaps.end(), 0.0F); }
m_renderers[j]->feed(&m_swaps[0]);
}
return true;
}
bool StreamRendererSpectrum::draw()
{
StreamRendererSpectrum::preDraw();
if (m_rendererCtx->getSelectedCount() != 0)
{
glPushMatrix();
glScalef(1, 1.0F / m_rendererCtx->getSelectedCount(), 1);
for (size_t i = 0; i < m_rendererCtx->getSelectedCount(); ++i)
{
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
glColor4f(m_color.r, m_color.g, m_color.b, m_rendererCtx->getTranslucency());
glTranslatef(0, float(m_rendererCtx->getSelectedCount() - i) - 1.0F, 0);
if (!m_rotate)
{
glScalef(1, -1, 1);
glRotatef(-90, 0, 0, 1);
}
m_subRendererCtx->setAspect(m_rendererCtx->getAspect());
m_subRendererCtx->setStackCount(m_rendererCtx->getSelectedCount());
m_subRendererCtx->setStackIndex(i);
m_renderers[m_rendererCtx->getSelected(i)]->render(*m_subRendererCtx);
/*
if (0) //if (bDrawBorders)
{
glDisable(GL_TEXTURE_1D);
glDisable(GL_BLEND);
glColor3f(0, 0, 0);
glBegin(GL_LINE_LOOP);
glVertex2f(0, 0);
glVertex2f(1, 0);
glVertex2f(1, 1);
glVertex2f(0, 1);
glEnd();
}
*/
glPopMatrix();
glPopAttrib();
}
glPopMatrix();
}
StreamRendererSpectrum::postDraw();
return true;
}
bool StreamRendererSpectrum::preDraw()
{
this->updateRulerVisibility();
// auto m_sColorGradient=CString("0:0,0,0; 100:100,100,100");
const std::string gradient =
"0:100, 100, 100; 12:50, 100, 100; 25:0, 50, 100; 38:0, 0, 50; 50:0, 0, 0; 62:50, 0, 0; 75:100, 50, 0; 88:100, 100, 50; 100:100, 100, 100";
if (!m_textureID) { m_textureID = m_gtkGLWidget.createTexture(gradient); }
glBindTexture(GL_TEXTURE_1D, m_textureID);
m_rendererCtx->setAspect(m_viewport->allocation.width * 1.0F / m_viewport->allocation.height);
return true;
}
bool StreamRendererSpectrum::finalize()
{
for (size_t i = 0; i < m_renderers.size(); ++i)
{
m_renderers[i]->rebuild(*m_subRendererCtx);
m_renderers[i]->refresh(*m_subRendererCtx);
}
redraw(true);
return true;
}
CString StreamRendererSpectrum::renderAsText(const size_t indent) const
{
auto& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Sampling rate: " << hdr.m_Sampling << "hz" << std::endl;
ss << std::string(indent, ' ') << "Channels: " << hdr.m_Header.getDimensionSize(0) << std::endl;
ss << std::string(indent, ' ') << "Abscissas per spectrum: " << hdr.m_Abscissas.getBufferElementCount() << std::endl;
// ss << std::string(indent, ' ') << "Channels: " << m_Header.m_header.getDimensionSize(0) << std::endl;
// ss << std::string(indent, ' ') << "Samples per chunk: " << m_Header.m_header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererSpectrum::mouseButton(const int x, const int y, const int button, const int status)
{
//if (button == 3 && status == 1) { showChunkList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
bool StreamRendererSpectrum::showChunkList() { return showMatrixList<TypeSpectrum>(m_stream, &m_streamListWindow, "List of chunks for Spectrum stream"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,207 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @note This renderer is intended to be used when there is only
// a stimulation track. More often, the Tracker users might want to
// see the stimulations overlayed on the signal track.
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <mTGtkGLWidget.hpp>
#include <m_GtkGL.hpp>
#include "StreamRendererStimulation.h"
#include "TypeStimulation.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererStimulation::initialize()
{
m_nChannel = 1;
m_sampling = 512;
m_samplesPerChunk = 32;
m_chunkDuration = CTime(m_sampling, m_samplesPerChunk);
// TRendererStimulation < false, CRendererLine >:new CRendererLine
AdvancedVisualization::IRenderer* renderer = AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Line, true);
if (renderer == nullptr) { return false; }
// Creates renderer context
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Signal);
m_rendererCtx->addChannel("Stims");
m_rendererCtx->setSampleDuration(CTime(m_sampling, 1).time());
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::CRulerProgressV, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::IRuler>,
AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerRightScale>>>;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(renderer);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
m_renderers.push_back(renderer);
gtk_widget_set_size_request(m_main, 640, 100);
return true;
}
bool StreamRendererStimulation::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
// std::cout << "Overridden stimulation renderer reset\n";
m_renderers[0]->clear(0);
const uint64_t chunkCount = (m_endTime - m_startTime).time() / m_chunkDuration.time();
const uint32_t numSamples = uint32_t(m_samplesPerChunk * chunkCount);
m_renderers[0]->setChannelCount(m_nChannel);
m_renderers[0]->setSampleCount(numSamples);
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
// Stick in empty chunks to get a background
std::vector<float> empty;
empty.resize(m_samplesPerChunk * 1, 0); // 1 channel
for (uint64_t i = 0; i < chunkCount; ++i) { m_renderers[0]->feed(&empty[0], m_samplesPerChunk); }
m_renderers[0]->rebuild(*m_rendererCtx);
return true;
}
bool StreamRendererStimulation::push(const TypeStimulation::Buffer& chunk, bool /*zeroInput*/)
{
for (size_t i = 0; i < chunk.m_buffer.getStimulationCount(); ++i)
{
m_renderers[0]->feed(chunk.m_buffer.getStimulationDate(i) - m_startTime.time(), chunk.m_buffer.getStimulationIdentifier(i));
}
return true;
}
CString StreamRendererStimulation::renderAsText(const size_t /*indent*/) const
{
// No specific details for stimulation streams
return "";
}
bool StreamRendererStimulation::showChunkList()
{
if (m_stimulationListWindow)
{
gtk_window_present(GTK_WINDOW(m_stimulationListWindow));
return true;
}
GtkBuilder* pBuilder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/tracker.ui";
if (!gtk_builder_add_from_file(pBuilder, filename, nullptr))
{
std::cout << "Problem loading [" << filename << "]\n";
return false;
}
m_stimulationListWindow = GTK_WIDGET(gtk_builder_get_object(pBuilder, "tracker-stimulation_list"));
GtkTreeView* channelTreeView = GTK_TREE_VIEW(gtk_builder_get_object(pBuilder, "tracker-stimulation_list-treeview"));
// GtkListStore* channelListStore = GTK_LIST_STORE(gtk_builder_get_object(pBuilder, "liststore_select"));
GtkTreeStore* channelListStore = gtk_tree_store_new(7, G_TYPE_UINT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_UINT64, G_TYPE_STRING,
G_TYPE_DOUBLE);
gtk_window_set_title(GTK_WINDOW(m_stimulationListWindow), "List of Stimulations in the stream");
add_column(channelTreeView, "Chunk#", 0, 20);
add_column(channelTreeView, "ChunkStart (s)", 1, 10);
add_column(channelTreeView, "ChunkEnd (s)", 2, 10);
add_column(channelTreeView, "Stim Time (s)", 3, 20);
add_column(channelTreeView, "Stim Id", 4, 5);
add_column(channelTreeView, "Stim Name", 5, 40);
add_column(channelTreeView, "Stim Duration", 6, 10);
gtk_tree_view_set_model(channelTreeView, GTK_TREE_MODEL(channelListStore));
GtkTreeIter it;
gtk_tree_store_clear(channelListStore);
// ::gtk_tree_view_set_model(m_pChannelTreeView, nullptr);
for (size_t i = 0; i < m_stream->getChunkCount(); ++i)
{
const TypeStimulation::Buffer* ptr = m_stream->getChunk(i);
if (!ptr) { break; }
for (uint32_t s = 0; s < ptr->m_buffer.getStimulationCount(); ++s)
{
const CString stimName = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(
OV_TypeId_Stimulation, ptr->m_buffer.getStimulationIdentifier(s));
gtk_tree_store_append(channelListStore, &it, nullptr);
gtk_tree_store_set(channelListStore, &it,
0, i,
1, ptr->m_StartTime.toSeconds(),
2, ptr->m_EndTime.toSeconds(),
3, CTime(ptr->m_buffer.getStimulationDate(s)).toSeconds(),
4, ptr->m_buffer.getStimulationIdentifier(s),
5, stimName.toASCIIString(),
6, CTime(ptr->m_buffer.getStimulationDuration(s)).toSeconds(), -1);
}
}
// GList* cols = gtk_tree_view_get_columns(m_pChannelTreeView);
// Hide instead of destroy on closing the window
g_signal_connect(m_stimulationListWindow, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
gtk_widget_show_all(GTK_WIDGET(m_stimulationListWindow));
g_object_unref(pBuilder);
return true;
}
bool StreamRendererStimulation::mouseButton(const int x, const int y, const int button, const int status)
{
// if (button == 3 && status == 1) { showStimulationList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,87 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include "Testclass.h"
// #include "Source.h"
#include "Stream.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
TestClass::TestClass(Kernel::IKernelContext& ctx) : m_Ctx(ctx)
{
std::cout << "Testing\n";
// Encoder2<TypeMatrix::Header, TypeMatrix::Buffer, TypeMatrix::End> testCoder(ctx);
// Encoder<TypeMatrix> testCoder(ctx);
#if 0
StreamSignal testStream(ctx);
StreamMatrix testStream2(ctx);
std::vector<Stream*> list;
list.push_back(&testStream);
list.push_back(&testStream2);
Stream* tmp = list[0];
std::cout << "First is " << ctx.getTypeManager().getTypeName(tmp->getTypeIdentifier()) << "\n";
#endif
//const CString eegFile = Directories::getDataDir() + CString("/scenarios/signals/bci-motor-imagery.ov");
//Source src;
//src.initialize(eegFile.toASCIIString());
#if 0
// Test code illustrating how to alter stimulation stream
for (auto it = m_Streams.begin();it != m_Streams.end(); ++it)
{
if(it->second->getTypeIdentifier() == OV_TypeId_Stimulations)
{
TypeError::Buffer *ptr = nullptr;
it->second->peek(CTime(5.0).time(), &ptr);
TypeStimulation::Buffer *ptr2 = reinterpret_cast<TypeStimulation::Buffer*>(ptr);
// std::cout << "cnt: " << ptr2->m_buffer.getStimulationCount() << "\n";
// Request early stop
ptr2->m_buffer.clear();
ptr2->m_buffer.appendStimulation(OVTK_StimulationId_ExperimentStop, CTime(5.0).time(),0);
}
}
#endif
#if 0
StreamHeaderSignal signalHeader;
signalHeader.m_samplingRate = 512;
Stream<StreamHeaderSignal, StreamDataSignal> testStream(m_ctx);
testStream.initialize(signalHeader);
StreamDataSignal* data = new(StreamDataSignal);
data->data.setDimensionSize(10,2);
testStream.push(data);
// Encode a stream
Encoder<StreamHeaderSignal, StreamDataSignal> encoder(ctx);
std::vector<CMemoryBuffer> encoded;
encoded.push_back( encoder.encodeHeader(testStream.getHeader()) );
for (size_t i=0;i<testStream.m_chunks.size(); ++i) { encoded.push_back( encoder.encodeBuffer(*testStream.m_chunks[i]) ); }
Stream* ptr = testStream;
#endif
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,53 @@
#include "TrackerPluginChannelCheck.h"
#include "Stream.h"
#include "TypeSignal.h"
namespace OpenViBE {
namespace Tracker {
bool TrackerPluginChannelCheck::process(Workspace& wp, ParallelExecutor& /* exec */)
{
log() << Kernel::LogLevel_Info << "TrackerPluginChannelCheck: Testing if all selected signal streams have the same number of channels ...\n";
uint32_t nChannels = 0;
size_t nTested = 0;
for (size_t t = 0; t < wp.getNumTracks(); ++t)
{
const auto& track = wp.getTrack(t);
for (size_t s = 0; s < track->getNumStreams(); ++s)
{
const auto stream = track->getStream(s);
// This plugin only handles signal streams
if (stream->getSelected() && stream->getTypeIdentifier() == OV_TypeId_Signal)
{
nTested++;
auto typedStream = std::static_pointer_cast<Stream<TypeSignal>>(stream);
const auto& hdr = typedStream->getHeader();
const uint32_t thisChannels = hdr.m_Header.getDimensionSize(0);
if (nTested == 1) { nChannels = thisChannels; }
else if (nChannels != thisChannels)
{
log() << Kernel::LogLevel_Error << "Stream " << (s + 1) << " of Track " << t + 1 << " has different number of signal channels ("
<< thisChannels << ") than previous signal streams (" << nChannels << ")\n";
return true;
}
}
}
// This plugin does not modify the track. If it did, we would need to call the following:
// track.setDirtyBit(true);
}
log() << Kernel::LogLevel_Info << "All signal streams have the same amount of channels (" << nTested << " tested)\n";
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,67 @@
#include <memory> // shared_ptr
#include "TrackerPluginCountStimulations.h"
#include "Stream.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
bool TrackerPluginCountStimulations::process(StreamBundle& track)
{
log() << Kernel::LogLevel_Info << "TrackerPluginCountStimulation: Counting stimulations in the selected streams\n";
bool processedSomething = false;
for (size_t s = 0; s < track.getNumStreams(); ++s)
{
const auto stream = track.getStream(s);
// This plugin only handles stimulation streams
if (stream->getSelected() && stream->getTypeIdentifier() == OV_TypeId_Stimulations)
{
processedSomething = true;
std::map<uint64_t, uint64_t> histogram;
double numStims = 0;
const auto typedStream = std::static_pointer_cast<const Stream<TypeStimulation>>(stream);
// Count occurrences
for (size_t i = 0; i < typedStream->getChunkCount(); ++i)
{
const auto chunk = typedStream->getChunk(i);
for (size_t stimIdx = 0; stimIdx < chunk->m_buffer.getStimulationCount(); ++stimIdx)
{
auto id = chunk->m_buffer.getStimulationIdentifier(stimIdx);
auto it = histogram.find(id);
if (it != histogram.end()) { it->second++; }
else { histogram[id] = 1; }
numStims++;
}
}
// Print
log() << Kernel::LogLevel_Info << "Stimulation counts for stream " << (s + 1) << " ...\n";
for (auto& it : histogram)
{
const CString name = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, it.first);
log() << Kernel::LogLevel_Info << (name.length() > 0 ? name : "Unregistered") << " (" << it.first << ") " << " : " << it.second
<< " (" << (it.second / numStims) * 100 << "%)" << "\n";
}
}
}
// This plugin does not modify the track. If it did, we would need to call the following:
// track.setDirtyBit(true);
if (!processedSomething) { log() << Kernel::LogLevel_Info << "No selected streams compatible with stimulation counting plugin\n"; }
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,59 @@
#include <algorithm> // std::sort
#include "TrackerPlugins.h"
#include "TrackerPluginCountStimulations.h"
#include "TrackerPluginChannelCheck.h"
namespace OpenViBE {
namespace Tracker {
TrackerPlugins::TrackerPlugins(const Kernel::IKernelContext& ctx) : Contexted(ctx)
{
// Declare all plugins here
m_pluginCreateCalls.push_back([&ctx]() { return new TrackerPluginCountStimulations(ctx); });
m_pluginCreateCalls.push_back([&ctx]() { return new TrackerPluginChannelCheck(ctx); });
// Create example instances of the plugins so the GUI can display a list
for (auto& fun : m_pluginCreateCalls)
{
auto ptr = fun();
m_trackerPlugins.push_back(ptr);
}
if (m_trackerPlugins.empty()) { return; }
// get both arrays sorted. No code beauty contest winners here...
std::vector<size_t> indexes;
for (size_t i = 0; i < m_trackerPlugins.size(); ++i) { indexes.push_back(i); }
auto& pluginRef = m_trackerPlugins;
auto& callRef = m_pluginCreateCalls;
std::sort(indexes.begin(), indexes.end(),
[&pluginRef](const size_t a, const size_t b) { return (pluginRef[a]->getName()) < (pluginRef[b]->getName()); });
std::vector<ITrackerPlugin*> sortedPlugins;
std::transform(indexes.begin(), indexes.end(), std::back_inserter(sortedPlugins), [pluginRef](const size_t i) { return pluginRef[i]; });
m_trackerPlugins = sortedPlugins;
std::vector<std::function<ITrackerPlugin*()>> sortedCalls;
std::transform(indexes.begin(), indexes.end(), std::back_inserter(sortedCalls), [callRef](const size_t i) { return callRef[i]; });
m_pluginCreateCalls = sortedCalls;
}
TrackerPlugins::~TrackerPlugins()
{
for (auto f : m_trackerPlugins) { delete f; }
m_trackerPlugins.clear();
m_pluginCreateCalls.clear();
}
ITrackerPlugin* TrackerPlugins::getPluginCopy(const size_t index) const
{
if (index >= m_pluginCreateCalls.size()) { return nullptr; }
const auto fun = m_pluginCreateCalls[index];
return fun();
}
} // namespace Tracker
} // namespace OpenViBE

Some files were not shown because too many files have changed in this diff Show More