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 3263 additions and 0 deletions
//
// RapidVisualization.hpp
// RapidVisualizerOSC
//
// Created by James on 10/11/2017.
//
//
#ifndef RapidVisualization_h
#define RapidVisualization_h
#include "RapidVisualizer.hpp"
class RapidVisualization
{
public:
RapidVisualization ( void );
~RapidVisualization ( void );
void setup ( ofRectangle posAndSize, uint32_t defaultHistorySize=256 );
// Append multiple types of graphs to cycle through as new entry addresses are added
// Implement?
//RapidVisualizer* addGraphType ( RapidVisualizer::graphTypeRealtime graphType );
//RapidVisualizer* addGraphType ( RapidVisualizer::graphTypeWithHistory graphType, uint32_t historySize );
void addData ( std::string address, std::vector<double>& data );
void addData ( std::string graphName, std::string subGraphName, std::vector<double>& data );
void reset ( void );
void update ( void );
void draw ( void );
void setPos ( ofVec2f pos );
void setSize ( ofVec2f size );
void setPosAndSize ( ofRectangle posAndSize );
void setHistorySize ( uint32_t size );
void setGraphColor ( ofColor graphColor );
//void setGraphColor ( ofColor backgroundColor );
//void setGraphColor ( ofColor textColor );
void setGuiHidden ( bool guiHidden );
private:
bool guiHidden = false;
uint32_t numGraphs = 0;
std::map<std::string, RapidVisualizer*> visualizers;
ofRectangle posAndSize;
uint32_t defaultHistorySize;
std::pair<std::string, std::string> getRoute ( std::string& address );
void updateRep ( void );
};
#endif /* RapidVisualization_h */
//
// RapidVisualizer.cpp
// RapidVisualizerOSC
//
// Created by James on 09/11/2017.
//
//
#include "RapidVisualizer.hpp"
RapidVisualizer::RapidVisualizer ( void )
{
uint32 initialHistorySize = 256;
minimumGui.setup();
expandedGui.setBackgroundColor(ofColor(0,0,0,127));
expandedGui.setup();
historySizeSlider.addListener(this, &RapidVisualizer::historySizeSliderChanged);
auto gNameRef = expandButton.setup(graphState.label, false);
expandButton.setBackgroundColor(ofColor(0, 0, 0, 127));
minimumGui.add(gNameRef);
expandedGui.add(graphTypesLabel.setup("Graph Type",""));
expandedGui.add(viewTypes[0].setup("Bar Chart", false));
expandedGui.add(viewTypes[1].setup("Line Graph", false));
expandedGui.add(viewTypes[2].setup("Line Graph History", false));
expandedGui.add(viewTypes[3].setup("Scope", false));
expandedGui.add(prefLabel.setup("Preferences",""));
expandedGui.add(splitArrayIntoGraphs.setup("Split Multiple Input", false));
expandedGui.add(historySizeSlider.setup("History Size", initialHistorySize, 4, 4096));
setHistory(initialHistorySize);
setLayout (Graph::GRAPHS_STACKED);
setSplitEachArgument (false);
setRect (ofRectangle(0,0,200,200));
}
RapidVisualizer::~RapidVisualizer ( void )
{
if (currentGraph)
{
delete currentGraph;
}
}
void RapidVisualizer::setup ( std::string label, graphTypeT graphType, Graph::graphLayout layout, bool splitEachArgument, ofRectangle positionAndSize )
{
setLabel (label);
setLayout (layout);
setSplitEachArgument (splitEachArgument);
setRect (positionAndSize);
setGraphType (graphType); // FIXME: Check if successfully initialized
}
void RapidVisualizer::setup ( std::string label, graphTypeT graphType, uint32_t historySize, Graph::graphLayout layout, bool splitEachArgument, ofRectangle positionAndSize )
{
setLabel (label);
setLayout (layout);
setSplitEachArgument (splitEachArgument);
setRect (positionAndSize);
setGraphType (graphType, historySize); // FIXME: Check if successfully initialized
}
void RapidVisualizer::setLabel ( std::string label )
{
graphState.label = label;
expandButton.setName(label);
if (currentGraph)
currentGraph->updateRep();
}
void RapidVisualizer::setGraphType ( graphTypeT graphType )
{
if (currentGraphType != graphType)
{
if (currentGraph)
{
// TODO: add lock for when doing this, or have all types loaded?
delete currentGraph;
}
switch (graphType) {
case BAR_CHART:
currentGraph = new BarChart ( &graphState );
graphState.hasHistory = false;
break;
case LINE_GRAPH:
currentGraph = new LineGraph ( &graphState );
graphState.hasHistory = false;
break;
case LINE_GRAPH_WITH_HISTORY:
currentGraph = new LineGraphHistory ( &graphState );
graphState.hasHistory = true;
break;
case SCOPE:
currentGraph = new ScopeWithHistory ( &graphState );
graphState.hasHistory = true;
break;
default:
break;
}
currentGraphType = graphType;
}
}
void RapidVisualizer::setGraphType ( graphTypeT graphType, uint32_t historySize )
{
setHistory(historySize);
setGraphType(graphType);
}
void RapidVisualizer::setLayout ( Graph::graphLayout layout )
{
graphState.layout = layout;
if (currentGraph)
currentGraph->updateRep();
}
void RapidVisualizer::setSplitEachArgument ( bool splitEachArgument )
{
this->splitEachArgument = splitEachArgument;
splitArrayIntoGraphs = splitEachArgument;
}
void RapidVisualizer::setPos ( ofVec2f pos )
{
graphState.positionAndSize.x = pos.x;
graphState.positionAndSize.y = pos.y;
if (currentGraph)
currentGraph->updateRep();
}
void RapidVisualizer::setSize ( ofVec2f size )
{
graphState.positionAndSize.width = size.x;
graphState.positionAndSize.height = size.y;
if (currentGraph)
currentGraph->updateRep();
}
void RapidVisualizer::setRect ( ofRectangle positionAndSize )
{
graphState.positionAndSize = positionAndSize;
if (currentGraph)
currentGraph->updateRep();
}
void RapidVisualizer::setHistory( uint32_t historySize )
{
graphState.historySize = historySize;
historySizeSlider = historySize;
if (currentGraph)
currentGraph->updateRep();
}
void RapidVisualizer::historySizeSliderChanged (int32_t &val)
{
setHistory(val);
}
void RapidVisualizer::addData ( std::string subLabel, std::vector<double>& data )
{
if (currentGraph)
{
if (splitEachArgument && data.size() > 1)
{
int16_t currentIndex = 0;
std::vector<double> splitData;
for (double d : data)
{
splitData = {d};
currentGraph->addData (subLabel + ofToString(currentIndex), splitData);
++currentIndex;
}
}
else
currentGraph->addData (subLabel, data);
}
}
void RapidVisualizer::update ( void )
{
if (currentGraph)
{
currentGraph->update();
}
}
void RapidVisualizer::drawMenu ( ofRectangle mainPosAndSize )
{
minimumGui.setPosition(graphState.positionAndSize.x, graphState.positionAndSize.y);
minimumGui.draw();
if (menuActive)
{
double expandedGuiXPos = graphState.positionAndSize.x + minimumGui.getWidth();
double expandedGuiYPos = graphState.positionAndSize.y;
double mainYPlusHeight = mainPosAndSize.y + mainPosAndSize.height;
double check = expandedGuiYPos + expandedGui.getHeight() - mainYPlusHeight;
if (check > 0)
{
expandedGuiYPos -= check;
}
expandedGui.setPosition(expandedGuiXPos, expandedGuiYPos);
expandedGui.draw();
}
}
void RapidVisualizer::draw ( void )
{
if (currentGraph)
{
currentGraph->draw();
}
//drawMenu();
menuActive = expandButton;
if (menuActive)
{
bool noneActive = true;
for (uint8_t i = 0; i < NUMVIEWTYPES; ++i)
{
if (viewTypes[i] && !oldTypeToggles[i])
{
std::fill(viewTypes, viewTypes + NUMVIEWTYPES, false);
viewTypes[i] = true;
setGraphType(static_cast<graphTypeT>(i));
}
if (viewTypes[i])
noneActive = false;
oldTypeToggles[i] = viewTypes[i];
}
if (noneActive && currentGraphType != NOT_INITIALIZED)
{
viewTypes[currentGraphType] = true;
}
if (splitEachArgument != splitArrayIntoGraphs)
{
// Dirty, solution for now
if (currentGraph)
currentGraph->reset();
splitEachArgument = splitArrayIntoGraphs;
}
}
}
uint32_t RapidVisualizer::getNumGraphs ( void ) const
{
if (currentGraph)
{
return currentGraph->getNumSubGraphs();
} else {
return 0;
}
}
#undef NUMVIEWTYPES
//
// RapidVisualizer.hpp
// RapidVisualizerOSC
//
// Created by James on 09/11/2017.
//
//
#ifndef RapidVisualizer_hpp
#define RapidVisualizer_hpp
#include <stdio.h>
#include <map>
#include "ofMain.h"
#include "Graph.hpp"
#include "BarChart.hpp"
#include "LineGraph.hpp"
#include "LineGraphHistory.hpp"
#include "ScopeWithHistory.hpp"
#define NUMVIEWTYPES 4
// TODO add historySize slider and init with default 256 or so
class RapidVisualizer {
public:
enum graphTypeT {
BAR_CHART,
LINE_GRAPH,
LINE_GRAPH_WITH_HISTORY,
SCOPE,
NOT_INITIALIZED
};
RapidVisualizer ( void );
~RapidVisualizer ( void );
void setup ( std::string label, graphTypeT graphType, Graph::graphLayout layout, bool splitEachArgument, ofRectangle positionAndSize );
void setup ( std::string label, graphTypeT graphType, uint32_t historySize, Graph::graphLayout layout, bool splitEachArgument, ofRectangle positionAndSize );
void setLabel ( std::string label );
void setGraphType ( graphTypeT graphType );
void setGraphType ( graphTypeT graphType, uint32_t historySize );
void setLayout ( Graph::graphLayout layout );
void setSplitEachArgument ( bool splitEachArgument );
void setPos ( ofVec2f pos );
void setSize ( ofVec2f size );
void setRect ( ofRectangle rect );
void setHistory ( uint32_t historySize );
void historySizeSliderChanged (int32_t &val);
void addData ( std::string subLabel, std::vector<double>& data );
void update ( void );
void drawMenu ( ofRectangle mainPosAndSize );
void draw ( void );
uint32_t getNumGraphs ( void ) const;
private:
graphTypeT currentGraphType = NOT_INITIALIZED;
Graph::GraphState graphState;
Graph* currentGraph = nullptr;
bool splitEachArgument = false;
bool menuActive = false;
ofxLabel graphName;
ofxLabel graphTypesLabel;
ofxLabel prefLabel;
ofxPanel minimumGui;
ofxToggle expandButton;
ofxPanel expandedGui;
ofxIntSlider historySizeSlider;
ofxToggle splitArrayIntoGraphs;
bool oldTypeToggles[NUMVIEWTYPES];
ofxToggle viewTypes[NUMVIEWTYPES];
};
#endif /* RapidVisualizer_hpp */
//
// RealTimeGraph.hpp
// RapidVisualizerOSC
//
// Created by James on 09/11/2017.
//
//
#ifndef RealTimeGraph_h
#define RealTimeGraph_h
#include <map>
#include <vector>
#include <string>
#include "Graph.hpp"
class RealTimeGraph : public Graph
{
public:
RealTimeGraph ( GraphState* state )
: Graph(state)
{
}
~RealTimeGraph ( void )
{
}
void addData ( std::string subLabel, std::vector<double>& data )
{
if ( subLabelData.find(subLabel) == subLabelData.end() ) {
// not found
subLabelData[subLabel] = DataContainer<std::vector<double>>();
}
subLabelData[subLabel].labelData = data;
subLabelData[subLabel].updateMinMax();
}
virtual void reset ( void )
{
subLabelData.clear();
}
virtual void updateRep ( void ) = 0; // update representation
virtual void drawSubGraph ( std::string subLabel, DataContainer<std::vector<double>>& data, ofRectangle subLabelrect ) = 0;
virtual void update ( void ) = 0;
void draw ( void )
{
uint32_t numElements = subLabelData.size();
uint16_t heightBetweenSubLabels = state->positionAndSize.height/numElements;
uint16_t subLabelY = 0;
ofSetColor (255,255,255);
ofDrawLine(state->positionAndSize.x, state->positionAndSize.y,
state->positionAndSize.x + state->positionAndSize.width, state->positionAndSize.y);
std::map<std::string, DataContainer<std::vector<double>>>::iterator it;
for(it = subLabelData.begin(); it != subLabelData.end(); ++it)
{
std::string subLabel = it->first;
DataContainer<std::vector<double>>& data = it->second;
drawSubGraph (subLabel, data, ofRectangle(state->positionAndSize.x,
state->positionAndSize.y + subLabelY,
state->positionAndSize.width,
heightBetweenSubLabels));
// Draw label and background
drawTextLabel(subLabel, ofVec2f(state->positionAndSize.x + state->positionAndSize.width,
state->positionAndSize.y + subLabelY),
ofColor(100, 100, 100), ofColor(textColor), TextAlignment::RIGHT, false);
// Draw max value
drawTextLabel(ofToString(data.maxValue),
ofVec2f(state->positionAndSize.x + state->positionAndSize.width/2,
state->positionAndSize.y + subLabelY),
ofColor(50, 50, 50), ofColor(255, 255, 255), TextAlignment::CENTER, false);
// Increment Y position
subLabelY += heightBetweenSubLabels;
// Draw min value
// Could show actually found min value
drawTextLabel(ofToString((data.minValue < 0) ? -data.maxValue : 0),
ofVec2f(state->positionAndSize.x + state->positionAndSize.width/2,
state->positionAndSize.y + subLabelY),
ofColor(50, 50, 50), ofColor(255, 255, 255), TextAlignment::CENTER, true);
// Draw Line at bottom of graph
ofSetLineWidth(2.0);
ofSetColor (180,180,180);
ofDrawLine(state->positionAndSize.x, state->positionAndSize.y + subLabelY,
state->positionAndSize.x + state->positionAndSize.width, state->positionAndSize.y + subLabelY);
}
}
uint32_t getNumSubGraphs ( void ) const
{
return subLabelData.size();
}
protected:
std::map <std::string, DataContainer<std::vector<double>>> subLabelData;
};
#endif /* RealTimeGraph_h */
//
// ScopeWithHistory.hpp
// RapidVisualizerOSC
//
// Created by James on 12/11/2017.
//
//
#ifndef ScopeWithHistory_h
#define ScopeWithHistory_h
#include <stdio.h>
#include "Graph.hpp"
class ScopeWithHistory : public Graph
{
public:
ScopeWithHistory ( GraphState* state ) : Graph (state)
{
//
}
~ScopeWithHistory ( void )
{
//
}
void addData ( std::string subLabel, std::vector<double>& data )
{
if (data.size() < state->historySize)
{
//FIXME: can be sped up by using the result of this search instead of accessing by[] again
if ( subLabelData.find(subLabel) == subLabelData.end() ) {
// not found
DataContainer<std::vector<double>> container;
container.labelData.resize(state->historySize);
std::fill(container.labelData.begin(), container.labelData.end(), 0.0);
subLabelData[subLabel] = container;
}
DataContainer<std::vector<double>>& dataRef = subLabelData[subLabel];
std::vector<double>& referencedList = dataRef.labelData;
while (referencedList.size() >= state->historySize)
{
referencedList.pop_back();
}
for (int32_t i = data.size()-1; i >= 0; --i)
{
double val = data[i];
if (val < dataRef.minValue)
dataRef.minValue = val;
if (val > dataRef.maxValue)
dataRef.maxValue = val;
if (fabs(val) > dataRef.maxValue)
dataRef.maxValue = fabs(val);
if (referencedList.size() + data.size() >= state->historySize)
{
referencedList[dataRef.iteratorPos] = val;
dataRef.iteratorPos = (dataRef.iteratorPos + data.size()) % state->historySize;
} else {
referencedList.insert(referencedList.begin(), val);
}
}
}
}
void updateRep ( void )
{
//
}
void drawSubGraph ( std::string subLabel, DataContainer<std::vector<double>>& data, ofRectangle subLabelRect )
{
double
minIn = 0,
minOut = 0,
maxOut = -subLabelRect.height,
startPosY = subLabelRect.height,
pointDistance = subLabelRect.width/data.labelData.size(),
separation = pointDistance/2;
//halfSeparation = separation/2;
bool drawZeroSep = false;
if (data.minValue < 0)
{ // Add negative part
startPosY = subLabelRect.height/2;
minIn = -data.maxValue;
minOut = subLabelRect.height/2;
maxOut /= 2;
drawZeroSep = true;
}
ofBeginShape();
ofFill();
ofVertex(subLabelRect.x, subLabelRect.y + startPosY);
double output = mapVals(data.labelData.front(), minIn, data.maxValue, minOut, maxOut );
ofVertex(subLabelRect.x, subLabelRect.y + startPosY + output);
uint32_t i = 0;
for (double d : data.labelData)
{
output = mapVals(d, minIn, data.maxValue, minOut, maxOut );
ofSetColor (graphColor);
ofVertex(subLabelRect.x + pointDistance * i + separation, subLabelRect.y + startPosY + output);
//ofDrawRectangle(subLabelRect.x + barSize * i + halfSeparation, subLabelRect.y + startPosY, barSize - separation, output );
++i;
}
output = mapVals(data.labelData.back(), minIn, data.maxValue, minOut, maxOut );
ofVertex(subLabelRect.x + subLabelRect.width, subLabelRect.y + startPosY + output);
ofVertex(subLabelRect.x + subLabelRect.width, subLabelRect.y + startPosY);
ofEndShape();
if (drawZeroSep)
{
ofSetLineWidth(1.25);
ofSetColor (175,150,150);
ofDrawLine(subLabelRect.x, subLabelRect.y + startPosY,
subLabelRect.x + subLabelRect.width, subLabelRect.y + startPosY);
}
}
void update ( void )
{
//
}
void reset ( void )
{
subLabelData.clear();
}
void draw ( void )
{
uint32_t numElements = subLabelData.size();
uint16_t heightBetweenSubLabels = state->positionAndSize.height/numElements;
uint16_t subLabelY = 0;
ofSetColor (255,255,255);
ofDrawLine(state->positionAndSize.x, state->positionAndSize.y,
state->positionAndSize.x + state->positionAndSize.width, state->positionAndSize.y);
std::map<std::string, DataContainer<std::vector<double>>>::iterator it;
for(it = subLabelData.begin(); it != subLabelData.end(); ++it)
{
std::string subLabel = it->first;
DataContainer<std::vector<double>>& data = it->second;
drawSubGraph (subLabel, data, ofRectangle(state->positionAndSize.x,
state->positionAndSize.y + subLabelY,
state->positionAndSize.width,
heightBetweenSubLabels));
// Draw label and background
drawTextLabel(subLabel, ofVec2f(state->positionAndSize.x + state->positionAndSize.width,
state->positionAndSize.y + subLabelY),
ofColor(100, 100, 100), ofColor(textColor), TextAlignment::RIGHT, false);
// Draw max value
drawTextLabel(ofToString(data.maxValue),
ofVec2f(state->positionAndSize.x + state->positionAndSize.width/2,
state->positionAndSize.y + subLabelY),
ofColor(50, 50, 50), ofColor(255, 255, 255), TextAlignment::CENTER, false);
// Increment Y position
subLabelY += heightBetweenSubLabels;
// Draw min value
// Show actual min value?
drawTextLabel(ofToString((data.minValue < 0) ? -data.maxValue : 0),
ofVec2f(state->positionAndSize.x + state->positionAndSize.width/2,
state->positionAndSize.y + subLabelY),
ofColor(50, 50, 50), ofColor(255, 255, 255), TextAlignment::CENTER, true);
// Draw Line at bottom of graph
ofSetLineWidth(2.0);
ofSetColor (180,180,180);
ofDrawLine(state->positionAndSize.x, state->positionAndSize.y + subLabelY,
state->positionAndSize.x + state->positionAndSize.width, state->positionAndSize.y + subLabelY);
}
}
uint32_t getNumSubGraphs ( void ) const
{
return subLabelData.size();
}
protected:
std::map <std::string, DataContainer<std::vector<double>>> subLabelData;
};
#endif /* ScopeWithHistory_h */
//
// BITEnvironment.cpp
// Bitalino
//
// Created by James on 23/11/2017.
//
//
#include "BITEnvironment.hpp"
namespace BIT
{
void Environment::setup( std::string nameOfBitalino, ofRectangle posAndSize )
{
this->posAndSize = posAndSize;
float graphHeight = posAndSize.height / ( NUM_SYNTH_PARAMS + 1 );
bst.setup( nameOfBitalino );
ofRectangle bitGraphRect( posAndSize.x, posAndSize.y, posAndSize.width, graphHeight );
bitalinoGraph.setup( "Bitalino", ofColor( 100, 100, 255 ),
ofColor( 100, 255, 100 ), ofColor( 100, 100, 100 ), bitGraphRect, true, true, 2000 );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
ofRectangle graphRect( posAndSize.x, posAndSize.y + ( i + 1 ) * graphHeight, posAndSize.width, graphHeight );
graphs[ i ].setup( ParamNames[ i ], ofColor( 255, 255, 255 ),
ofColor( 255, 100, 100 ), ofColor( 100, 100, 100 ), graphRect, true, false, 2000 );
// Link buffers together
bst.loopDataBuffers[ i ].linkTo( &graphs[ i ].memory );
graphs[ i ].memory.linkTo( &bst.loopDataBuffers[ i ] );
}
// Only needs one way link
bst.bitalinoChannel.linkTo( &bitalinoGraph.memory );
}
void Environment::update ( void )
{ // This runs concurrent with ofx
// THIS can dissapear
RingBufferAny::VariableHeader headerInfo;
while ( bst.toEnvironment.anyAvailableForPop( headerInfo ) )
{
if ( headerInfo.type_index == typeid( RecordingSelection ) ) {
RecordingSelection sel;
bst.toEnvironment.pop( &sel, 1 );
if ( sel.position < 0 )
selectedTrainingParts.clear( );
else
selectedTrainingParts.push_back( sel.position );
} else if ( headerInfo.type_index == typeid( State ) ) {
selectedTrainingParts.clear( );
bst.toEnvironment.pop( &currentState, 1 );
switch ( currentState )
{
case PERFORMING:
bitalinoGraph.graphColor = ofColor( 100, 255, 100 );
bitalinoGraph.redraw( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].graphColor = ofColor( 100, 100, 255 );
graphs[ i ].backgroundColor = ofColor( 255, 255, 255 );
graphs[ i ].redraw( );
}
break;
case RECORDING:
bitalinoGraph.graphColor = ofColor( 100, 255, 100 );
bitalinoGraph.redraw( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].graphColor = ofColor( 255, 255, 255 );
graphs[ i ].backgroundColor = ofColor( 180, 100, 100 );
graphs[ i ].redraw( );
}
break;
case PLAYING:
bitalinoGraph.graphColor = ofColor( 100, 255, 100 );
bitalinoGraph.redraw( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].graphColor = ofColor( 255, 100, 100 );
graphs[ i ].backgroundColor = ofColor( 100, 100, 100 );
graphs[ i ].redraw( );
}
break;
case TRAINING:
// Fallthrough style
case IDLE:
bitalinoGraph.graphColor = ofColor( 100, 100, 100 );
bitalinoGraph.redraw( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].graphColor = ofColor( 100, 100, 100 );
graphs[ i ].backgroundColor = ofColor( 100, 100, 100 );
graphs[ i ].redraw( );
}
break;
default:
break;
}
} else if ( headerInfo.type_index == typeid( BITSequencerThread::RealtimeViewData ) ) {
BITSequencerThread::RealtimeViewData data[ headerInfo.valuesPassed ];
bst.toEnvironment.pop( data, headerInfo.valuesPassed );
for ( size_t i = 0; i < headerInfo.valuesPassed; ++i )
{
transport = data[ i ].transportPosition;
}
}
}
bitalinoGraph.update( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].update( );
}
}
bool Environment::isMouseInside ( ofVec2f mousePos)
{
return posAndSize.inside( mousePos );
}
void Environment::mouseActive ( ofVec2f mousePos, int8_t mouseState, bool mouseStateChanged )
{
if ( currentState == PLAYING && selectedTrainingParts.empty( ) ) // Only then are buffers to be altered
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
PokeGraph& currentGraph = graphs[ i ];
if ( currentGraph.isMouseInside( mousePos ) )
{
if ( previousActive && previousActive != &currentGraph )
{
previousActive->mouseActive( mousePos, -1, true );
}
currentGraph.mouseActive( mousePos, mouseState, mouseStateChanged );
previousActive = &currentGraph;
}
}
}
void Environment::drawSelectedTrainingParts( void )
{
ofColor highlight( 255, 255, 255, 50 );
ofColor pillars( 255, 255, 255, 150 );
size_t size = selectedTrainingParts.size( );
int8_t complete = 0;
double previousPoint = 0;
for ( uint32_t i = 0; i < size; ++i )
{ // TODO fix wrap cases
double point = selectedTrainingParts[ i ];
complete = i % 2;
if ( i + 1 == size && complete == 0)
{
double transp = ( transport - point );
if ( transp < 0 )
{
selectedTrainingParts.insert( selectedTrainingParts.begin( ) + ( i + 1 ), 1.0 );
selectedTrainingParts.insert( selectedTrainingParts.begin( ) + ( i + 2 ), 0.0 );
i -= 1;
size += 2;
continue;
}
ofRectangle rect( posAndSize.x + point * posAndSize.width, posAndSize.y,
transp * posAndSize.width, posAndSize.height );
ofSetColor( highlight );
ofFill( );
ofDrawRectangle( rect );
ofSetColor( pillars );
ofSetLineWidth( 1.5f );
ofNoFill( );
ofDrawRectangle( rect );
ofSetColor( 255, 255, 255, 255 );
return;
}
if ( complete == 1 )
{
ofRectangle rect( posAndSize.x + previousPoint * posAndSize.width, posAndSize.y,
( point - previousPoint ) * posAndSize.width, posAndSize.height );
ofSetColor( highlight );
ofFill( );
ofDrawRectangle( rect );
ofSetColor( pillars );
ofSetLineWidth( 1.5f );
ofNoFill( );
ofDrawRectangle( rect );
}
previousPoint = point;
}
ofSetColor( 255, 255, 255, 255 );
}
void Environment::draw ( void )
{ // This runs concurrent with ofx
double xPosTransport = posAndSize.x + posAndSize.width * transport;
ofDrawLine( xPosTransport, posAndSize.y, xPosTransport, posAndSize.y + posAndSize.height );
bitalinoGraph.draw( );
if ( !selectedTrainingParts.empty( ) )
drawSelectedTrainingParts( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].draw( );
}
bool connected = bst.rapidBitalino.bitalinoThread.isConnected( );
drawTextLabel( stateStrings[ static_cast< uint8_t >( currentState ) ], ofVec2f( posAndSize.x + 4, posAndSize.y + 21 ),
(( connected ) ? ofColor( 24, 219, 92 ) : ofColor( 255, 24, 24 ) ), ofColor( 10, 10, 10 ),
ofxBitmapText::TextAlignment::LEFT, false );
}
void Environment::resize ( ofRectangle posAndSize )
{
this->posAndSize = posAndSize;
float graphHeight = posAndSize.height / ( NUM_SYNTH_PARAMS + 1 );
bitalinoGraph.setRect( ofRectangle( posAndSize.x, posAndSize.y, posAndSize.width, graphHeight ) );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
graphs[ i ].setRect( ofRectangle( posAndSize.x, posAndSize.y + ( i + 1 ) * graphHeight, posAndSize.width, graphHeight ) );
}
}
void Environment::stop( void )
{
bst.stop();
}
void Environment::audioOut( float *output, int bufferSize, int nChannels )
{ // Audio thread sync callback
bst.audioOut(output, bufferSize, nChannels);
}
} // End namespace BIT::
//
// BITEnvironment.hpp
// Bitalino
//
// Created by James on 23/11/2017.
//
//
#ifndef BITEnvironment_hpp
#define BITEnvironment_hpp
#include <stdio.h>
#include <vector>
#include <atomic>
#include "ofMain.h"
#include "BITSequencerThread.hpp"
#include "PokeGraph.hpp"
namespace BIT
{
// ML Bitalino Synth environment
// Functions as the view + a portion of the controller ( PokeGraphs )
class Environment
{
public:
BITSequencerThread bst;
void setup ( std::string nameOfBitalino, ofRectangle posAndSize );
void update ( void );
bool isMouseInside ( ofVec2f mousePos );
void mouseActive ( ofVec2f mousePos, int8_t mouseState, bool mouseStateChanged );
void draw ( void );
void drawSelectedTrainingParts ( void );
void resize ( ofRectangle posAndSize );
void stop ( void );
void audioOut(float * output, int bufferSize, int nChannels);
private:
std::vector< double > selectedTrainingParts;
ofRectangle posAndSize;
PokeGraph* previousActive = nullptr;
PokeGraph bitalinoGraph; // To show the input in sync with output parameters
PokeGraph graphs[ NUM_SYNTH_PARAMS ]; // To show and allow editing of parameters
double transport;
State currentState;
};
} // End namespace BIT::
#endif /* BITSynth_hpp */
//
// BITSequencerThread.cpp
// Bitalino
//
// Created by James on 08/12/2017.
//
//
#include "BITSequencerThread.hpp"
namespace BIT
{
BITSequencerThread::BITSequencerThread ( void ) : ThreadedProcess ()
{
setState(IDLE);
recording.store(false);
selectingData.store(false);
transport.store(0.0);
}
BITSequencerThread::~BITSequencerThread ( void )
{
}
void BITSequencerThread::setup( std::string nameOfBitalino )
{
// Set callback for when model is done training (called in rapidMixThread.update method)
rapidMixThread.doneTrainingCallback = [&] ( void )
{
//printf("New Model Trained\n");
sendRunDataToOFX();
trainingDone = true;
transport.store(0.0);
setState( PERFORMING );
//trainingText = "New Model Trained";
};
// Set buttonPressedCallback for rapidBitalino
rapidBitalino.buttonPressedCallback = std::bind(&BITSequencerThread::buttonPressed, this);
// Set buttonReasedCallback for rapidBitalino
rapidBitalino.buttonReleasedCallback = std::bind(&BITSequencerThread::buttonReleased, this);
// Set up training thread
rapidMixThread.setup();
// Set up BITalino accessor + thread
rapidBitalino.setup( nameOfBitalino, 10 ); // downsampling 10 of the data (TODO experiment with different settings with bayes)
toEnvironment.setup( 8192 ); // Leave room for 16kb of memory to be used in the ring-queue
bitalinoChannel.setup( 2000 );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
loopDataBuffers[ i ].setup( 2000 );
}
State s = currentState.load( );
toEnvironment.push( &s, 1 ); // Send initial state
// Start the thread
startThread( 10000 ); // Sleep 10 ms
}
void BITSequencerThread::setState ( State state )
{
toEnvironment.push ( &state, 1 );
this->currentState.store(state);
}
State BITSequencerThread::getState ( void ) const
{
return this->currentState.load();
}
const char* BITSequencerThread::getStateString ( void )
{
return stateStrings[static_cast<uint8_t>(getState())];
}
void BITSequencerThread::stop( void )
{
// Stop all asynchronous threads
this->stopThread();
rapidMixThread.stopThread();
rapidBitalino.stop();
}
void BITSequencerThread::audioOut( float *output, int bufferSize, int nChannels )
{ // Audio thread sync callback
synthesizer.audioOut(output, bufferSize, nChannels);
}
double BITSequencerThread::getTransport ( void )
{
return transport.load( );
}
void BITSequencerThread::buttonPressed ( void )
{ // Bitalino button press
longSelectionTimer.reset( );
switch ( currentState.load( ) )
{ // Run update for state
case IDLE:
//printf( "Started \n" );
generateFirstData( );
setState( PLAYING );
break;
case PERFORMING:
{
if ( trainingDone )
{
if ( recording.load( ) )
{
//printf( "Playing recorded gestures \n" );
recording.store( false );
setState( PLAYING );
} else {
//printf( "Started recording \n" );
recording.store( true );
transport.store( 0.0 );
// TODO: create extra state for just recording?
State recState = RECORDING; // Only used by environment for color change
toEnvironment.push( &recState, 1 );
}
}
break;
}
case PLAYING:
{
if ( !selectingData.load( ) )
{
RecordingSelection sel{ static_cast< double >( loopPosition ) / loopSize };
toEnvironment.push( &sel, 1 );
//printf( "Selecting data for example \n" );
trainingData.createNewPhrase( "NewPhrase" );
//printf( "Added training element, now size of %lu\n", trainingData.trainingSet.size( ) );
selectingData.store( true );
}
break;
}
default:
break;
}
}
void BITSequencerThread::buttonReleased( void )
{ // Bitalino button release
State s = currentState.load();
switch ( s )
{ // Run update for state
case PLAYING:
{
if (selectingData.load())
{
if (longSelectionTimer.getTimeElapsed() > longPress)
{
RecordingSelection sel{ static_cast< double >( loopPosition ) / loopSize };
toEnvironment.push( &sel, 1 );
//printf("Recorded example \n");
selectingData.store(false);
} else {
//printf("Throwing away last training sample, press was for stopping \n");
trainingData.trainingSet.pop_back();
RecordingSelection sel{ -1.0 };
toEnvironment.push( &sel, 1 );
bitalinoChannel.setRangeToConstant( 0, loopSize, 0.0 );
// Draw line here for next part ?
if (trainingData.trainingSet.size() >= 1)
{
//printf("Training on selected data \n");
RecordingSelection sel{ -1.0 };
toEnvironment.push( &sel, 1 );
trainingDone = false;
rapidMixThread.train(trainingData);
setState( TRAINING );
} else {
//printf("Reinitializing");
generateFirstData();
toEnvironment.push(&s, 1); // Make sure everything gets reinitialized
}
selectingData.store(false);
}
}
break;
}
default:
break;
}
}
void BITSequencerThread::mainThreadCallback( void )
{ // Process thread sync callback
// Get input data (BITalino):
RealtimeViewData viewData;
std::vector< double > inputData = rapidBitalino.update( ); // Update method uses ringbuffer and is threadsafe
State curState = currentState.load( );
bool newState = ( curState != previousState );
if ( newState )
{
dataPlayTimer.reset( );
longSelectionTimer.reset( );
switch ( curState )
{ // Run inits for states
case PERFORMING:
loop.clear( );
loopSize = 0;
break;
case PLAYING:
loopPosition = 0;
trainingData.trainingSet.clear( );
break;
default:
break;
}
} else
switch ( curState )
{ // Run update for state
case PERFORMING:
{ // Update case for performing state
if (dataPlayTimer.getTimeElapsed( ) > controlRate && trainingDone)
{
if ( !recording.load( ) )
transport.store( inputData[0] / maxValueEMG );
SynthControlData ctrlData( rapidMixThread.model->run( inputData ) );
synthesizer.controlDataBuffer.push( &ctrlData, 1 );
currentControlData = ctrlData;
if ( recording.load( ) )
{
bitalinoChannel.resizeBounded( loopSize + 1 );
bitalinoChannel[ loopSize ] = inputData[ 0 ];
for ( uint32_t p = 0; p < NUM_SYNTH_PARAMS; ++p )
{
loopDataBuffers[ p ].resizeBounded( loopSize + 1 );
loopDataBuffers[ p ][ loopSize ] = ctrlData.values[ p ];
}
++loopSize;
}
dataPlayTimer.reset( );
}
break;
}
case PLAYING:
{ // Update case for playing state
if (dataPlayTimer.getTimeElapsed() > controlRate)
{
bitalinoChannel[ loopPosition ] = inputData[ 0 ];
SynthControlData ctrlData;
for ( uint32_t p = 0; p < NUM_SYNTH_PARAMS; ++p )
{
ctrlData.values[ p ] = loopDataBuffers[ p ][ loopPosition ];
}
synthesizer.controlDataBuffer.push(&ctrlData, 1);
currentControlData = ctrlData;
if (++loopPosition >= loopSize)
loopPosition = 0;
transport.store( static_cast< double >( loopPosition ) / loopSize );
if (selectingData.load())
{ // Add element to trainingData
std::vector<double> outputTraining = ctrlData.getValuesAsVector( );
trainingData.addElement(inputData, outputTraining);
}
dataPlayTimer.reset();
}
break;
}
default:
break;
}
if ( currentState.load( ) != IDLE )
{
viewData.transportPosition = transport.load( );
toEnvironment.push( &viewData, 1 );
}
bitalinoChannel.update( );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
loopDataBuffers[ i ].update( );
}
rapidMixThread.update();
previousState = curState;
}
void BITSequencerThread::generateFirstData ( void )
{
uint8_t midiStart = 55;
uint32_t doremiSize = 2;
// Calculate new size of buffers
uint32_t resizeSize = doremiSize * majorScaleSize * samplesPerExample;
// Resize all buffers
bitalinoChannel.resizeBounded( resizeSize );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
loopDataBuffers[ i ].resizeBounded( resizeSize );
}
// Calculate doremi ladder and also random data blocks for other parameters
int index = 0;
for (uint32_t i = 0; i < doremiSize; ++i)
{
for (uint32_t step = 0; step < majorScaleSize; ++step)
{
double frequency = MTOF( midiStart + ( i * 12 ) + majorScale[ step ] );
SynthControlData ctrlData;
ctrlData.values[ CARRIER ] = frequency;
for ( uint16_t x = 0; x < NUM_SYNTH_PARAMS; ++x )
{
if ( x != static_cast< uint8_t >( CARRIER ) )
ctrlData.values[ x ] = ( ( double ) rand( ) / RAND_MAX );
}
for ( uint32_t p = 0; p < NUM_SYNTH_PARAMS; ++p )
{
loopDataBuffers[ p ].setRangeToConstant( index, index + samplesPerExample, ctrlData.values[ p ] );
}
index += samplesPerExample;
}
}
bitalinoChannel.setRangeToConstant( 0, resizeSize, 0 );
loopPosition = 0;
loopSize = resizeSize;
}
void BITSequencerThread::generateFirstRandomData ( void )
{ // NO LONGER USED
for (uint32_t i = 0; i < 10; ++i)
{
SynthControlData ctrlData;
for (uint16_t x = 0; x < NUM_SYNTH_PARAMS; ++x)
{
ctrlData.values[x] = ((double)rand()/RAND_MAX);
}
for (uint32_t y = 0; y < samplesPerExample; ++y)
loop.push_back(ctrlData);
}
}
void BITSequencerThread::sendRunDataToOFX ( void )
{ // Sweeps through the trained model and shows the input vs the outputs
uint32_t numValues = 256;
bitalinoChannel.resizeBounded( numValues );
for ( uint32_t i = 0; i < NUM_SYNTH_PARAMS; ++i )
{
loopDataBuffers[ i ].resizeBounded( numValues );
}
for ( int i = 0.0; i < numValues; i += 1 )
{
double inp = static_cast< double >( i * 2 );
std::vector< double > input( NUM_INPUTS_FOR_TRAINING, inp );
bitalinoChannel[ i ] = inp;
std::vector< double > outputs = rapidMixThread.model->run( input );
for ( uint32_t p = 0; p < NUM_SYNTH_PARAMS; ++p )
{
loopDataBuffers[ p ][ i ] = outputs[ p ];
}
}
}
}
//
// BITSequencerThread.hpp
// Bitalino
//
// Created by James on 08/12/2017.
//
//
#ifndef BITSequencerThread_hpp
#define BITSequencerThread_hpp
#include <stdio.h>
#include <functional>
#include "Timer.h"
#include "SigTools.hpp"
#include "RapidBitalino.h"
#include "RapidMixThread.h"
#include "BITSynth.hpp"
#include "ThreadedProcess.h"
#include "RingBufferAny.hpp"
#include "LinkedBuffer.hpp"
namespace BIT
{
class BITSequencerThread : public ThreadedProcess
{
// Functions as the model
public:
LinkedBuffer< double > bitalinoChannel;
LinkedBuffer< double > loopDataBuffers[ NUM_SYNTH_PARAMS ];
// Todo move back to protected/private (public for testing w/o bitalino)
void buttonPressed ( void );
void buttonReleased ( void );
// BITalino thread, TODO: threadsafe accessors etc
RapidBitalino rapidBitalino;
// Synth which can only be changed by ringbuffer (threadsafe)
Synth synthesizer;
// Threadsafe queue of events
RingBufferAny toEnvironment;
BITSequencerThread ( void );
~BITSequencerThread ( void );
void setup ( std::string nameOfBitalino );
void setState ( State state ); // Atomic set
State getState ( void ) const; // Atomic get
const char* getStateString ( void );
void stop ( void );
void audioOut(float * output, int bufferSize, int nChannels);
double getTransport ( void );
struct RealtimeViewData {
double transportPosition = 0.0;
};
protected:
//void buttonPressed ( void );
//void buttonReleased ( void );
void mainThreadCallback ( void );
void generateFirstData ( void );
void generateFirstRandomData ( void );
void sendRunDataToOFX ( void );
private:
// Rapidmix stuff
RapidMixThread rapidMixThread;
rapidmix::trainingData trainingData;
bool gatheringTrainingData = false;
bool trainingDone = false;
// Other stuff
uint32_t sampleRate = 44100;
uint32_t controlRate = 15; // 15ms
uint32_t samplesPerExample = 50;
// DEBUGDEBUG
//--
// State
std::atomic< State > currentState;
State previousState; // (synced with Data thread)
std::atomic< bool > recording;
std::atomic< bool > selectingData;
// Data thread synced
BIT::SynthControlData currentControlData;
std::vector< BIT::SynthControlData > loop;
int loopSize;
int loopPosition;
// Timers
Timer dataPlayTimer;
Timer longSelectionTimer;
uint32_t longPress = 400; // 400ms
double maxValueEMG = 512.0;
std::atomic< double > transport;
};
}
#endif /* BITSequencerThread_hpp */
//
// 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