Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 3220 additions and 0 deletions
//
// BITSynth.cpp
// Bitalino
//
// Created by James on 07/12/2017.
//
//
#include "BITSynth.hpp"
namespace BIT {
// Simple fm synth
Synth::Synth ( void )
{
// Set up the ringbuffer for controldata which is pushed in to the audio thread
controlDataBuffer.setup( 100 ); // Leave room for 10 control data to sync over to audio thread
}
void Synth::audioOut ( float *output, int bufferSize, int nChannels )
{ // Audio thread sync callback
for ( uint32_t i = 0; i < bufferSize; ++i )
{
if ( controlDataBuffer.items_available_for_read( ) )
controlDataBuffer.pop( &audioControlData, 1 );
SVF.setCutoff( dFilt1.lopass( 110 + fmin( fabs( audioControlData[ CUTOFF ] ), 0.95 ) * 5000, 0.001 ) );
SVF.setResonance( 0.1 + fmin( fabs( audioControlData[ RESONANCE ] ), 0.9 ) );
double modulator = VCO2.sinewave( fabs( 20 + audioControlData[ MODULATOR ] * 1000 ) ) * ( 500 * fabs( audioControlData[ AMP ] ) );
double carrier = VCO1.sinewave( fabs( audioControlData[ CARRIER ] + modulator ) );
double filtered = SVF.play( carrier, 1.0, 0, 0, 0 ) * 0.25;
double delay1 = DL1.dl( filtered, dFilt2.lopass( fmin( fabs( audioControlData[ SIZE_A ] ) * 88200, 88199 ), 0.01 ),
fabs( audioControlData[ FB_A ] ) );
double delay2 = DL2.dl( filtered, dFilt3.lopass( fmin( fabs( audioControlData[ SIZE_B ] ) * 88200, 88199 ), 0.01 ),
fabs( audioControlData[ FB_B ] ) );
output[ i * nChannels ] = filtered * 0.75 + delay1 * 0.125 + delay2 * 0.0125;
output[ i * nChannels + 1 ] = filtered * 0.75 + delay1 * 0.0125 + delay2 * 0.125;
}
}
}
//
// BITSynth.hpp
// Bitalino
//
// Created by James on 07/12/2017.
//
//
#ifndef BITSynth_hpp
#define BITSynth_hpp
#include <stdio.h>
#include <vector>
#include "ofxMaxim.h"
#include "RingBuffer.hpp"
#include "GlobalDefs.h"
namespace BIT {
// Synthesizer
class Synth {
public:
// From data thread to Audio thread
RingBuffer<BIT::SynthControlData> controlDataBuffer;
Synth ( void );
void audioOut( float *output, int bufferSize, int nChannels );
private:
// ofMaxim audio stuff
maxiOsc VCO1, VCO2;
maxiSVF SVF;
maxiFractionalDelay DL1, DL2;
// For smoothing parameter changes:
maxiFilter dFilt1, dFilt2, dFilt3;
// Audio thread synced
BIT::SynthControlData audioControlData;
};
}
#endif /* BITSynth_hpp */
//
// BitalinoThread.h
// Bitalino
//
// Created by James on 19/11/2017.
//
//
#ifndef BitalinoThread_h
#define BitalinoThread_h
#include <stdio.h>
#include "ThreadedProcess.h"
#include "RingBuffer.hpp"
#include "bitalino.h"
class BitalinoThread : public ThreadedProcess {
public:
RingBuffer< BITalino::Frame > data; // The data gathered in the main thread, safely accessable from outside
BitalinoThread ( void ) : ThreadedProcess ( )
{
connected.store( false );
recording.store( false );
tries.store( 0 );
// NOTE THAT DEVICE NAME CAN BE SET IN SETUP
this->deviceName = "/dev/cu.BITalino-DevB"; // Debug init ( Mac OS virt. serial over bt )
}
~BitalinoThread ( void )
{
}
void setup ( std::string deviceName, uint64_t bufferSize,
uint16_t sampleRate, std::vector<int32_t> channels,
uint16_t frameCount=100 )
{
this->deviceName = deviceName;
this->sampleRate = sampleRate;
this->channels = channels;
frameBuffer.resize( frameCount );
pollTestBuffer.resize( 1 );
data.setup( bufferSize );
startThread( 500 ); // sleep for 0.5 ms after each callback
}
void setRecording ( bool v )
{ // Allow data to be pushed in to the ringbuffer
recording.store( v );
}
bool isRecording ( void ) const
{
return recording.load( );
}
bool isConnected ( void ) const
{
return connected.load( );
}
uint32_t getConnectionTries( void ) const
{
return tries.load( );
}
protected:
void mainThreadCallback ( void ) override
{
try {
switch ( threadState )
{
case SEARCHING_FOR_DEVICE:
++currentTries;
tries.store( currentTries );
if ( !dev )
dev = new BITalino( deviceName.c_str( ) );
dev->start( sampleRate, channels );
threadState = IDLE_BUT_CONNECTED;
connected.store( true );
currentTries = 0;
break;
case IDLE_BUT_CONNECTED:
if ( recording.load( ) )
{
threadState = RECORDING_DATA;
} else {
dev->read( pollTestBuffer ); // Poll small data to check if alive
usleep( 100000 ); // Wait 100 ms
}
break;
case RECORDING_DATA:
if ( !recording.load( ) )
{
threadState = IDLE_BUT_CONNECTED;
} else {
dev->read( frameBuffer );
if ( data.items_available_for_write( ) >= frameBuffer.size( ) )
data.push( &frameBuffer[ 0 ], frameBuffer.size( ) );
// Else skipped frame... notify?
}
break;
default:
break;
}
} catch ( BITalino::Exception &e ) {
// printf( "BITalino exception: %s\n", e.getDescription( ) );
// TODO: check which exact exception is communication lost etc.
threadState = SEARCHING_FOR_DEVICE;
connected.store( false );
if ( dev )
{
delete dev;
dev = nullptr;
}
usleep( 500000 ); // 500ms Timeout before trying to reconnect again
}
}
std::atomic< bool > connected;
std::atomic< bool > recording;
std::string deviceName;
BITalino* dev = nullptr;
uint16_t sampleRate = 1000;
std::vector< int32_t > channels;
std::atomic< uint32_t > tries;
uint32_t currentTries = 0;
private:
enum State {
SEARCHING_FOR_DEVICE,
IDLE_BUT_CONNECTED,
RECORDING_DATA
};
State threadState = SEARCHING_FOR_DEVICE;
BITalino::VFrame frameBuffer;
BITalino::VFrame pollTestBuffer;
};
#endif /* BitalinoThread_h */
//
// GlobalDefs.h
// Bitalino
//
// Created by James on 08/12/2017.
//
//
#ifndef GlobalDefs_h
#define GlobalDefs_h
namespace BIT
{
// Synth parameters
// Internal states
struct RecordingSelection {
double position;
};
enum State {
TRAINING,
PERFORMING,
RECORDING,
PLAYING,
IDLE,
NUMBER_OF_STATES
};
static const char* stateStrings[NUMBER_OF_STATES]
{
"Training",
"Performing",
"Recording",
"Playing",
"Idle"
};
//
enum SynthControl
{
CARRIER,
MODULATOR,
AMP,
CUTOFF,
RESONANCE,
SIZE_A,
SIZE_B,
FB_A,
FB_B,
NUM_SYNTH_PARAMS
};
static const char* ParamNames[NUM_SYNTH_PARAMS] =
{
"1 Carrier",
"2 Modulator",
"3 Amp",
"4 Cutoff",
"5 Resonance",
"6 SizeA",
"7 SizeB",
"8 FeedbackA",
"9 FeedbackB"
};
// Major Scale
static const uint8_t majorScaleSize = 7;
static const uint8_t majorScale[majorScaleSize]
{
0, 2, 4, 5, 7, 9, 11
};
// Synth ctrldata struct
struct SynthControlData
{
double values[BIT::NUM_SYNTH_PARAMS];
SynthControlData ( void )
{
}
SynthControlData ( std::vector<double> data )
{
setValues (data);
}
double& operator[] (const BIT::SynthControl index)
{
return values[static_cast<uint32_t>(index)];
}
int32_t setValues ( std::vector<double>& data )
{ // Returns number of elements which exceeds limit of synth param size
std::copy(data.begin(), data.begin() + BIT::NUM_SYNTH_PARAMS, values);
return data.size() - BIT::NUM_SYNTH_PARAMS;
}
std::vector<double> getValuesAsVector ( void )
{
return std::vector<double>(std::begin(values), std::end(values));
}
};
}
#endif /* GlobalDefs_h */
//
// LinkedBuffer.hpp
// Lock-free (except when linking atm) buffer which propagates its changes to linked buffers
// All linked buffers can be in different threads (read: should be)
// Requires an update function to be called in the thread which it resides in
// Concurrent sets must be ordered to have synchronized operations, accumulation can be nonsync
//
// Created by James on 04/01/2018.
// Copyright © 2017 James Frink. All rights reserved.
//
/*
TODOS:
- Cache line optimization!!!
- Write own underlying allocator so ( AUTO ) resizing can be lock-free
- !!!!! Add iterators ( But not the c++17 deprecated one )
- ACCUMULATOR!! ( This and certain operations can be concurrent without passing a + b, just + and b)
- Make linking lock-free
- Check for feedback loops when linking and throw error / return false if thats the case
- !! Add more instructions, such as:
- Curves?
- !Summing
- !Checksum sharing
- Function?
- Make something useful of double operator setter? < Test new implementation
- Resizing!
- Allow different sized buffers with relative - interpolated - sets?
allow all integer and float types in subscript operator
*/
// This is mostly just an experiment
#ifndef LinkedBuffer_hpp
#define LinkedBuffer_hpp
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include "Spinlock.hpp"
#include "RingBufferAny.hpp"
template < class T >
class LinkedBuffer {
public:
template < class subT >
struct Subscript {
subT index;
LinkedBuffer< T >* parent;
T operator= ( const T& val )
{
parent->set( index, val );
return val;
}
operator T ( void ) const
{
return parent->get( index );
}
};
const T& get ( size_t index ) const;
const T& get ( double index ) const;
Subscript< size_t > operator[ ] ( const int index ); // Set single value
Subscript< double > operator[ ] ( const double index ); // Set interpolated
void set ( size_t index, T value );
void set ( double index, T value ); // For interpolated setting
//void accumulate ( size_t index, T value );
void assign ( size_t startIndex, T* values, size_t numItems );
void setRangeToConstant ( size_t startIndex, size_t stopIndex, T value );
void setLine ( double startIndex, T startValue, double stopIndex, T stopValue );
void setup ( size_t bufferSize ); // Sets up buffer size and queue sizes
void resize ( size_t bufferSize ); // TODO: propegate resize message?
void resizeBounded ( size_t bufferSize ); // Resize without trying to alloc
size_t size ( void ) const;
// TODO: Check for possible feedback loops
bool linkTo ( LinkedBuffer* const other ); // Returns true on link (false on == this or if null)
bool unLinkFrom ( LinkedBuffer* const other ); // Returns true on unlink, false if doesn't exist
bool update ( void );
uint32_t maxDataToParsePerUpdate = 4096; // Set this to a value that doesn't block your thread for forever
size_t trueSize;
private:
template < class TT >
void propagate ( const LinkedBuffer* from, TT* const data, size_t numItems );
RingBufferAny comQueue; // Lock-free linked buffer communications queue
std::vector< LinkedBuffer< T >* > links;
Spinlock editLinks;
std::vector< T > localMemory;
size_t memorySize;
enum MessageTypes {
BOUNDED_MEMORY_RESIZE,
SINGLE_INDEX,
SET_RANGE_TO_CONSTANT,
SET_LINE,
INDEX_SPECIFIER_ACCUM, // TODO
DATA,
NONE
};
// Command struct headers
struct ControlMessage {
const LinkedBuffer* lastSource; // For propagation ( don't send back to source )
MessageTypes type = NONE;
union message {
struct BoundedResizeHeader {
size_t size;
} boundedResizeHeader;
struct SingleIndexHeader {
size_t index;
} singleIndexHeader;
struct SetRangeToConstantHeader {
size_t startIndex;
size_t stopIndex;
T value;
} setRangeToConstantHeader;
struct SetLineHeader {
double startIndex;
T startValue;
double distance;
T deltaIncrement;
} setLineHeader;
} containedMessage;
};
// Add instructions such as resize buffer etc
// TODO: Union or something like it to create better header instruction storage
ControlMessage lastIndexSpecifier;
static const size_t tSize = sizeof ( T );
};
template < class T >
const T& LinkedBuffer< T >::get ( const size_t index ) const
{
return localMemory[ index ];
}
template < class T >
const T& LinkedBuffer< T >::get ( const double index ) const
{
size_t truncPos = index; // Truncate
double fractAmt = index - truncPos;
return localMemory[ truncPos ] * ( 1.0 - fractAmt ) + localMemory[ ++truncPos ] * fractAmt;
}
template < class T >
typename LinkedBuffer< T >::template Subscript< size_t > LinkedBuffer< T >::operator[ ] ( const int index )
{ // Single value setting through subscript operator
return Subscript< size_t > { static_cast< size_t >( index ), this };
}
template < class T >
typename LinkedBuffer< T >::template Subscript< double > LinkedBuffer< T >::operator[ ] ( const double index )
{ // Interpolated setting through subscript operator
return Subscript< double > { index , this };
}
template < class T >
void LinkedBuffer< T >::set ( size_t index, T value )
{
// Single value
ControlMessage hm;
hm.lastSource = this;
hm.type = SINGLE_INDEX;
hm.containedMessage.singleIndexHeader.index = index;
if ( !comQueue.push( &hm, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
// Propagate the value on next update
if ( !comQueue.push( &value, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
template < class T >
void LinkedBuffer< T >::set ( double index, T value )
{ // Linear interpolated set
T values[2]; // Linear interpolated values starting at index x
size_t truncPos = index; // Truncate
if ( truncPos >= 0 && truncPos < memorySize )
{ // Only set value if within bounds ( unsigned )
double fractAmt = index - truncPos;
size_t secondPos = truncPos + 1;
bool fits = ( secondPos < memorySize );
if ( fits )
{
double iFractAmt = ( 1.0 - fractAmt );
values[ 0 ] = ( localMemory[ truncPos ] * fractAmt ) + ( value * iFractAmt );
values[ 1 ] = ( localMemory[ secondPos ] * iFractAmt ) + ( value * fractAmt );
} else {
values[0] = value;
}
ControlMessage hm;
hm.lastSource = this;
hm.type = SINGLE_INDEX;
hm.containedMessage.singleIndexHeader.index = truncPos;
// Propagate the header on next update
if ( !comQueue.push( &hm, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
// Propagate the value(s) on next update
if ( !comQueue.push( values, ( fits ) ? 2 : 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
}
template < class T >
void LinkedBuffer< T >::assign ( size_t startIndex, T* values, size_t numItems )
{
ControlMessage hm;
hm.lastSource = this;
hm.type = SINGLE_INDEX;
hm.containedMessage.singleIndexHeader.index = index;
// Propagate the header on next update
if ( !comQueue.push( &hm, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
// Propagate the value(s) on next update
if ( !comQueue.push( &values, numItems ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
template < class T >
void LinkedBuffer< T >::setRangeToConstant( size_t startIndex, size_t stopIndex, T value )
{
ControlMessage hm;
hm.lastSource = this;
hm.type = SET_RANGE_TO_CONSTANT;
hm.containedMessage.setRangeToConstantHeader.startIndex = startIndex;
hm.containedMessage.setRangeToConstantHeader.stopIndex = stopIndex;
hm.containedMessage.setRangeToConstantHeader.value = value;
// Propagate header
if ( !comQueue.push( &hm, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
template < class T >
void LinkedBuffer< T >::setLine ( double startIndex, T startValue, double stopIndex, T stopValue )
{
double distance = stopIndex - startIndex;
if ( distance < 0 )
{
distance = std::abs( distance );
std::swap( startValue, stopValue );
std::swap( startIndex, stopIndex );
}
if ( distance > 0 )
{ // Disallow div by zero
ControlMessage hm;
hm.lastSource = this;
hm.type = SET_LINE;
hm.containedMessage.setLineHeader.startIndex = startIndex;
hm.containedMessage.setLineHeader.startValue = startValue;
hm.containedMessage.setLineHeader.distance = distance;
hm.containedMessage.setLineHeader.deltaIncrement = ( stopValue - startValue ) / distance;
// Propagate the header on next update
if ( !comQueue.push( &hm, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
}
template < class T >
void LinkedBuffer< T >::setup ( size_t bufferSize )
{
resize( bufferSize );
}
template < class T >
void LinkedBuffer< T >::resize ( size_t bufferSize )
{
localMemory.resize( bufferSize );
memorySize = bufferSize;
trueSize = memorySize;
comQueue.resize( ( bufferSize * tSize ) * 4 ); // This is the memory overhead
}
template < class T >
void LinkedBuffer< T >::resizeBounded ( size_t bufferSize )
{ // Just sets memory size ( unless > trueSize )
ControlMessage hm;
hm.lastSource = this;
hm.type = BOUNDED_MEMORY_RESIZE;
hm.containedMessage.boundedResizeHeader.size = bufferSize;
// Propagate the header on next update
if ( !comQueue.push( &hm, 1 ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
template < class T >
size_t LinkedBuffer< T >::size ( void ) const
{
return memorySize;
}
template < class T >
bool LinkedBuffer< T >::linkTo ( LinkedBuffer< T >* const other )
{
if ( other == this || other == nullptr )
return false;
// TODO: Check here for feedback loop
// TODO: Should effort be made to make this lock-free?
// Reasoning: live linking, not only in setup?
editLinks.lock( );
links.push_back( other );
editLinks.unlock( );
return true;
}
template < class T >
bool LinkedBuffer< T >::unLinkFrom ( LinkedBuffer< T >* const other )
{
if ( other == this || other == nullptr )
return false;
bool success = false;
// TODO: Should effort be made to make this lock-free?
// Reasoning: live linking, not only in setup?
editLinks.lock( );
auto it = std::find( links.begin( ), links.end( ), other );
if ( it != links.end( ) )
{
links.erase( it );
success = true;
}
editLinks.unlock( );
return success;
}
template < class T >
bool LinkedBuffer< T >::update ( void )
{
uint32_t parsedData = 0;
bool anyNewData = false;
RingBufferAny::VariableHeader headerInfo;
while ( comQueue.anyAvailableForPop( headerInfo ) && ( ++parsedData < maxDataToParsePerUpdate ) )
{
if ( headerInfo.type_index == typeid( ControlMessage ) ) {
comQueue.pop( &lastIndexSpecifier, 1 );
switch ( lastIndexSpecifier.type )
{
case SET_RANGE_TO_CONSTANT:
{
auto m = lastIndexSpecifier.containedMessage.setRangeToConstantHeader;
if ( m.stopIndex <= memorySize )
std::fill( localMemory.begin( ) + m.startIndex,
localMemory.begin( ) + m.stopIndex,
m.value );
anyNewData = true;
break;
}
case SET_LINE:
{
auto m = lastIndexSpecifier.containedMessage.setLineHeader;
double currentIndex = m.startIndex;
if ( m.startIndex >= 0 && m.startIndex < memorySize )
{
T startValue = m.startValue;
T deltaIncrement = m.deltaIncrement;
for ( size_t i = 0; i < m.distance; ++i )
{
size_t truncPos = currentIndex; // Truncate
double fractAmt = currentIndex - truncPos;
size_t secondPos = truncPos + 1;
T value = startValue + deltaIncrement * i;
if ( secondPos < memorySize )
{
double iFractAmt = ( 1.0 - fractAmt );
localMemory[ truncPos ] = ( localMemory[ truncPos ] * fractAmt ) + ( value * iFractAmt );
localMemory[ secondPos ] = ( localMemory[ secondPos ] * iFractAmt ) + ( value * fractAmt );
} else {
localMemory[ truncPos ] = value;
}
currentIndex += 1;
}
anyNewData = true;
}
break;
}
case BOUNDED_MEMORY_RESIZE:
{
size_t newSize = lastIndexSpecifier.containedMessage.boundedResizeHeader.size;
if ( newSize <= trueSize )
{
memorySize = newSize;
} else {
// TODO: Allocator
resize( newSize );
}
break;
}
default:
break;
}
// Propagate header
ControlMessage nextIndexSpecifier( lastIndexSpecifier );
nextIndexSpecifier.lastSource = this;
propagate( lastIndexSpecifier.lastSource, &nextIndexSpecifier, 1 );
} else if ( headerInfo.type_index == typeid( T ) ) {
if ( lastIndexSpecifier.type == SINGLE_INDEX )
{
auto m = lastIndexSpecifier.containedMessage.singleIndexHeader;
if ( m.index < memorySize )
{
// Set the value(s) locally
auto setIndex = ( localMemory.begin( ) + m.index ).operator->();
comQueue.pop( setIndex, headerInfo.valuesPassed );
propagate( lastIndexSpecifier.lastSource, setIndex, headerInfo.valuesPassed );
anyNewData = true;
}
} else {
std::runtime_error( "Data was not expected" );
}
lastIndexSpecifier = ControlMessage( ); // Reset
} else {
throw std::runtime_error( "Uncaught data" );
}
}
return anyNewData;
}
template < class T >
template < class TT >
void LinkedBuffer< T >::propagate ( const LinkedBuffer* from, TT* const data, size_t numItems )
{
if ( from == nullptr )
throw std::runtime_error( " Propagation source is null " );
// Propagation is spread across update methods of multiple threads
for ( LinkedBuffer* lb : links )
{
if ( lb != from )
{
if ( !lb->comQueue.push( data, numItems ) )
throw std::runtime_error( "Item(s) does not fit in queue, add auto-resizing here once == non-blocking" );
}
}
}
#endif /* LinkedBuffer_hpp */
//
// PokeGraph.cpp
// Bitalino
//
// Created by James on 21/12/2017.
//
//
#include "PokeGraph.hpp"
void PokeGraph::setup ( std::string label, ofColor textColor, ofColor graphColor, ofColor backgroundColor,
ofRectangle posRect, bool fillGraph, bool dontDrawZero, uint32_t memorySize )
{
this->label = label;
this->textColor = textColor;
this->graphColor = graphColor;
this->backgroundColor = backgroundColor;
this->posRect = posRect;
graphRect = posRect;
graphRect.scaleFromCenter( 0.99f, 0.75f );
this->fillGraph = fillGraph;
this->dontDrawZero = dontDrawZero;
memory.setup( memorySize );
calculateScaling( );
renderToMemory( );
}
void PokeGraph::setResolution ( uint32_t memorySize )
{
memory.resize( memorySize );
calculateScaling( );
renderToMemory( );
}
uint32_t PokeGraph::getResolution ( void ) const
{
return memory.size( );
}
void PokeGraph::pokeValue ( double position, double value )
{
memory[ position * memory.size( ) ] = value;
renderToMemory( );
}
bool PokeGraph::isMouseInside ( ofVec2f mousePos)
{
return graphRect.inside( mousePos );
}
void PokeGraph::mouseActive ( ofVec2f mousePos, int8_t mouseState, bool mouseStateChanged )
{
switch ( mouseState ) {
case 0:
// Left button pressed
if ( mouseStateChanged )
{ // Pressed
initialClickpos = mousePos;
} else {
// Held
memory.setLine( ( initialClickpos.x - graphRect.x ) * screenToMemoryScaling.x, // startIndex
maxVal - ( initialClickpos.y - graphRect.y ) * screenToMemoryScaling.y, // startValue
( mousePos.x - graphRect.x ) * screenToMemoryScaling.x, // StopIndex
maxVal - ( mousePos.y - graphRect.y ) * screenToMemoryScaling.y ); // StopValue
}
break;
case 1:
// Fallthrough to right mouse button
case 2:
// Right button pressed
if ( mouseStateChanged )
{
previousPosition = mousePos;
}
if ( std::fabs( previousPosition.x - mousePos.x ) > memoryToScreenScaling.x )
{ // Smooth out sudden changes
memory.setLine( ( previousPosition.x - graphRect.x ) * screenToMemoryScaling.x, // startIndex
maxVal - ( previousPosition.y - graphRect.y ) * screenToMemoryScaling.y, // startValue
( mousePos.x - graphRect.x ) * screenToMemoryScaling.x, // StopIndex
maxVal - ( mousePos.y - graphRect.y ) * screenToMemoryScaling.y ); // StopValue
} else {
memory[ ( mousePos.x - graphRect.x ) * screenToMemoryScaling.x ] =
maxVal - ( mousePos.y - graphRect.y ) * screenToMemoryScaling.y;
}
previousPosition = mousePos;
break;
default:
// Reset
previousPosition = ofVec2f( );
initialClickpos = ofVec2f( );
// Mouse released
break;
}
}
void PokeGraph::update ( void )
{
if ( memory.update( ) )
{
for ( int i = 0; i < memory.size( ); ++i )
{
double value = std::fabs( memory[ i ] );
if ( value > maxVal )
maxVal = value;
}
calculateScaling( );
renderToMemory( );
// Needs to update
// Redraw
}
}
void PokeGraph::draw ( void )
{
rendered.draw( posRect.position );
}
void PokeGraph::resize ( ofVec2f wh )
{
this->posRect.width = wh.x;
this->posRect.height = wh.y;
graphRect = posRect;
graphRect.scaleFromCenter( 0.99f, 0.8f );
calculateScaling( );
renderToMemory( );
}
void PokeGraph::setRect ( ofRectangle posRect )
{
this->posRect = posRect;
graphRect = posRect;
graphRect.scaleFromCenter( 0.99f, 0.8f );
calculateScaling( );
renderToMemory( );
}
void PokeGraph::redraw ( void )
{
calculateScaling( );
renderToMemory( );
}
void PokeGraph::calculateScaling ( void )
{
memoryToScreenScaling = ofVec2f( graphRect.width / static_cast< float > ( memory.size( ) - 1 ), graphRect.height / maxVal );
screenToMemoryScaling = ofVec2f( static_cast< float > ( memory.size( ) - 1 ) / graphRect.width, maxVal / graphRect.height );
}
void PokeGraph::renderToMemory ( void )
{
ofRectangle relativePosRect = ofRectangle( 2, 0, posRect.width - 2, posRect.height - 2 );
ofRectangle relativeGraphRect = ofRectangle( graphRect.x - posRect.x,
graphRect.y - posRect.y,
graphRect.width,
graphRect.height );
rendered.clear( );
ofFbo::Settings settings;
settings.width = posRect.width;
settings.height = posRect.height;
settings.useDepth = false;
settings.useStencil = false;
rendered.allocate( settings );
rendered.begin( );
ofClear( 255, 255, 255, 0 );
ofSetLineWidth( 2 );
//ofBackground(255, 100, 100, 255);
// Background
// Fill
ofFill( );
ofSetColor( backgroundColor, 100 );
ofDrawRectRounded( relativePosRect, 5 );
// Label
ofSetColor( textColor );
ofDrawBitmapString( label, relativePosRect.x + 2, relativePosRect.y + 14 );
float absWidth = relativeGraphRect.width ;
// Graph
if ( fillGraph )
{
ofBeginShape( );
// Fill
ofFill( );
ofSetColor( graphColor, 100 );
ofVertex( relativeGraphRect.x, ( relativeGraphRect.y + relativeGraphRect.height ) );
for ( int32_t i = 0; i < memory.size( ); ++i )
{
double value = memory[ i ];
if ( !dontDrawZero || ( dontDrawZero && value != 0 ) )
ofVertex( relativeGraphRect.x + i * memoryToScreenScaling.x,
( relativeGraphRect.y + relativeGraphRect.height ) - value * memoryToScreenScaling.y );
else
{
ofVertex( relativeGraphRect.x + i * memoryToScreenScaling.x,
( relativeGraphRect.y + relativeGraphRect.height ) );
}
}
ofVertex( relativeGraphRect.x + absWidth, ( relativeGraphRect.y + relativeGraphRect.height ) );
ofEndShape( );
}
ofBeginShape( );
// Outline
ofNoFill( );
ofSetColor( graphColor );
ofVertex( relativeGraphRect.x, ( relativeGraphRect.y + relativeGraphRect.height ) );
for ( int32_t i = 0; i < memory.size( ); ++i )
{
double value = std::fabs( memory[ i ] );
if ( !dontDrawZero || ( dontDrawZero && value != 0 ) )
ofVertex( relativeGraphRect.x + i * memoryToScreenScaling.x,
( relativeGraphRect.y + relativeGraphRect.height ) - value * memoryToScreenScaling.y );
else
{
ofEndShape( );
ofBeginShape( );
}
}
ofVertex( relativeGraphRect.x + absWidth, ( relativeGraphRect.y + relativeGraphRect.height ) );
ofEndShape( );
ofSetColor( backgroundColor, 175 );
ofSetLineWidth( 1.5f );
ofDrawLine( relativeGraphRect.x, relativeGraphRect.y + relativeGraphRect.height,
relativeGraphRect.x + relativeGraphRect.width, relativeGraphRect.y + relativeGraphRect.height );
// Outline
ofNoFill( );
ofSetColor( backgroundColor );
ofDrawRectRounded( relativePosRect, 5 );
rendered.end( );
ofSetColor( 255, 255, 255 );
}
//
// PokeGraph.hpp
// Bitalino
//
// Created by James on 21/12/2017.
//
//
// A grapb which allows input and makes use of a threadsafe non-locking buffer
#ifndef PokeGraph_hpp
#define PokeGraph_hpp
#include <stdio.h>
#include "ofMain.h"
#include "LinkedBuffer.hpp"
class PokeGraph {
public:
LinkedBuffer< double > memory; // Threadsafe non-(b)locking, linkable buffer
void setup ( std::string label, ofColor textColor, ofColor graphColor, ofColor backgroundColor, ofRectangle posRect, bool fillGraph, bool dontDrawZero, uint32_t memorySize );
void setResolution ( uint32_t memorySize );
uint32_t getResolution ( void ) const;
void pokeValue ( double position, double value );
bool isMouseInside ( ofVec2f mousePos );
void mouseActive ( ofVec2f mousePos, int8_t mouseState, bool mouseStateChanged );
void update ( void );
void draw ( void );
void resize ( ofVec2f wh );
void setRect ( ofRectangle posRect );
bool fillGraph = false;
bool dontDrawZero = false;
ofColor textColor, graphColor, backgroundColor;
void redraw ( void );
protected:
void calculateScaling ( void );
void renderToMemory ( void );
private:
ofFbo rendered;
ofVec2f memoryToScreenScaling;
ofVec2f screenToMemoryScaling;
ofVec2f initialClickpos;
ofVec2f previousPosition;
ofRectangle graphRect;
ofRectangle posRect;
std::string label;
double maxVal = 1.0;
};
#endif /* PokeGraph_hpp */
//
// RapidBitalino.cpp
// Bitalino
//
// Created by James on 20/11/2017.
//
//
#include "RapidBitalino.h"
RapidBitalino::RapidBitalino ( void )
{
}
RapidBitalino::~RapidBitalino ( void )
{
}
void RapidBitalino::setup ( std::string nameOfBitalino, uint32_t sampleEveryN )
{
setDownsample( downsample );
// Set up the bitalino
bitalinoThread.setup( nameOfBitalino, 2000,
1000, {0, 1, 2, 3, 4, 5},
100 );
// Start the bitalino's thread ( and allow it to start searching for the device )
// Start receiving data as soon as it's connected
bitalinoThread.setRecording( true );
// Initialize with 0, 0
bitalinoProcessedOutput = std::vector< double >( NUM_INPUTS_FOR_TRAINING, 0.0 );
// Setup OSC
sender.setup( HOST, PORT );
}
void RapidBitalino::setDownsample ( uint32_t downsample )
{
this->downsample.store( downsample );
}
bool RapidBitalino::processBitalinoOutput( BITalino::Frame& frame )
{
std::vector< double > output;
discreteValCalibrated = static_cast< float >( frame.analog [ 0 ] - 512.0 );
testCheapRMSdiscrete -= testCheapRMSdiscrete / M;
testCheapRMSdiscrete += ( discreteValCalibrated * discreteValCalibrated ) / M;
if ( sampleValue >= downsample.load( ) )
{
double rms = sqrt( testCheapRMSdiscrete );
double deltaRMS = rms - previousValue;
double bayesianFiltered = rapidProcess.bayesFilter( discreteValCalibrated / 1000.0 );
buttonState = static_cast< double >( frame.digital [ 0 ] );
double accelZ = static_cast< double > ( frame.analog [ 4 ] );
double deltaAccelZ = accelZ - previousAccelZ;
bitalinoProcessedOutput = std::vector< double >( NUM_INPUTS_FOR_TRAINING, rms ); // Hack for more hidden nodes atm.
// UNCOMMENT TO GET DEBUG OSC DATA
// OSC MESSAGE ]
/*
ofxOscMessage d;
d.setAddress( "/BitalinoRawDigital" );
d.addIntArg( frame.digital [ 0 ] );
d.addIntArg( frame.digital [ 1 ] );
d.addIntArg( frame.digital [ 2 ] );
d.addIntArg( frame.digital [ 3 ] );
sender.sendMessage( d );
ofxOscMessage a;
a.setAddress( "/BitalinoRawAnalog" );
a.addIntArg( frame.analog [ 0 ] );
a.addIntArg( frame.analog [ 1 ] );
a.addIntArg( frame.analog [ 2 ] );
a.addIntArg( frame.analog [ 3 ] );
a.addIntArg( frame.analog [ 4 ] );
a.addIntArg( frame.analog [ 5 ] );
sender.sendMessage( a );
ofxOscMessage b;
b.setAddress( "/BayesianFilter" );
b.addDoubleArg( bayesianFiltered );
sender.sendMessage( b );
ofxOscMessage m;
m.setAddress( "/RMS" );
m.addDoubleArg( rms );
sender.sendMessage( m );
ofxOscMessage dt;
dt.setAddress( "/DeltaRMS" );
dt.addDoubleArg( deltaRMS );
sender.sendMessage( dt );
ofxOscMessage at;
at.setAddress( "/DeltaACCELZ" );
at.addDoubleArg( deltaAccelZ );
sender.sendMessage( at );
ofxOscMessage n;
n.setAddress( "/ProcessedRaw" );
n.addDoubleArg( discreteValCalibrated );
sender.sendMessage( n );
*/
// --
sampleValue = 0;
previousValue = rms;
previousAccelZ = accelZ;
return true;
}
++sampleValue;
return false;
}
std::vector< double > RapidBitalino::update ( void )
{
std::vector< double > output = std::vector<double>( NUM_INPUTS_FOR_TRAINING, 0.0 );
double numItemsToAverage = 0.0;
bool buttonPress = previousButtonPress;
uint32_t items = bitalinoThread.data.items_available_for_read( );
if ( items )
{
buttonPress = false;
BITalino::Frame data [ items ];
bitalinoThread.data.pop( data, items );
for ( uint32_t i = 0; i < items; ++i )
{
if ( processBitalinoOutput( data [ i ] ) )
{ // Downsample scope
if ( !data [ i ].digital [ 0 ] )
buttonPress = true;
assert( output.size( ) == bitalinoProcessedOutput.size( ) );
// Sum the data
std::transform( output.begin( ), output.end( ),
bitalinoProcessedOutput.begin( ), output.begin( ), std::plus<double>( ) );
++numItemsToAverage;
}
}
// calculate the average
if ( numItemsToAverage > 1.0 )
std::transform( output.begin( ), output.end( ), output.begin( ),
std::bind2nd( std::divides<double>( ), numItemsToAverage ) );
} else {
output = bitalinoProcessedOutput;
}
if ( buttonPressedCallback && buttonPress && !previousButtonPress )
buttonPressedCallback( );
else if ( buttonReleasedCallback && !buttonPress && previousButtonPress )
buttonReleasedCallback( );
previousButtonPress = buttonPress;
return output;
}
void RapidBitalino::draw ( void )
{
// Todo, draw some trivial debug interface items
// ie:
/*
- BITalino isConnected ( )
- BITalino isRecording ( ) // if receiving data ?
- RapidMixThread isTraining ( )
- input and output values
*/
//ofDrawBox( ofVec2f( 400,( tOutput/4 )*600 ), 50 );
//ofDrawSphere( 512, 384, ( tOutput/4 )*600 );
drawTextLabel( ( bitalinoThread.isConnected( ) ) ? "Bitalino connected" : "Looking for BITalino... " + ofToString( bitalinoThread.getConnectionTries( ) ) + " tries", ofVec2f( 0,0 ), ofColor( 0,0,0 ), ofColor( 255,255,255 ), ofxBitmapText::TextAlignment::LEFT, false );
}
void RapidBitalino::stop ( void )
{
bitalinoThread.stopThread( );
}
//
// RapidBitalino.h
// Bitalino
//
// Created by James on 20/11/2017.
//
//
#ifndef RapidBitalino_h
#define RapidBitalino_h
#include <algorithm>
#include <functional>
#include "ofMain.h"
#include "BitalinoThread.h"
#include "rapidmix.h"
// TODO ifdefs for osc debug
#include "ofxOsc.h"
#include "Tools.h"
#define HOST "localhost"
#define PORT 8338
#define NUM_INPUTS_FOR_TRAINING 1
class RapidBitalino {
public:
BitalinoThread bitalinoThread; // For isConnected etc
std::function< void ( void ) > buttonPressedCallback = nullptr;
std::function< void ( void ) > buttonReleasedCallback = nullptr;
RapidBitalino ( void );
~RapidBitalino ( void );
void setup ( std::string nameOfBitalino, uint32_t sampleEveryN );
void setDownsample ( uint32_t downsample );
std::vector< double > update ( void ); // returns averages of all data that is currently waiting in buffer
void draw ( void );
void stop ( void );
private:
// OSC
ofxOscSender sender;
// Private member funcs
bool processBitalinoOutput( BITalino::Frame& frame );
// My own stuff
uint32_t samplesToCollect;
std::vector< double > currentTrainingOutput;
// Processing the BITalino output
double discreteVal = 0;
double discreteValCalibrated = 0;
double N = 1000;
double M = 100;
double testCheapRMSdiscrete = 0;
double buttonState = 0;
double previousAccelZ = 0.0;
std::vector< double > bitalinoProcessedOutput;
bool previousButtonPress = false;
// rapidStream for Bayesian filter
rapidmix::rapidStream rapidProcess;
//debug
uint32_t sampleValue = 0;
std::atomic< uint32_t > downsample;
double previousValue = 0;
};
#endif /* RapidBitalino_h */
//
// RapidMixThread/h
// This implementation of rapidMix trains in a different thread and has a synchronized
// update process with your main thread, which triggers a ( sync ) callback on doneTraining.
// Allows you to train one model and keep using it while the next one trains, the new
// model will be swapped in sync with the thread the update is called in once it's done.
// Created by James on 19/11/2017.
//
//
#ifndef RapidMixThread_h
#define RapidMixThread_h
#include <algorithm>
#include <functional>
#include <unistd.h> // usleep
#include "ThreadedProcess.h"
#include "rapidmix.h"
typedef rapidmix::staticRegression selectedTraining;
class RapidMixThread : public ThreadedProcess
{
public:
std::function< void ( void ) > doneTrainingCallback = nullptr;
selectedTraining* model = nullptr; // The model to be accessed from outside this thread
RapidMixThread ( void ) : ThreadedProcess ( )
{
training.store( false );
doneTraining.store( false );
}
~RapidMixThread ( void )
{
stopThread( );
if ( newModel )
delete newModel;
if ( model )
delete model;
printf ( "Stop rapidmixthread\n" );
}
void setup ( void )
{
model = createModel( );
startThread( 100000 ); // start training thread with sleep time of 100 ms
}
bool train ( rapidmix::trainingData& data )
{
if ( !training.load( ) )
{
this->trainingData = data;
training.store( true );
return true;
}
return false;
}
bool isTraining ( void ) const
{
return training.load( );
}
void update ( void )
{ // To be called in your main loop
if ( doneTraining.load( ) )
{
if ( newModel )
{
std::swap( model, newModel );
} else
throw std::runtime_error( "New model was not created successfully" );
if ( doneTrainingCallback )
doneTrainingCallback( );
//printf( "Done training\n" );
doneTraining.store( false );
}
}
protected:
selectedTraining* createModel ( void )
{
// Doing this to prevent repetitive stuff when testing different models
selectedTraining* t = new selectedTraining( );
t->setNumHiddenNodes( 18 );
return t;
}
void mainThreadCallback ( void )
{ // Training thread
if ( isTraining( ) )
{
if ( newModel )
delete newModel;
newModel = createModel( );
//printf( "Started training in thread... \n" );
if ( !trainingData.trainingSet.empty( ) && trainingData.trainingSet[0].elements.size( ) > 1 )
if ( !newModel->train( trainingData ) )
throw std::runtime_error( "Training failed" );;
training.store( false );
doneTraining.store( true );
}
}
std::atomic< bool > training;
std::atomic< bool > doneTraining;
selectedTraining* newModel = nullptr;
rapidmix::trainingData trainingData;
private:
};
#endif /* RapidMixThread_h */
/*
* RingBuffer.hpp
*/
#ifndef RINGBUFFER_HPP
#define RINGBUFFER_HPP
#include <iostream>
#include <atomic>
#include <string>
#include <unistd.h>
#include <string.h> // memcpy
#include <cstring>
template <class T>
class RingBuffer
{
public:
RingBuffer();
RingBuffer(unsigned long size);
~RingBuffer();
void setup(unsigned long size);
unsigned long push(const T* data, unsigned long n);
unsigned long pop(T* data, unsigned long n);
unsigned long getWithoutPop(T* data, unsigned long n);
unsigned long items_available_for_write() const;
unsigned long items_available_for_read() const;
bool isLockFree() const;
void pushMayBlock(bool block);
void popMayBlock(bool block);
void setBlockingNap(unsigned long blockingNap);
void setSize(unsigned long size);
void reset();
private:
unsigned long array_size;
T* buffer = nullptr;
long type_size; // also depends on #channels
std::atomic<unsigned long> tail_index; // write pointer
std::atomic<unsigned long> head_index; // read pointer
bool blockingPush;
bool blockingPop;
useconds_t blockingNap = 500;
}; // RingBuffer{}
template <class T>
RingBuffer<T>::RingBuffer()
{
tail_index = 0;
head_index = 0;
blockingPush = false;
blockingPop = false;
} // RingBuffer()
template <class T>
RingBuffer<T>::RingBuffer(unsigned long array_size)
{
tail_index = 0;
head_index = 0;
blockingPush = false;
blockingPop = false;
setup(array_size);
} // RingBuffer()
template <class T>
RingBuffer<T>::~RingBuffer()
{
if (buffer)
delete [] buffer;
} // ~RingBuffer()
template <class T>
void RingBuffer<T>::setup(unsigned long array_size)
{
if (buffer)
delete [] buffer;
this->array_size = array_size;
buffer = new T [array_size](); // allocate storage
type_size = sizeof(T);
}
template <class T>
unsigned long RingBuffer<T>::items_available_for_write() const
{
long pointerspace = head_index.load()-tail_index.load(); // signed
if(pointerspace > 0) return pointerspace; // NB: > 0 so NOT including 0
else return pointerspace + array_size;
} // items_available_for_write()
template <class T>
unsigned long RingBuffer<T>::items_available_for_read() const
{
long pointerspace = tail_index.load() - head_index.load(); // signed
if(pointerspace >= 0) return pointerspace; // NB: >= 0 so including 0
else return pointerspace + array_size;
} // items_available_for_read()
template <class T>
void RingBuffer<T>::pushMayBlock(bool block)
{
this->blockingPush = block;
} // pushMayBlock()
template <class T>
void RingBuffer<T>::popMayBlock(bool block)
{
this->blockingPop = block;
} // popMayBlock()
template <class T>
void RingBuffer<T>::setBlockingNap(unsigned long blockingNap)
{
this->blockingNap = blockingNap;
} // setBlockingNap()
/*
* Try to write as many items as possible and return the number actually written
*/
template <class T>
unsigned long RingBuffer<T>::push(const T* data, unsigned long n)
{
unsigned long space = array_size, n_to_write, memory_to_write, first_chunk, second_chunk, current_tail;
//space = items_available_for_write();
if(blockingPush)
{
while((space = items_available_for_write()) < n)
{ // blocking
usleep(blockingNap);
} // while, possibly use a system level sleep operation with CVAR?
} // if
n_to_write = (n <= space) ? n : space; // limit
current_tail = tail_index.load();
if(current_tail + n_to_write <= array_size)
{ // chunk fits without wrapping
memory_to_write = n_to_write * type_size;
memcpy(buffer+current_tail, data, memory_to_write);
tail_index.store(current_tail + n_to_write);
}
else { // chunk has to wrap
first_chunk = array_size - current_tail;
memory_to_write = first_chunk * type_size;
memcpy(buffer + current_tail, data, memory_to_write);
second_chunk = n_to_write - first_chunk;
memory_to_write = second_chunk * type_size;
memcpy(buffer, data + first_chunk, memory_to_write);
tail_index.store(second_chunk);
}
return n_to_write;
} // push()
/*
* Try to read as many items as possible and return the number actually read
*/
template <class T>
unsigned long RingBuffer<T>::pop(T* data, unsigned long n)
{
unsigned long space = array_size, n_to_read, memory_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); //if checking outside of thread, not necessary?
if(blockingPop)
{
while((space = items_available_for_read()) < n)
{ // blocking
usleep(blockingNap);
} // while
} // if
if(space == 0)
return 0;
n_to_read = (n <= space) ? n : space; // limit
current_head = head_index.load();
if(current_head + n_to_read <= array_size)
{ // no wrapping necessary
memory_to_read = n_to_read * type_size;
memcpy(data, buffer + current_head, memory_to_read);
head_index.store(current_head + n_to_read);
}
else { // read has to wrap
first_chunk = array_size - current_head;
memory_to_read = first_chunk * type_size;
memcpy(data, buffer + current_head, memory_to_read);
second_chunk = n_to_read - first_chunk;
memory_to_read = second_chunk * type_size;
memcpy(data + first_chunk, buffer, memory_to_read);
head_index.store(second_chunk);
}
return n_to_read;
} // pop()
/*
* Try to read as many items as possible and return the number actually read
*/
template <class T>
unsigned long RingBuffer<T>::getWithoutPop(T* data, unsigned long n)
{
unsigned long space = array_size, n_to_read, memory_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); //if checking outside of thread, not necessary?
if(blockingPop)
{
while((space = items_available_for_read()) < n)
{ // blocking
usleep(blockingNap);
} // while
} // if
if(space == 0)
return 0;
n_to_read = (n <= space) ? n : space; // limit
current_head = head_index.load();
if(current_head + n_to_read <= array_size)
{ // no wrapping necessary
memory_to_read = n_to_read * type_size;
memcpy(data, buffer + current_head, memory_to_read);
//head_index.store(current_head + n_to_read);
}
else { // read has to wrap
first_chunk = array_size - current_head;
memory_to_read = first_chunk * type_size;
memcpy(data, buffer + current_head, memory_to_read);
second_chunk = n_to_read - first_chunk;
memory_to_read = second_chunk * type_size;
memcpy(data + first_chunk, buffer, memory_to_read);
//head_index.store(second_chunk);
}
return n_to_read;
} // getWithoutPop()
template <class T>
bool RingBuffer<T>::isLockFree() const
{
return (tail_index.is_lock_free() && head_index.is_lock_free());
} // isLockFree()
template <class T>
void RingBuffer<T>::setSize(unsigned long size)
{
if (buffer)
delete [] buffer;
this->array_size = array_size;
buffer = new T [array_size](); // allocate storage
type_size = sizeof(T);
tail_index.store(0);
head_index.store(0);
}
template <class T>
void RingBuffer<T>::reset()
{
tail_index.store(0);
head_index.store(0);
} // isLockFree()
#endif
//
// RingBufferAny.hpp
// Can be used as a non-blocking eventpoll/ringbuffer without having to write unions etc ahead of time
// Allowing rapid expansion of inter-thread communication with several datatypes being stored in the same queue.
// Created by James Frink on 16/12/2017.
// Copyright © 2017 James Frink. All rights reserved.
//
#ifndef RingBufferAny_h
#define RingBufferAny_h
#include <atomic>
#include <memory>
#include <typeindex>
#include <type_traits>
#include "RingBufferV.hpp"
#include "Spinlock.hpp"
// WARNING: stdlib objects that use dynamic allocation ( string, list, vector etc are not supported atm )
// If you use your own structures, make sure to use primitive types and stack allocated arrays
// Spinlocks in this are only to protect against wrongful use and when resizing the buffer
// Note that resizing the buffer to a smaller one than before might cause a complete reset of the heads
class RingBufferAny : protected RingBufferV< int8_t > {
public:
struct VariableHeader {
std::type_index type_index = typeid( nullptr );
size_t sizeOfInput = 0;
size_t valuesPassed = 0;
};
RingBufferAny ( void ) : RingBufferV< int8_t > ( ) {}
RingBufferAny ( size_t sizeOfBuffer ) : RingBufferV< int8_t > ( )
{
setup( sizeOfBuffer );
}
~RingBufferAny ( void ) {}
void setup ( size_t sizeOfBuffer )
{
resize( sizeOfBuffer );
}
void resize ( size_t size )
{
inputSpin.lock( );
outputSpin.lock( );
this->sizeOfBuffer.store( size );
this->RingBufferV< int8_t >::setSize( size );
inputSpin.unlock( );
outputSpin.unlock( );
}
size_t size ( void ) const
{
return sizeOfBuffer.load( );
}
template <class T>
void adjustSizeIfNeeded( size_t numElements )
{
size_t totalSize = numElements * sizeof( T );
if ( totalSize > sizeOfBuffer.load( ) )
{
setup( totalSize );
}
}
void reset ( void )
{
this->RingBufferV< int8_t >::reset( );
}
// TODO add vector/list serialization support
bool anyAvailableForPop ( VariableHeader& outputHeader )
{ // Return if an element is available to be popped out of the queue, as well as the header information if available
outputSpin.lock( ); // Lock
size_t avForRead = items_available_for_read( );
if ( avForRead > headerSize )
{
this->RingBufferV< int8_t >::getWithoutPop( reinterpret_cast< int8_t* > ( &outputHeader ), headerSize ); // Unlock case 1
outputSpin.unlock( );
return ( avForRead >= ( headerSize + outputHeader.sizeOfInput ) );
}
outputSpin.unlock( ); // Unlock case 2
return false;
}
template <class T>
bool push (T* inputData, size_t numValues)
{ // Push specified number of values starting at pointer inputData on to the queue
VariableHeader vh;
vh.type_index = std::type_index( typeid( T ) );
vh.valuesPassed = numValues;
vh.sizeOfInput = sizeof( *inputData ) * numValues;
inputSpin.lock( ); // Lock
if ( ( headerSize + vh.sizeOfInput ) <= items_available_for_write( ) )
{
// Write header
this->RingBufferV< int8_t >::push( reinterpret_cast< int8_t* > ( &vh ), headerSize );
// Write object(s)
this->RingBufferV< int8_t >::push( reinterpret_cast< int8_t* > ( inputData ), vh.sizeOfInput );
inputSpin.unlock( ); // Unlock case 1
return true;
}
inputSpin.unlock( ); // Unlock case 2
return false;
}
template <class T>
void pop ( T* output, size_t outputSize )
{ // Pop single or multiple values from queue
VariableHeader vh;
outputSpin.lock( ); // Lock
size_t avForRead = items_available_for_read( );
// Get Header
if ( avForRead < headerSize )
throw std::runtime_error( "Not enough data in buffer to pop header, use anyAvailableForPop to check this!" );
else
this->RingBufferV< int8_t >::pop( reinterpret_cast< int8_t* > ( &vh ), headerSize );
if ( std::type_index( typeid( T ) ) != vh.type_index )
throw std::runtime_error( "Type matching error!");
if ( vh.valuesPassed > outputSize ) // Possibility: add seperate unloading (with moveable header?)
throw std::runtime_error( "Number of values passed on push will not fit in pop structure, use the correct return structure." );
// Get Object(s)
if ( avForRead < vh.sizeOfInput )
throw std::runtime_error( "Not enough data in buffer to pop object(s), use anyAvailableForPop to check this!" );
else
this->RingBufferV<int8_t>::pop( reinterpret_cast< int8_t* > ( output ), vh.sizeOfInput );
outputSpin.unlock( ); // Unlock
}
protected:
std::atomic< size_t > sizeOfBuffer;
static const size_t headerSize = sizeof( VariableHeader );
private:
Spinlock inputSpin; // Placeholder type
Spinlock outputSpin;// ''
};
#endif /* RingBufferAny_h */
/*
* RingBufferV.hpp
*/
#ifndef RingBufferV_HPP
#define RingBufferV_HPP
#include <iostream>
#include <atomic>
#include <string>
#include <unistd.h>
#include <string.h> // memcpy
#include <cstring>
#include <vector>
// Vector implementation of RingBuffer for better resizing + allowing later implementation of own non-blocking freestore allocator without having to resort to using boost lib
template <class T>
class RingBufferV
{
public:
RingBufferV ( void );
RingBufferV ( size_t size );
~RingBufferV ( void );
void setup ( size_t size );
size_t push ( const T* data, size_t n );
size_t pop ( T* data, size_t n );
size_t getWithoutPop ( T* data, size_t n );
size_t items_available_for_write ( void ) const;
size_t items_available_for_read ( void ) const;
bool isLockFree ( void ) const;
void pushMayBlock ( bool block );
void popMayBlock ( bool block );
void setBlockingNap ( size_t blockingNap );
void setSize ( size_t size );
void reset ( void );
private:
size_t array_size;
std::vector< T > buffer;
static constexpr long type_size = sizeof( T ); // Not used atm
std::atomic< size_t > tail_index = { 0 }; // write pointer
std::atomic< size_t > head_index = { 0 }; // read pointer
bool blockingPush;
bool blockingPop;
useconds_t blockingNap = 500; // 0.5 ms
}; // RingBufferV{}
template < class T >
RingBufferV< T >::RingBufferV ( void )
{
blockingPush = false;
blockingPop = false;
} // RingBufferV( void )
template < class T >
RingBufferV< T >::RingBufferV ( size_t array_size )
{
blockingPush = false;
blockingPop = false;
setup( array_size );
} // RingBufferV( void )
template < class T >
RingBufferV< T >::~RingBufferV ( void )
{
} // ~RingBufferV( void )
template < class T >
void RingBufferV< T >::setup ( size_t array_size )
{
setSize( array_size ); // allocate storage
}
template < class T >
size_t RingBufferV< T >::items_available_for_write ( void ) const
{
long pointerspace = head_index.load( ) - tail_index.load( ); // signed
if ( pointerspace > 0 )
return pointerspace; // NB: > 0 so NOT including 0
else
return pointerspace + array_size;
} // items_available_for_write( void )
template < class T >
size_t RingBufferV< T >::items_available_for_read ( void ) const
{
long pointerspace = tail_index.load( ) - head_index.load( ); // signed
if ( pointerspace >= 0 )
return pointerspace; // NB: >= 0 so including 0
else
return pointerspace + array_size;
} // items_available_for_read( void )
template < class T >
void RingBufferV< T >::pushMayBlock ( bool block )
{
this->blockingPush = block;
} // pushMayBlock( void )
template < class T >
void RingBufferV< T >::popMayBlock ( bool block )
{
this->blockingPop = block;
} // popMayBlock( void )
template < class T >
void RingBufferV< T >::setBlockingNap ( size_t blockingNap )
{
this->blockingNap = blockingNap;
} // setBlockingNap( void )
/*
* Try to write as many items as possible and return the number actually written
*/
template < class T >
size_t RingBufferV< T >::push ( const T* data, size_t n )
{
size_t space = array_size, n_to_write, first_chunk, second_chunk, current_tail;
//space = items_available_for_write();
if ( blockingPush )
{
while ( ( space = items_available_for_write( ) ) < n )
{ // blocking
usleep( blockingNap );
} // while, Any better solutions to this?
} // if
n_to_write = ( n <= space ) ? n : space; // limit
current_tail = tail_index.load( );
if ( current_tail + n_to_write <= array_size )
{ // chunk fits without wrapping
std::copy_n( data, n_to_write, buffer.begin( ) + current_tail );
tail_index.store( current_tail + n_to_write );
} else { // chunk has to wrap
first_chunk = array_size - current_tail;
std::copy_n( data, first_chunk, buffer.begin( ) + current_tail );
second_chunk = n_to_write - first_chunk;
std::copy_n( data + first_chunk, second_chunk, buffer.begin( ) );
tail_index.store( second_chunk );
}
return n_to_write;
} // push( const T* data, size_t n )
/*
* Try to read as many items as possible and return the number actually read
*/
template < class T >
size_t RingBufferV< T >::pop ( T* data, size_t n )
{
size_t space = array_size, n_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); // if checking outside of thread, not necessary?
if ( blockingPop )
{
while ( ( space = items_available_for_read( ) ) < n )
{ // blocking
usleep( blockingNap );
} // while
} // if
if ( space == 0 )
return 0;
n_to_read = ( n <= space ) ? n : space; // limit
current_head = head_index.load( );
if ( current_head + n_to_read <= array_size )
{ // no wrapping necessary
std::copy_n( buffer.begin( ) + current_head, n_to_read, data );
head_index.store( current_head + n_to_read );
} else { // read has to wrap
first_chunk = array_size - current_head;
std::copy_n( buffer.begin( ) + current_head, first_chunk, data );
second_chunk = n_to_read - first_chunk;
std::copy_n( buffer.begin( ), second_chunk, data + first_chunk );
head_index.store( second_chunk );
}
return n_to_read;
} // pop( T* data, size_t n )
/*
* Try to read as many items as possible and return the number actually read
*/
template < class T >
size_t RingBufferV< T >::getWithoutPop ( T* data, size_t n )
{
size_t space = array_size, n_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); //if checking outside of thread, not necessary?
if ( blockingPop )
{
while ( ( space = items_available_for_read( ) ) < n )
{ // blocking
usleep( blockingNap );
} // while
} // if
if ( space == 0 )
return 0;
n_to_read = ( n <= space ) ? n : space; // limit
current_head = head_index.load( );
if ( current_head + n_to_read <= array_size )
{ // no wrapping necessary
std::copy_n( buffer.begin( ) + current_head, n_to_read, data );
//head_index.store(current_head + n_to_read);
} else { // read has to wrap
first_chunk = array_size - current_head;
std::copy_n( buffer.begin( ) + current_head, first_chunk, data );
second_chunk = n_to_read - first_chunk;
std::copy_n( buffer.begin( ), second_chunk, data + first_chunk );
//head_index.store(second_chunk);
}
return n_to_read;
} // getWithoutPop( T* data, size_t n )
template <class T>
bool RingBufferV<T>::isLockFree ( void ) const
{
return ( tail_index.is_lock_free( ) && head_index.is_lock_free( ) );
} // isLockFree( void )
template <class T>
void RingBufferV< T >::setSize( size_t size )
{
this->array_size = size;
buffer.resize( array_size ); // allocate storage
if ( tail_index.load( ) <= array_size || head_index.load( ) <= array_size )
{
reset( );
}
}
template <class T>
void RingBufferV<T>::reset( void )
{
tail_index.store( 0 );
head_index.store( 0 );
} // isLockFree( void )
#endif
//
// SigTools.cpp
//
//
// Created by James on 08/01/17.
//
//
#include "SigTools.hpp"
double MTOF ( unsigned char midi ) {
return pow( 2, static_cast< double >( midi-69 ) / 12.0 ) * 440.0;
}
double FTOM ( double freq ) {
return 12.0 * log2( freq / 440.0 ) + 69;
}
//
// SigTools.hpp
//
//
// Created by James on 08/01/17.
//
//
#ifndef SigTools_hpp
#define SigTools_hpp
#include <stdio.h>
#include <math.h>
double MTOF ( unsigned char midi );
double FTOM ( double freq );
#endif /* SigTools_hpp */
//
// Spinlock.h
// beladrum
//
// Created by James on 11/05/17.
// Copyright © 2017 HKU. All rights reserved.
//
#ifndef Spinlock_h
#define Spinlock_h
#include <atomic>
class Spinlock {
std::atomic_flag locked = ATOMIC_FLAG_INIT ;
public:
void lock ( void ) {
while (locked.test_and_set(std::memory_order_acquire)) { ; }
}
void unlock ( void ) {
locked.clear(std::memory_order_release);
}
};
#endif /* Spinlock_h */
//
// ThreadedProcess.h
// Bitalino
//
// Created by James on 19/11/2017.
//
//
#ifndef ThreadedProcess_h
#define ThreadedProcess_h
#include <stdio.h>
#include <stdint.h>
#include <functional>
#include <thread>
#include <atomic>
class ThreadedProcess {
public:
ThreadedProcess ( void )
{
running.store( false );
}
virtual ~ThreadedProcess ( void )
{
stopThread( );
}
bool isThreadRunning ( void ) const
{
return running.load( );
}
// ( re )Start main thread
void startThread ( uint64_t threadSleepTime = 1 )
{
this->threadSleepTime = threadSleepTime;
if ( running.load( ) )
{ // Thread is already running
stopThread( );
}
running.store( true );
processMainThread = std::thread( &ThreadedProcess::mainLoop, this ); // Spawn thread
}
// Stop main thread
void stopThread ( void )
{
if ( running.load( ) )
{
running.store( false );
processMainThread.join( );
}
}
protected:
virtual void mainThreadCallback ( void ) = 0;
std::thread processMainThread;
private:
void mainLoop ( void )
{
while ( running.load( ) )
{
mainThreadCallback( );
usleep( threadSleepTime );
}
}
std::atomic< bool > running;
uint64_t threadSleepTime = 1;
};
#endif /* ThreadedProcess_h */
//
// Timer.h
// Bitalino
//
// Created by James on 28/11/2017.
//
//
#ifndef Timer_h
#define Timer_h
#include <stdio.h>
#include <chrono>
class Timer {
public:
uint64_t getTimeElapsed ( void )
{ // Since last call of reset
return std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::high_resolution_clock::now() - start ).count( );
}
void reset ( void )
{
start = std::chrono::high_resolution_clock::now( );
}
private:
std::chrono::high_resolution_clock::time_point start;
};
#endif /* Timer_h */
//
// Tools.h
// Bitalino
//
// Created by James on 22/11/2017.
//
//
#ifndef Tools_h
#define Tools_h
#include "ofMain.h"
namespace ofxBitmapText {
enum class TextAlignment {
LEFT,
CENTER,
RIGHT
};
static inline void drawTextLabel ( std::string label, ofVec2f position, ofColor labelBackgroundColor, ofColor stringColor, TextAlignment alignment, bool drawAbove )
{
uint32_t strLenPix = label.length( )*8;
switch ( alignment ) {
case TextAlignment::LEFT:
// No exception
break;
case TextAlignment::CENTER:
position.x -= strLenPix / 2;
break;
case TextAlignment::RIGHT:
position.x -= strLenPix;
break;
default:
break;
}
ofFill( );
ofSetColor( labelBackgroundColor );
ofRectangle bmpStringRect( position.x - 2,
position.y + ( ( drawAbove ) ? -4 : 12 ) + 2,
strLenPix + 4, -12 );
ofDrawRectangle( bmpStringRect );
ofSetColor( stringColor );
ofDrawBitmapString( label, position.x, position.y + ( ( drawAbove ) ? -4 : 12 ) );
ofSetColor( 255, 255, 255, 255 );
}
}
#endif /* Tools_h */
/**
* \copyright Copyright 2014-2016 PLUX - Wireless Biosignals, S.A.
* \author Filipe Silva
* \version 2.1
* \date February 2016
*
* \section LICENSE
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
#ifdef _WIN32 // 32-bit or 64-bit Windows
#define HASBLUETOOTH
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2bth.h>
#else // Linux or Mac OS
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#ifdef HASBLUETOOTH // Linux only
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <stdlib.h>
#endif // HASBLUETOOTH
void Sleep(int millisecs)
{
usleep(millisecs*1000);
}
#endif // Linux or Mac OS
#include "bitalino.h"
/*****************************************************************************/
// CRC4 check function
static const unsigned char CRC4tab[16] = {0, 3, 6, 5, 12, 15, 10, 9, 11, 8, 13, 14, 7, 4, 1, 2};
static bool checkCRC4(const unsigned char *data, int len)
{
unsigned char crc = 0;
for (int i = 0; i < len-1; i++)
{
const unsigned char b = data[i];
crc = CRC4tab[crc] ^ (b >> 4);
crc = CRC4tab[crc] ^ (b & 0x0F);
}
// CRC for last byte
crc = CRC4tab[crc] ^ (data[len-1] >> 4);
crc = CRC4tab[crc];
return (crc == (data[len-1] & 0x0F));
}
/*****************************************************************************/
// BITalino public methods
BITalino::VDevInfo BITalino::find(void)
{
VDevInfo devs;
DevInfo devInfo;
#ifdef _WIN32
char addrStr[40];
WSADATA m_data;
if (WSAStartup(0x202, &m_data) != 0) throw Exception(Exception::PORT_INITIALIZATION);
WSAQUERYSETA querySet;
ZeroMemory(&querySet, sizeof querySet);
querySet.dwSize = sizeof(querySet);
querySet.dwNameSpace = NS_BTH;
HANDLE hLookup;
DWORD flags = LUP_CONTAINERS | LUP_RETURN_ADDR | LUP_RETURN_NAME | LUP_FLUSHCACHE;
bool tryempty = true;
bool again;
do
{
again = false;
if (WSALookupServiceBeginA(&querySet, flags, &hLookup) != 0)
{
WSACleanup();
throw Exception(Exception::BT_ADAPTER_NOT_FOUND);
}
while (1)
{
BYTE buffer[1500];
DWORD bufferLength = sizeof(buffer);
WSAQUERYSETA *pResults = (WSAQUERYSETA*)&buffer;
if (WSALookupServiceNextA(hLookup, flags, &bufferLength, pResults) != 0) break;
if (pResults->lpszServiceInstanceName[0] == 0 && tryempty)
{ // empty name : may happen on the first inquiry after the device was connected
tryempty = false; // redo the inquiry a second time only (there may be a device with a real empty name)
again = true;
break;
}
DWORD strSiz = sizeof addrStr;
if (WSAAddressToStringA(pResults->lpcsaBuffer->RemoteAddr.lpSockaddr, pResults->lpcsaBuffer->RemoteAddr.iSockaddrLength,
NULL, addrStr, &strSiz) == 0)
{
addrStr[strlen(addrStr)-1] = 0; // remove trailing ')'
devInfo.macAddr = addrStr+1; // remove leading '('
devInfo.name = pResults->lpszServiceInstanceName;
devs.push_back(devInfo);
}
}
WSALookupServiceEnd(hLookup);
} while (again);
WSACleanup();
#else // Linux or Mac OS
#ifdef HASBLUETOOTH
#define MAX_DEVS 255
int dev_id = hci_get_route(NULL);
int sock = hci_open_dev(dev_id);
if (dev_id < 0 || sock < 0)
throw Exception(Exception::PORT_INITIALIZATION);
inquiry_info ii[MAX_DEVS];
inquiry_info *pii = ii;
int num_rsp = hci_inquiry(dev_id, 8, MAX_DEVS, NULL, &pii, IREQ_CACHE_FLUSH);
if(num_rsp < 0)
{
::close(sock);
throw Exception(Exception::PORT_INITIALIZATION);
}
for (int i = 0; i < num_rsp; i++)
{
char addr[19], name[248];
ba2str(&ii[i].bdaddr, addr);
if (hci_read_remote_name(sock, &ii[i].bdaddr, sizeof name, name, 0) >= 0)
{
devInfo.macAddr = addr;
devInfo.name = name;
devs.push_back(devInfo);
}
}
::close(sock);
if (pii != ii) free(pii);
#else
throw Exception(Exception::BT_ADAPTER_NOT_FOUND);
#endif // HASBLUETOOTH
#endif // Linux or Mac OS
return devs;
}
/*****************************************************************************/
BITalino::BITalino(const char *address) : nChannels(0), isBitalino2(false)
{
#ifdef _WIN32
if (_memicmp(address, "COM", 3) == 0)
{
fd = INVALID_SOCKET;
char xport[40] = "\\\\.\\"; // preppend "\\.\"
strcat_s(xport, 40, address);
hCom = CreateFileA(xport, // comm port name
GENERIC_READ | GENERIC_WRITE,
0, // comm devices must be opened w/exclusive-access
NULL, // no security attributes
OPEN_EXISTING, // comm devices must use OPEN_EXISTING
0, // not overlapped I/O
NULL); // hTemplate must be NULL for comm devices
if (hCom == INVALID_HANDLE_VALUE)
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
DCB dcb;
if (!GetCommState(hCom, &dcb))
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
dcb.BaudRate = CBR_115200;
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.fNull = FALSE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(hCom, &dcb))
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
COMMTIMEOUTS ct;
ct.ReadIntervalTimeout = 0;
ct.ReadTotalTimeoutConstant = 5000; // 5 s
ct.ReadTotalTimeoutMultiplier = 0;
ct.WriteTotalTimeoutConstant = 5000; // 5 s
ct.WriteTotalTimeoutMultiplier = 0;
if (!SetCommTimeouts(hCom, &ct))
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
}
else // address is a Bluetooth MAC address
{
hCom = INVALID_HANDLE_VALUE;
WSADATA m_data;
if (WSAStartup(0x202, &m_data) != 0)
throw Exception(Exception::PORT_INITIALIZATION);
SOCKADDR_BTH so_bt;
int siz = sizeof so_bt;
if (WSAStringToAddressA((LPSTR)address, AF_BTH, NULL, (sockaddr*)&so_bt, &siz) != 0)
{
WSACleanup();
throw Exception(Exception::INVALID_ADDRESS);
}
so_bt.port = 1;
fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if (fd == INVALID_SOCKET)
{
WSACleanup();
throw Exception(Exception::PORT_INITIALIZATION);
}
DWORD rcvbufsiz = 128*1024; // 128k
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char*) &rcvbufsiz, sizeof rcvbufsiz);
if (connect(fd, (const sockaddr*)&so_bt, sizeof so_bt) != 0)
{
int err = WSAGetLastError();
close();
switch(err)
{
case WSAENETDOWN:
throw Exception(Exception::BT_ADAPTER_NOT_FOUND);
case WSAETIMEDOUT:
throw Exception(Exception::DEVICE_NOT_FOUND);
default:
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
}
}
readtimeout.tv_sec = 5;
readtimeout.tv_usec = 0;
}
#else // Linux or Mac OS
if (memcmp(address, "/dev/", 5) == 0)
{
fd = open(address, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0)
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
if (fcntl(fd, F_SETFL, 0) == -1) // remove the O_NDELAY flag
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
termios term;
if (tcgetattr(fd, &term) != 0)
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
cfmakeraw(&term);
term.c_oflag &= ~(OPOST);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 1;
term.c_iflag &= ~(INPCK | PARMRK | ISTRIP | IGNCR | ICRNL | INLCR | IXON | IXOFF | IMAXBEL); // no flow control
term.c_iflag |= (IGNPAR | IGNBRK);
term.c_cflag &= ~(CRTSCTS | PARENB | CSTOPB | CSIZE); // no parity, 1 stop bit
term.c_cflag |= (CLOCAL | CREAD | CS8); // raw mode, 8 bits
term.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOPRT | ECHOK | ECHOKE | ECHONL | ECHOCTL | ISIG | IEXTEN | TOSTOP); // raw mode
if (cfsetspeed(&term, B115200) != 0)
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
if (tcsetattr(fd, TCSANOW, &term) != 0)
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
isTTY = true;
}
else // address is a Bluetooth MAC address
#ifdef HASBLUETOOTH
{
sockaddr_rc so_bt;
so_bt.rc_family = AF_BLUETOOTH;
if (str2ba(address, &so_bt.rc_bdaddr) < 0)
throw Exception(Exception::INVALID_ADDRESS);
so_bt.rc_channel = 1;
fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (fd < 0)
throw Exception(Exception::PORT_INITIALIZATION);
if (connect(fd, (const sockaddr*)&so_bt, sizeof so_bt) != 0)
{
close();
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
}
isTTY = false;
}
#else
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
#endif // HASBLUETOOTH
#endif // Linux or Mac OS
// check if device is BITalino2
const std::string ver = version();
const std::string::size_type pos = ver.find("_v");
if (pos != std::string::npos)
{
const char *xver = ver.c_str() + pos+2;
if (atoi(xver) >= 5) isBitalino2 = true;
}
}
/*****************************************************************************/
BITalino::~BITalino(void)
{
try
{
if (nChannels != 0) stop();
}
catch (Exception) {} // if stop() fails, close anyway
close();
}
/*****************************************************************************/
std::string BITalino::version(void)
{
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
const char *header = "BITalino";
const size_t headerLen = strlen(header);
send(0x07); // 0 0 0 0 0 1 1 1 - Send version string
std::string str;
while(1)
{
char chr;
if (recv(&chr, sizeof chr) != sizeof chr) // a timeout has occurred
throw Exception(Exception::CONTACTING_DEVICE);
const size_t len = str.size();
if (len >= headerLen)
{
if (chr == '\n') return str;
str.push_back(chr);
}
else
if (chr == header[len])
str.push_back(chr);
else
{
str.clear(); // discard all data before version header
if (chr == header[0]) str.push_back(chr);
}
}
}
/*****************************************************************************/
void BITalino::start(int samplingRate, const Vint &channels, bool simulated)
{
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
unsigned char cmd;
switch (samplingRate)
{
case 1:
cmd = 0x03;
break;
case 10:
cmd = 0x43;
break;
case 100:
cmd = 0x83;
break;
case 1000:
cmd = 0xC3;
break;
default:
throw Exception(Exception::INVALID_PARAMETER);
}
char chMask;
if (channels.empty())
{
chMask = 0x3F; // all 6 analog channels
nChannels = 6;
}
else
{
chMask = 0;
nChannels = 0;
for(Vint::const_iterator it = channels.begin(); it != channels.end(); it++)
{
int ch = *it;
if (ch < 0 || ch > 5) throw Exception(Exception::INVALID_PARAMETER);
const char mask = 1 << ch;
if (chMask & mask) throw Exception(Exception::INVALID_PARAMETER);
chMask |= mask;
nChannels++;
}
}
send(cmd); // <Fs> 0 0 0 0 1 1 - Set sampling rate
// A6 A5 A4 A3 A2 A1 0 1 - Start live mode with analog channel selection
// A6 A5 A4 A3 A2 A1 1 0 - Start simulated mode with analog channel selection
send((chMask << 2) | (simulated ? 0x02 : 0x01));
}
/*****************************************************************************/
void BITalino::stop(void)
{
if (nChannels == 0) throw Exception(Exception::DEVICE_NOT_IN_ACQUISITION);
send(0x00); // 0 0 0 0 0 0 0 0 - Go to idle mode
nChannels = 0;
version(); // to flush pending frames in input buffer
}
/*****************************************************************************/
int BITalino::read(VFrame &frames)
{
if (nChannels == 0) throw Exception(Exception::DEVICE_NOT_IN_ACQUISITION);
unsigned char buffer[8]; // frame maximum size is 8 bytes
if (frames.empty()) frames.resize(100);
char nBytes = nChannels + 2;
if (nChannels >= 3 && nChannels <= 5) nBytes++;
for(VFrame::iterator it = frames.begin(); it != frames.end(); it++)
{
if (recv(buffer, nBytes) != nBytes) return int(it - frames.begin()); // a timeout has occurred
while (!checkCRC4(buffer, nBytes))
{ // if CRC check failed, try to resynchronize with the next valid frame
// checking with one new byte at a time
memmove(buffer, buffer+1, nBytes-1);
if (recv(buffer+nBytes-1, 1) != 1) return int(it - frames.begin()); // a timeout has occurred
}
Frame &f = *it;
f.seq = buffer[nBytes-1] >> 4;
for(int i = 0; i < 4; i++)
f.digital[i] = ((buffer[nBytes-2] & (0x80 >> i)) != 0);
f.analog[0] = (short(buffer[nBytes-2] & 0x0F) << 6) | (buffer[nBytes-3] >> 2);
if (nChannels > 1)
f.analog[1] = (short(buffer[nBytes-3] & 0x03) << 8) | buffer[nBytes-4];
if (nChannels > 2)
f.analog[2] = (short(buffer[nBytes-5]) << 2) | (buffer[nBytes-6] >> 6);
if (nChannels > 3)
f.analog[3] = (short(buffer[nBytes-6] & 0x3F) << 4) | (buffer[nBytes-7] >> 4);
if (nChannels > 4)
f.analog[4] = ((buffer[nBytes-7] & 0x0F) << 2) | (buffer[nBytes-8] >> 6);
if (nChannels > 5)
f.analog[5] = buffer[nBytes-8] & 0x3F;
}
return (int) frames.size();
}
/*****************************************************************************/
void BITalino::battery(int value)
{
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
if (value < 0 || value > 63) throw Exception(Exception::INVALID_PARAMETER);
send(value << 2); // <bat threshold> 0 0 - Set battery threshold
}
/*****************************************************************************/
void BITalino::trigger(const Vbool &digitalOutput)
{
unsigned char cmd;
const size_t len = digitalOutput.size();
if (isBitalino2)
{
if (len != 0 && len != 2) throw Exception(Exception::INVALID_PARAMETER);
cmd = 0xB3; // 1 0 1 1 O2 O1 1 1 - Set digital outputs
}
else
{
if (len != 0 && len != 4) throw Exception(Exception::INVALID_PARAMETER);
if (nChannels == 0) throw Exception(Exception::DEVICE_NOT_IN_ACQUISITION);
cmd = 0x03; // 0 0 O4 O3 O2 O1 1 1 - Set digital outputs
}
for(size_t i = 0; i < len; i++)
if (digitalOutput[i])
cmd |= (0x04 << i);
send(cmd);
}
/*****************************************************************************/
void BITalino::pwm(int pwmOutput)
{
if (!isBitalino2) throw Exception(Exception::NOT_SUPPORTED);
if (pwmOutput < 0 || pwmOutput > 255) throw Exception(Exception::INVALID_PARAMETER);
send((char) 0xA3); // 1 0 1 0 0 0 1 1 - Set analog output (1 byte follows: 0..255)
send(pwmOutput);
}
/*****************************************************************************/
BITalino::State BITalino::state(void)
{
#pragma pack(1) // byte-aligned structure
struct StateX
{
unsigned short analog[6], battery;
unsigned char batThreshold, portsCRC;
} statex;
#pragma pack() // restore default alignment
if (!isBitalino2) throw Exception(Exception::NOT_SUPPORTED);
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
send(0x0B); // 0 0 0 0 1 0 1 1 - Send device status
if (recv(&statex, sizeof statex) != sizeof statex) // a timeout has occurred
throw Exception(Exception::CONTACTING_DEVICE);
if (!checkCRC4((unsigned char *) &statex, sizeof statex))
throw Exception(Exception::CONTACTING_DEVICE);
State state;
for(int i = 0; i < 6; i++)
state.analog[i] = statex.analog[i];
state.battery = statex.battery;
state.batThreshold = statex.batThreshold;
for(int i = 0; i < 4; i++)
state.digital[i] = ((statex.portsCRC & (0x80 >> i)) != 0);
return state;
}
/*****************************************************************************/
const char* BITalino::Exception::getDescription(void)
{
switch (code)
{
case INVALID_ADDRESS:
return "The specified address is invalid.";
case BT_ADAPTER_NOT_FOUND:
return "No Bluetooth adapter was found.";
case DEVICE_NOT_FOUND:
return "The device could not be found.";
case CONTACTING_DEVICE:
return "The computer lost communication with the device.";
case PORT_COULD_NOT_BE_OPENED:
return "The communication port does not exist or it is already being used.";
case PORT_INITIALIZATION:
return "The communication port could not be initialized.";
case DEVICE_NOT_IDLE:
return "The device is not idle.";
case DEVICE_NOT_IN_ACQUISITION:
return "The device is not in acquisition mode.";
case INVALID_PARAMETER:
return "Invalid parameter.";
case NOT_SUPPORTED:
return "Operation not supported by the device.";
default:
return "Unknown error.";
}
}
/*****************************************************************************/
// BITalino private methods
void BITalino::send(char cmd)
{
Sleep(150);
#ifdef _WIN32
if (fd == INVALID_SOCKET)
{
DWORD nbytwritten = 0;
if (!WriteFile(hCom, &cmd, sizeof cmd, &nbytwritten, NULL))
throw Exception(Exception::CONTACTING_DEVICE);
if (nbytwritten != sizeof cmd)
throw Exception(Exception::CONTACTING_DEVICE);
}
else
if (::send(fd, &cmd, sizeof cmd, 0) != sizeof cmd)
throw Exception(Exception::CONTACTING_DEVICE);
#else // Linux or Mac OS
if (write(fd, &cmd, sizeof cmd) != sizeof cmd)
throw Exception(Exception::CONTACTING_DEVICE);
#endif
}
/*****************************************************************************/
int BITalino::recv(void *data, int nbyttoread)
{
#ifdef _WIN32
if (fd == INVALID_SOCKET)
{
for(int n = 0; n < nbyttoread;)
{
DWORD nbytread = 0;
if (!ReadFile(hCom, (char *) data+n, nbyttoread-n, &nbytread, NULL))
throw Exception(Exception::CONTACTING_DEVICE);
if (nbytread == 0)
{
DWORD stat;
if (!GetCommModemStatus(hCom, &stat) || !(stat & MS_DSR_ON))
throw Exception(Exception::CONTACTING_DEVICE); // connection is lost
return n; // a timeout occurred
}
n += nbytread;
}
return nbyttoread;
}
#endif
#ifndef _WIN32 // Linux or Mac OS
timeval readtimeout;
readtimeout.tv_sec = 5;
readtimeout.tv_usec = 0;
#endif
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
for(int n = 0; n < nbyttoread;)
{
int state = select(FD_SETSIZE, &readfds, NULL, NULL, &readtimeout);
if(state < 0) throw Exception(Exception::CONTACTING_DEVICE);
if (state == 0) return n; // a timeout occurred
#ifdef _WIN32
int ret = ::recv(fd, (char *) data+n, nbyttoread-n, 0);
#else // Linux or Mac OS
ssize_t ret = ::read(fd, (char *) data+n, nbyttoread-n);
#endif
if(ret <= 0) throw Exception(Exception::CONTACTING_DEVICE);
n += ret;
}
return nbyttoread;
}
/*****************************************************************************/
void BITalino::close(void)
{
#ifdef _WIN32
if (fd == INVALID_SOCKET)
CloseHandle(hCom);
else
{
closesocket(fd);
WSACleanup();
}
#else // Linux or Mac OS
::close(fd);
#endif
}
/*****************************************************************************/