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,165 @@
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
#include <bitset>
#include <sstream>
#include "ovasCConfigurationEEGO.h"
namespace OpenViBE {
namespace AcquisitionServer {
// Function to set a predefined string in the combobox.
// Copied from ovasCConfigurationBuilder. Seems to be OK, albeit it is strange to have the code duplication,
// but other amplifier drivers do it too.
// Would be nicer if the method would move to some utilities provider.
static void GTKComboBoxSetActiveText(GtkComboBox* box, const gchar* text)
{
GtkTreeModel* treeModel = gtk_combo_box_get_model(box);
GtkTreeIter it;
int index = 0;
gchar* name = nullptr;
if (gtk_tree_model_get_iter_first(treeModel, &it))
{
do
{
gtk_tree_model_get(treeModel, &it, 0, &name, -1);
if (std::string(name) == std::string(text))
{
gtk_combo_box_set_active(box, index);
return;
}
index++;
} while (gtk_tree_model_iter_next(treeModel, &it));
}
}
// If you added more reference attribute, initialize them here
CConfigurationEEGO::CConfigurationEEGO(IDriverContext& ctx, const char* gtkBuilderFilename, CHeaderEEGO& eegoHeader)
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_eegoHeader(eegoHeader) {}
bool CConfigurationEEGO::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
// Insert here the pre-configure code.
// For example, you may want to check if a device is currently connected
// and if more than one are connected. Then you can list in a dedicated combo-box
// the device currently connected so the user can choose which one he wants to acquire from.
// Steffen Heimes: This is actually a TODO:
GtkWidget* widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_signal_range_eeg"));
m_eegRangeComboBox = GTK_COMBO_BOX(widget);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_signal_range_bip"));
m_bipRangeComboBox = GTK_COMBO_BOX(widget);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_eeg_mask"));
m_eegEntryMask = GTK_ENTRY(widget);
g_signal_connect(widget, "changed", G_CALLBACK(updateChannelNumCB), this);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_bip_mask"));
m_bipEntryMask = GTK_ENTRY(widget);
g_signal_connect(widget, "changed", G_CALLBACK(updateChannelNumCB), this);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_num_channels"));
m_nChannelEntry = GTK_ENTRY(widget);
if (!m_eegRangeComboBox) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not connect to range widget \n"; }
else
{
if (m_eegoHeader.isEEGRangeSet())
{
const std::string range = std::to_string(m_eegoHeader.getEEGRange());
GTKComboBoxSetActiveText(m_eegRangeComboBox, range.c_str());
}
else { gtk_combo_box_set_active(m_eegRangeComboBox, 0); }
if (m_eegoHeader.isBIPRangeSet())
{
const std::string range = std::to_string(m_eegoHeader.getBIPRange());
GTKComboBoxSetActiveText(m_bipRangeComboBox, range.c_str());
}
else { gtk_combo_box_set_active(m_bipRangeComboBox, 0); }
}
if (m_eegoHeader.isBIPMaskSet()) { gtk_entry_set_text(m_bipEntryMask, m_eegoHeader.getBIPMask()); }
if (m_eegoHeader.isEEGMaskSet()) { gtk_entry_set_text(m_eegEntryMask, m_eegoHeader.getEEGMask()); }
return true;
}
bool CConfigurationEEGO::postConfigure()
{
if (m_applyConfig)
{
const gchar* rangeEEG = gtk_combo_box_get_active_text(m_eegRangeComboBox);
const gchar* rangeBip = gtk_combo_box_get_active_text(m_bipRangeComboBox);
const gchar* maskBip = gtk_entry_get_text(m_bipEntryMask);
const gchar* maskEEG = gtk_entry_get_text(m_eegEntryMask);
m_eegoHeader.setBIPMask(maskBip);
m_eegoHeader.setEEGMask(maskEEG);
m_eegoHeader.setEEGRange(rangeEEG ? atoi(rangeEEG) : 0);
m_eegoHeader.setBIPRange(rangeBip ? atoi(rangeBip) : 0);
}
if (!CConfigurationBuilder::postConfigure()
) { return false; } // normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are released
// get sum of max active channels. It would be a good idea to use the amplifier connected as a source of the maximum of available channels.
const uint64_t bip = m_eegoHeader.getBIPMaskInt();
const uint64_t eeg = m_eegoHeader.getEEGMaskInt();
const std::bitset<64> bitsetEEG(eeg);
const std::bitset<24> bitsetBip(bip);
m_header->setChannelCount(bitsetEEG.count() + bitsetBip.count() + 2);
// Plus status channels: trigger and sample counter
return true;
}
/// GTK Callbacks
/* static */
void CConfigurationEEGO::updateChannelNumCB(GtkWidget* /*widget*/, CConfigurationEEGO* pThis)
{
// get the values
const gchar* maskBip = gtk_entry_get_text(pThis->m_bipEntryMask);
const gchar* maskEEG = gtk_entry_get_text(pThis->m_eegEntryMask);
uint64_t bip;
uint64_t eeg;
const bool bipSuccess = CHeaderEEGO::convertMask(maskBip, bip);
const bool eegSuccess = CHeaderEEGO::convertMask(maskEEG, eeg);
const std::bitset<64> bitsetEEG(eeg);
const std::bitset<24> bitsetBip(bip);
// format them
std::stringstream ss;
if (eegSuccess) { ss << bitsetEEG.count(); }
else { ss << "Error"; }
ss << " + ";
if (bipSuccess) { ss << bitsetBip.count(); }
else { ss << "Error"; }
ss << " + 2; (EEG + BIP + STATUS)";
// set text
gtk_entry_set_text(pThis->m_nChannelEntry, ss.str().c_str());
const uint32_t nChannel = bitsetEEG.count() + bitsetBip.count() + 2;
pThis->m_header->setChannelCount(nChannel);
// Workaround! The current channel number is not derived from the channel count. It is retrieved from the /here hidden/
// widget when the channel editing window is opening. Thus we have to set the value there too.
if (GTK_SPIN_BUTTON(pThis->m_nChannels)) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(pThis->m_nChannels), nChannel); }
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyEEGOAPI
@@ -0,0 +1,51 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
#include <ovasIDriver.h>
#include <../ovasCConfigurationBuilder.h>
#include <gtk/gtk.h>
#include "ovasCHeaderEEGO.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationEEGO
* \author Steffen Heimes (Eemagine GmbH)
* \date Fri May 27 21:48:42 2011
* \brief The CConfigurationEEGO handles the configuration dialog for setting specific for EEGO.
* \sa CDriverEEGO
*/
class CConfigurationEEGO final : public CConfigurationBuilder
{
public:
CConfigurationEEGO(IDriverContext& ctx, const char* gtkBuilderFilename, CHeaderEEGO& eegoHeader);
bool preConfigure() override;
bool postConfigure() override;
// Data
protected:
IDriverContext& m_driverCtx;
// Methods
private:
static void updateChannelNumCB(GtkWidget* widget, CConfigurationEEGO* pThis);
// Data
CHeaderEEGO& m_eegoHeader;
GtkComboBox* m_eegRangeComboBox = nullptr;
GtkComboBox* m_bipRangeComboBox = nullptr;
GtkEntry* m_eegEntryMask = nullptr;
GtkEntry* m_bipEntryMask = nullptr;
GtkEntry* m_nChannelEntry = nullptr;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,471 @@
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
// stl includes
#include <algorithm>
#include <bitset>
#include <exception>
#include <memory>
#if defined TARGET_OS_Linux || defined TARGET_OS_MacOS
#include <cstddef>
#include <type_traits>
#include <utility>
#endif
// OV includes
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
// auxilliary classes for this implementation
#include "ovasCDriverEEGO.h"
#include "ovasCConfigurationEEGO.h"
// The interface to the driver
// We have to setup the binding method and the unicode support first
#define _UNICODE
#if defined(_MBCS) // Only unicode should be supported. Better stay away from MBCS methods!
#undef _MBCS
#endif
#include <eemagine/sdk/factory.h> // Where it all starts
// Namespaces
namespace OpenViBE {
namespace AcquisitionServer {
namespace es = eemagine::sdk;
#if defined TARGET_OS_Linux || defined TARGET_OS_MacOS
namespace std {
template<class T> struct _Unique_if {
typedef unique_ptr<T> _Single_object;
};
template<class T> struct _Unique_if<T[]> {
typedef unique_ptr<T[]> _Unknown_bound;
};
template<class T, size_t N> struct _Unique_if<T[N]> {
typedef void _Known_bound;
};
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n) {
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
}
#endif
//___________________________________________________________________//
// //
CDriverEEGO::CDriverEEGO(IDriverContext& ctx)
: IDriver(ctx)
, m_settings("AcquisitionServer_Driver_EEGO", m_driverCtx.getConfigurationManager()), m_triggerChannel(-1) // == Nonexistent
, m_lastTriggerValue(~0) // Set every bit to 1
, m_iBIPRange(1500)
, m_iEEGRange(1000)
, m_sEEGMask("0xFFFFFFFFFFFFFFFF") // 64 channels
, m_sBIPMask("0xFFFFFF") // 24 channels
{
m_header.setSamplingFrequency(500);
m_header.setChannelCount(88);
// The following class allows saving and loading driver settings from the acquisition server .conf file
m_settings.add("Header", &m_header);
// To save your custom driver settings, register each variable to the SettingsHelper
//m_settings.add("SettingName", &variable);
m_settings.add("BIPRange", &m_iBIPRange);
m_settings.add("EEGRange", &m_iEEGRange);
m_settings.add("EEGMask", &m_sEEGMask);
m_settings.add("BIPMask", &m_sBIPMask);
m_settings.load();
}
CDriverEEGO::~CDriverEEGO() {}
const char* CDriverEEGO::getName() { return "EEGO"; }
bool CDriverEEGO::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()
|| !m_header.isChannelCountSet()
|| !m_header.isSamplingFrequencySet()) { return false; }
try
{
// Builds up a buffer to store
// acquired samples. This buffer
// will be sent to the acquisition
// server later...
m_sample = std::make_unique<float[]>(m_header.getChannelCount() * nSamplePerSentBlock);
m_samplesInBuffer = 0;
// Get the amplifier. If none is connected an exception will be thrown
try { m_pAmplifier.reset(factory().getAmplifier()); }
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Failure to get an amplifier! Reason: " << ex.what() << "\n";
throw;
}
if (m_driverCtx.isImpedanceCheckRequested())
{
// end streaming first, if started
m_pStream.reset();
// After init we are in impedance mode until the recording is started
m_pStream.reset(m_pAmplifier->OpenImpedanceStream(getRefChannelMask()));
}
}
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to initialize the driver. Exception: " << ex.what() << "\n";
m_sample.reset();
m_pAmplifier.reset();
m_pStream.reset();
return false;
}
// Save parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
bool CDriverEEGO::check_configuration()
{
// get masks from configuration
const uint64_t maskBip = getBipChannelMask();
const uint64_t maskEEG = getRefChannelMask();
const std::bitset<64> bitsetEEG(maskEEG);
const std::bitset<24> bitsetBip(maskBip);
const size_t allChannels = bitsetBip.count() + bitsetEEG.count() + 2; // trigger and sample count as additional channels
if (allChannels < m_header.getChannelCount())
{
// Not enough channels, we have to reduce them
GtkWidget* dialog = gtk_message_dialog_new(nullptr, // parent
GTK_DIALOG_MODAL, // Behavoir
GTK_MESSAGE_QUESTION, // Type
GTK_BUTTONS_OK_CANCEL, // buttons
"The channels masks are set to only stream %ld channels, but %d channels should be streamed.\n"
"Change the amount of channels to %ld?", allChannels, m_header.getChannelCount(), allChannels);
const gint res = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
dialog = nullptr;
switch (res)
{
case GTK_RESPONSE_OK:
// update the channel count to contain only the selected masks
m_header.setChannelCount(allChannels);
break;
default:
// Nothing can be done here
return false;
}
}
return true;
}
uint64_t CDriverEEGO::getRefChannelMask() const
{
uint64_t maskEEG(0);
if (!CHeaderEEGO::convertMask(m_sEEGMask, maskEEG))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Error converting mask: m_sEEGMask: " << m_sEEGMask << "\n";
}
return maskEEG;
}
uint64_t CDriverEEGO::getBipChannelMask() const
{
uint64_t maskBip(0);
if (!CHeaderEEGO::convertMask(m_sBIPMask, maskBip))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Error converting mask: maskBip: " << maskBip << "\n";
}
return maskBip;
}
eemagine::sdk::factory& CDriverEEGO::factory()
{
if (m_pFactory == nullptr)
{
// create the amplifier factory
// To initialize we need to locate the path of the DLL
// Create path to the dll
#ifdef _WIN32
const CString libDir = Directories::getBinDir() + "\\eego-SDK.dll";
const std::string path(libDir.toASCIIString());
#else
const std::string path("libeego-SDK.so");
#endif // _WIN32
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "SDK dll/so path: " << path << "\n";
m_pFactory = std::make_unique<es::factory>(path);
// to check what is going on case of error; Log version
const auto version = m_pFactory->getVersion();
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "EEGO SDK Version: " << version.major << "." << version.minor << "." << version.micro << "." <<
version.build << "\n";
}
return *m_pFactory;
}
bool CDriverEEGO::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted() || !m_pAmplifier) { return false; }
// Check configuration
if (!check_configuration()) { return false; }
// ...
// request hardware to start
// sending data
// ..
const double bipRange = m_iBIPRange / 1000.;
const double eegRange = m_iEEGRange / 1000.;
try
{
// stop old streams, if existing
m_pStream.reset();
// Create the eeg stream
m_pStream.reset(m_pAmplifier->OpenEegStream(m_header.getSamplingFrequency(), eegRange, bipRange, getRefChannelMask(), getBipChannelMask()));
// Error check
if (!m_pStream)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "The stream returned is NULL!" << "\n";
return false;
}
// find trigger channel
auto list = m_pStream->getChannelList();
const auto triggerIterator = std::find_if(list.begin(), list.end(), [](eemagine::sdk::channel& chan)
{
return chan.getType() == eemagine::sdk::channel::trigger;
});
if (triggerIterator == list.end())
{
m_triggerChannel = -1; // Unkown
}
else { m_triggerChannel = (*triggerIterator).getIndex(); }
// Wait till we are really getting data.
while (m_pStream->getData().getSampleCount() == 0)
{
System::Time::sleep(5); // Do Nothing
}
}
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open EEG stream: " << ex.what() << "\n";
return false;
}
return true;
}
bool CDriverEEGO::loop()
{
bool result = false;
try { result = loop_wrapped(); }
catch (const std::exception& ex) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error in data update: " << ex.what() << "\n"; }
catch (...) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unknown error in data update." << "\n"; }
return result;
}
bool CDriverEEGO::loop_wrapped()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted() && !m_driverCtx.isImpedanceCheckRequested()) { return true; } // Nothing to be done here!
if (!m_pStream) { return false; }
// Check if we really provide enough channels
// When doing impedance only the normal EEG channels are tested. This is fine and handled.
if (m_pStream->getChannelList().size() < m_header.getChannelCount() && m_driverCtx.isStarted()) // !started -> impedance
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "The amplifier got asked for more channels than it could provide" << "\n";
return false;
}
if (m_driverCtx.isStarted()) // Normal operation
{
eemagine::sdk::buffer data;
try { data = m_pStream->getData(); }
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error fetching data: " << ex.what() << "\n";
return false;
}
const size_t nSample = data.getSampleCount();
// For EEGO the with every index increment, the channel is incremented.
// For OpenVibe it means next sample. Therefor we have to transpose the data
for (size_t sample = 0; sample < nSample; ++sample)
{
for (size_t channel = 0; channel < m_header.getChannelCount(); ++channel)
{
const int ovIdx = int(m_samplesInBuffer + channel * m_nSamplePerSentBlock);
const double& sampleVal = data.getSample(channel, sample);
m_sample[ovIdx] = float(sampleVal);
}
// Add potential triggers to stimulation set
// check for triggers
if (m_triggerChannel >= 0) // Only try to find triggers when the channel exists
{
// A trigger is detected when the level changes in positive direction, all additional bits are seen as trigger code
// a change from 1 to 0 is ignored
const uint32_t currentTriggers = uint32_t(data.getSample(m_triggerChannel, sample));
const uint32_t currentNewTriggers = currentTriggers & ~m_lastTriggerValue;
m_lastTriggerValue = currentTriggers;
if (currentNewTriggers != 0)
{
const uint64_t currentTime = CTime(m_header.getSamplingFrequency(), m_samplesInBuffer).time();
m_stimSet.appendStimulation(OVTK_StimulationId_Label(currentNewTriggers), currentTime, 0);
}
}
// Send buffer counter
m_samplesInBuffer++;
// Send buffer is full, so send it
if (m_samplesInBuffer == m_nSamplePerSentBlock)
{
m_callback->setSamples(m_sample.get());
m_callback->setStimulationSet(m_stimSet);
// When your sample buffer is fully loaded,
// it is advised to ask the acquisition server
// to correct any drift in the acquisition automatically.
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
m_samplesInBuffer = 0;
m_stimSet.clear();
}
}
}
else // Impedance
{
// Get the impedance data, here the data is always the most current state.
// The method can block if impedance still needs to be calculated.
eemagine::sdk::buffer data;
try { data = m_pStream->getData(); }
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error fetching data: " << ex.what() << "\n";
return false;
}
// We have to take care not to r/w over any boundary.
const size_t minChannels = std::min(size_t(data.getChannelCount()), m_header.getChannelCount());
for (size_t channel = 0; channel < minChannels; ++channel) { m_driverCtx.updateImpedance(channel, data.getSample(channel, 0)); }
}
return true;
}
bool CDriverEEGO::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
// ...
// request the hardware to stop
// sending data
// ...
if (m_driverCtx.isImpedanceCheckRequested())
{
try
{
m_pStream.reset();
m_pStream.reset(m_pAmplifier->OpenImpedanceStream(getRefChannelMask())); // And we can stream Impedances once more.
}
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error restarting impedance: " << ex.what() << "\n";
return false;
}
}
return true;
}
bool CDriverEEGO::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// uninitialize hardware here
// ...
m_pStream.reset();
m_pAmplifier.reset();
m_sample.reset();
m_callback = nullptr;
m_samplesInBuffer = 0;
return true;
}
//___________________________________________________________________//
// //
bool CDriverEEGO::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDriverEEGO::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationEEGO config(m_driverCtx,
Directories::getDataDir() + "/applications/acquisition-server/interface-EEGO.ui",
m_header);
m_header.setBIPRange(m_iBIPRange);
m_header.setEEGRange(m_iEEGRange);
m_header.setBIPMask(m_sBIPMask);
m_header.setEEGMask(m_sEEGMask);
if (!config.configure(m_header)) { return false; }
m_iBIPRange = m_header.getBIPRange();
m_iEEGRange = m_header.getEEGRange();
m_sBIPMask = m_header.getBIPMask();
m_sEEGMask = m_header.getEEGMask();
m_settings.save();
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,99 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
#include <memory>
#include "ovasIDriver.h"
#include "ovasCHeaderEEGO.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
// forward declarations
namespace eemagine {
namespace sdk {
class amplifier;
class stream;
class factory;
}
}
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverEEGO
* \author Steffen Heimes (eemagine GmbH)
* \date Mon Oct 20 14:40:33 2014
* \brief The CDriverEEGO allows the acquisition server to acquire data from an EEGO device.
*
* \sa CConfigurationEEGO
*/
class CDriverEEGO final : public IDriver
{
public:
explicit CDriverEEGO(IDriverContext& ctx);
~CDriverEEGO() override;
const char* getName() override;
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag /*flag*/) const
override { return false; } // The only currently used flag is for checking for unstability. eego is stable now.
private:
bool loop_wrapped();
/**
* Check if the configuration makes sense and tries to fix it, informing the user.
*/
bool check_configuration();
uint64_t getRefChannelMask() const;
uint64_t getBipChannelMask() const;
eemagine::sdk::factory& factory();
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
CHeaderEEGO m_header;
uint32_t m_nSamplePerSentBlock = 0;
std::unique_ptr<float[]> m_sample;
std::unique_ptr<eemagine::sdk::factory> m_pFactory;
std::unique_ptr<eemagine::sdk::amplifier> m_pAmplifier;
std::unique_ptr<eemagine::sdk::stream> m_pStream;
private:
uint32_t m_samplesInBuffer = 0;
uint32_t m_triggerChannel = 0;
CStimulationSet m_stimSet; // Storing the samples over time
// To detect flanks in the trigger signal. The last state on the trigger input.
uint32_t m_lastTriggerValue = 0;
// For setting store/load
uint32_t m_iBIPRange = 0; // [mV]
uint32_t m_iEEGRange = 0; // [mV]
CString m_sEEGMask; // String interpreted as value to be interpreted as bitfield
CString m_sBIPMask; // String interpreted as value to be interpreted as bitfield
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,94 @@
#if defined TARGET_HAS_ThirdPartyEEGOAPI
#include <algorithm>
#include <locale>
#include <boost/algorithm/string.hpp>
#include "ovasCHeaderEEGO.h"
namespace OpenViBE {
namespace AcquisitionServer {
void CHeaderEEGO::setEEGRange(const uint32_t range)
{
m_iEEGRange = range;
m_bEEGRangeSet = true;
}
void CHeaderEEGO::setEEGMask(const CString& mask)
{
m_sEEGMask = mask;
m_bEEGMaskSet = true;
}
void CHeaderEEGO::setBIPRange(const uint32_t range)
{
m_iBIPRange = range;
m_bBIPRangeSet = true;
}
void CHeaderEEGO::setBIPMask(const CString& mask)
{
m_sBIPMask = mask;
m_bBIPMaskSet = true;
}
/* static */
bool CHeaderEEGO::convertMask(char const* str, uint64_t& out)
{
bool error = false;
// init r_outValue anyway
out = 0;
std::string input(str); //easier substring handling etc. Minor performance penalty which should not matter.
boost::algorithm::trim(input); // Make sure to handle whitespace correctly
// check prefixes
if (boost::algorithm::istarts_with(input, "0b"))
{
// binary
const auto substring = input.substr(2);
// check for valid string members
if (!std::all_of(substring.begin(), substring.end(), [&](const char& chr) { return chr == '0' || chr == '1'; })) { error = true; }
else
{
// use the substring for string to number conversion as base 2
out = strtoull(substring.c_str(), nullptr, 2);
}
}
else if (boost::algorithm::istarts_with(input, "0x"))
{
// hex
const auto substring = input.substr(2);
std::locale loc;
if (!std::all_of(substring.begin(), substring.end(), [&](const char& chr) { return std::isxdigit(chr, loc); })) { error = true; }
else { out = strtoull(substring.c_str(), nullptr, 16); }
}
else if (boost::algorithm::istarts_with(input, "0"))
{
// octal
const auto substring = input.substr(1);
if (!std::all_of(substring.begin(), substring.end(), [&](const char& chr) { return chr >= '0' && chr < '8'; })) { error = true; }
else { out = strtoull(substring.c_str(), nullptr, 8); }
}
else
{
// decimal
//const auto substring = input;
std::locale loc;
if (!std::all_of(input.begin(), input.end(), [&](const char& chr) { return std::isdigit(chr, loc); })) { error = true; }
else { out = strtoull(input.c_str(), nullptr, 10); }
}
// if no special handling for the base 2 case is neccessary we can just use the std::stroull implementation and do not mess with that any further.
return !error;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,63 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyEEGOAPI
#include "../ovasCHeader.h"
namespace OpenViBE {
namespace AcquisitionServer {
class CHeaderEEGO final : public CHeader
{
public:
CHeaderEEGO() { }
// EEG, referential channels
// range
uint32_t getEEGRange() const { return m_iEEGRange; }
void setEEGRange(uint32_t range);
bool isEEGRangeSet() const { return m_bEEGRangeSet; }
// mask
CString getEEGMask() const { return m_sEEGMask; }
uint64_t getEEGMaskInt() const { return strtoull(m_sEEGMask, nullptr, 0); } // Same as method above. Only string parsing has been done
void setEEGMask(const CString& mask);
bool isEEGMaskSet() const { return m_bEEGMaskSet; }
// Bipolar channels
// range
uint32_t getBIPRange() const { return m_iBIPRange; }
uint64_t getBIPMaskInt() const { return strtoull(m_sBIPMask, nullptr, 0); } // Same as method above. Only string parsing has been done
void setBIPRange(uint32_t range);
bool isBIPRangeSet() const { return m_bBIPRangeSet; }
// mask
CString getBIPMask() const { return m_sBIPMask; }
void setBIPMask(const CString& mask);
bool isBIPMaskSet() const { return m_bBIPMaskSet; }
// Converts a string representing a number to this number as unsigned 64 bit value.
// Accepts 0x, 0b and 0 notation for hexadecimal, binary and octal notation.
// Otherwise it is interpreted as decimal.
// Returns true if the conversion was successfull, false on error.
// Please note that the error checking goes beyond the parsing strtoull etc.:
// The strto* methods stop parsing at the first character which could not be interpreted.
// Here the string is checked against all invalid chars and an error will be returned.
static bool convertMask(char const* str, uint64_t& out);
// data
protected:
uint32_t m_iEEGRange = 1000;
uint32_t m_iBIPRange = 1500;
CString m_sEEGMask = "0xFFFFFFFFFFFFFFFF";
CString m_sBIPMask = "0xFFFFFF";
bool m_bEEGRangeSet = false;
bool m_bBIPRangeSet = false;
bool m_bEEGMaskSet = false;
bool m_bBIPMaskSet = false;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif