changeset 0:02467299402e

First import CollidoscopeApp for Raspberry Pi JackDevice Teensy code for Collidoscope
author Fiore Martin <f.martin@qmul.ac.uk>
date Thu, 30 Jun 2016 14:50:06 +0200
parents
children b5bcad8e7803
files CollidoscopeApp/include/AudioEngine.h CollidoscopeApp/include/BufferToWaveRecorderNode.h CollidoscopeApp/include/Chunk.h CollidoscopeApp/include/Config.h CollidoscopeApp/include/DrawInfo.h CollidoscopeApp/include/EnvASR.h CollidoscopeApp/include/Log.h CollidoscopeApp/include/MIDI.h CollidoscopeApp/include/Messages.h CollidoscopeApp/include/Oscilloscope.h CollidoscopeApp/include/PGranular.h CollidoscopeApp/include/PGranularNode.h CollidoscopeApp/include/ParticleController.h CollidoscopeApp/include/Resources.h CollidoscopeApp/include/RingBufferPack.h CollidoscopeApp/include/RtMidi.h CollidoscopeApp/include/Wave.h CollidoscopeApp/linux/CMakeLists.txt CollidoscopeApp/linux/cibuild CollidoscopeApp/src/AudioEngine.cpp CollidoscopeApp/src/BufferToWaveRecorderNode.cpp CollidoscopeApp/src/Chunk.cpp CollidoscopeApp/src/CollidoscopeApp.cpp CollidoscopeApp/src/Config.cpp CollidoscopeApp/src/Log.cpp CollidoscopeApp/src/MIDI.cpp CollidoscopeApp/src/PGranularNode.cpp CollidoscopeApp/src/ParticleController.cpp CollidoscopeApp/src/RtMidi.cpp CollidoscopeApp/src/Wave.cpp JackDevice/ContextJack.cpp JackDevice/ContextJack.h JackDevice/DeviceManagerJack.h TeensyCode/collidoscope_double_knob.ino TeensyCode/collidoscope_single_knob.ino
diffstat 35 files changed, 8056 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/AudioEngine.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <array>
+
+#include "cinder/audio/Context.h"
+#include "cinder/audio/ChannelRouterNode.h"
+#include "cinder/audio/MonitorNode.h"
+#include "cinder/audio/FilterNode.h"
+#include "BufferToWaveRecorderNode.h"
+#include "PGranularNode.h"
+#include "RingBufferPack.h"
+
+#include "Messages.h"
+#include "Config.h"
+
+
+class AudioEngine
+{
+public:
+
+    AudioEngine();
+
+    ~AudioEngine();
+
+    // no copies
+    AudioEngine( const AudioEngine &copy ) = delete;
+    AudioEngine & operator=(const AudioEngine &copy) = delete;
+
+    void setup( const Config& Config );
+
+    size_t getSampleRate();
+
+    void record( size_t index );
+
+    void loopOn( size_t waveIdx );
+
+    void loopOff( size_t waveIdx );
+
+    void noteOn( size_t waveIdx, int note );
+
+    void noteOff( size_t waveIdx, int note );
+
+    size_t getRecordWaveAvailable( size_t index );
+
+    bool readRecordWave( size_t waveIdx, RecordWaveMsg*, size_t count );
+
+    void setSelectionSize( size_t waveIdx, size_t size );
+
+    void setSelectionStart( size_t waveIdx, size_t start );
+
+    void setGrainDurationCoeff( size_t waveIdx, double coeff );
+
+    void setFilterCutoff( size_t waveIdx, double cutoff );
+
+    void checkCursorTriggers( size_t waveIdx, std::vector<CursorTriggerMsg>& cursorTriggers );
+
+    const ci::audio::Buffer& getAudioOutputBuffer( size_t waveIdx ) const;
+
+
+private:
+
+    // nodes for mic input 
+    std::array< ci::audio::ChannelRouterNodeRef, NUM_WAVES > mInputRouterNodes;
+    // nodes for recording audio input into buffer. Also sends chunks information through 
+    // non-blocking queue 
+    std::array< BufferToWaveRecorderNodeRef, NUM_WAVES > mBufferRecorderNodes;
+    // pgranulars for loop synths 
+    std::array< PGranularNodeRef, NUM_WAVES > mPGranularNodes;
+
+
+    std::array< ci::audio::ChannelRouterNodeRef, NUM_WAVES > mOutputRouterNodes;
+    // nodes to get the audio buffer scoped in the oscilloscope 
+    std::array< ci::audio::MonitorNodeRef, NUM_WAVES > mOutputMonitorNodes;
+    // nodes for lowpass filtering
+    std::array< cinder::audio::FilterLowPassNodeRef, NUM_WAVES> mLowPassFilterNodes;
+
+    std::array< std::unique_ptr< RingBufferPack<CursorTriggerMsg> >, NUM_WAVES > mCursorTriggerRingBufferPacks;
+
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/BufferToWaveRecorderNode.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "cinder/Cinder.h"
+#include "cinder/audio/Node.h"
+#include "cinder/audio/SampleRecorderNode.h"
+#include "cinder/audio/dsp/RingBuffer.h"
+#include "cinder/Filesystem.h"
+
+#include "Messages.h"
+
+typedef std::shared_ptr<class BufferToWaveRecorderNode>	BufferToWaveRecorderNodeRef;
+
+typedef ci::audio::dsp::RingBufferT<RecordWaveMsg> RecordWaveMsgRingBuffer;
+
+
+class BufferToWaveRecorderNode : public ci::audio::SampleRecorderNode {
+public:
+
+    static const float kRampTime;
+
+    //! Constructor. numChunks is the total number of chunks this biffer has to be borken down in. 
+    //! numSeconds lenght of the buffer in seconds 
+    BufferToWaveRecorderNode( std::size_t numChunks, double numSeconds );
+
+    //! Starts recording. Resets the write position to zero (call disable() to pause recording).
+    void start();
+    //! Stops recording. Same as calling disable().
+    void stop();
+
+    //! \brief Sets the length of the recording buffer in frames.
+    //!
+    //! If the write position is non-zero, the old contents will be preserved (by copying it to the newly allocated Buffer).
+    //! If \a shrinkToFit is set to `true`, the internal Buffer will be down-sized if necessary, otherwise it will only re-allocate when growing while changing its dimensions to match \a numFrames (default shrinkToFit = false).
+    void setNumFrames(size_t numFrames, bool shrinkToFit = false);
+    //! Sets the length of the recording buffer in seconds. \see setNumFrames
+    void setNumSeconds(double numSeconds, bool shrinkToFit = false);
+
+    //! Returns the length of the recording buffer in frames.
+    size_t		getNumFrames() const	{ return mRecorderBuffer.getNumFrames(); }
+    //! Returns the length of the recording buffer in seconds.
+    double		getNumSeconds() const;
+
+    //! \brief Returns a copy of the recored samples, up to the current write position.
+    //!
+    //! This method is non locking, and as such any resizing calls must be performed on the same thread or be otherwise synchronized.
+    ci::audio::BufferRef	getRecordedCopy() const;
+
+    //! \brief Writes the currently recorded samples to a file at \a filePath
+    //!
+    //! The encoding format is derived from \a filePath's extension and \a sampleType (default = SampleType::INT_16).
+    //! \note throws AudioFileExc if the write request cannot be completed.
+    void writeToFile(const ci::fs::path &filePath, ci::audio::SampleType sampleType = ci::audio::SampleType::INT_16);
+
+    //! Returns the frame of the last buffer overrun or 0 if none since the last time this method was called. When this happens, it means the recorded buffer probably has skipped some frames.
+    uint64_t getLastOverrun();
+
+    RecordWaveMsgRingBuffer& getRingBuffer() { return mRingBuffer; }
+
+    ci::audio::Buffer* getRecorderBuffer() { return &mRecorderBuffer; }
+
+
+protected:
+    void initialize()				override;
+    void process(ci::audio::Buffer *buffer)	override;
+
+    void initBuffers(size_t numFrames);
+
+    static const float kMinAudioVal; 
+    static const float kMaxAudioVal;
+
+    ci::audio::BufferDynamic		mRecorderBuffer;
+    ci::audio::BufferDynamicRef		mCopiedBuffer;
+    std::atomic<uint64_t>	mLastOverrun;
+
+    RecordWaveMsgRingBuffer mRingBuffer;
+
+    const std::size_t mNumChunks;
+    const double mNumSeconds;
+    std::size_t mNumSamplesPerChunk;
+    std::atomic<std::size_t> mChunkIndex;
+
+    size_t mChunkSampleCounter;
+    float mChunkMaxAudioVal;
+    float mChunkMinAudioVal;
+
+    float mEnvRamp;
+    float mEnvRampRate;
+    size_t mEnvRampLen;
+    size_t mEnvDecayStart;
+
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Chunk.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,57 @@
+
+#pragma once
+
+#include "cinder/Color.h"
+#include "cinder/gl/Batch.h"
+
+class DrawInfo;
+
+class Chunk
+{
+
+public:
+
+    const static float kWidth;
+    const static float kHalfWidth;
+
+	Chunk( size_t index );
+
+    void inline setTop(float t) { mAudioTop = t; mAnimate = 0.0f; mResetting = false; /* startes the animation to crate a chunk */ }
+    void inline setBottom(float b) { mAudioBottom = b; mAnimate = 0.0f; mResetting = false; }
+    float inline getTop() const { return mAudioTop; }
+    float inline getBottom() const { return mAudioBottom; }
+
+	void reset(){
+		mResetting = true;
+	}
+
+    void update( const DrawInfo& di );
+
+    void draw( const DrawInfo& di, ci::gl::BatchRef &batch );
+
+    void drawBar( const DrawInfo& di, ci::gl::BatchRef &batch );
+
+	void setAsSelectionStart(bool start){
+		isSelectionStart = start;
+	}
+
+	void setAsSelectionEnd(bool end){
+		isSelectionEnd = end;
+	}
+
+private:
+
+    float mAudioTop;
+    float mAudioBottom;
+
+    float mX;
+
+    float mAnimate = 1.0;
+    int mIndex;
+
+    bool isSelectionStart = false;
+    bool isSelectionEnd = false;
+
+    bool mResetting = false;
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Config.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <string>
+#include <array>
+#include "cinder/Color.h"
+#include "cinder/Xml.h"
+
+
+class Config
+{
+public:
+
+    Config();
+
+    // no copies 
+    Config( const Config &copy ) = delete;
+    Config & operator=(const Config &copy) = delete;
+
+    /* load values for internal field from configuration file. Throws ci::Exception */
+    void loadFromFile( std::string&& path );
+
+    std::string getInputDeviceKey() const
+    {
+        return mAudioInputDeviceKey;  // Komplete 1/2
+        //return "{0.0.1.00000000}.{a043bc8c-1dd1-4c94-82b4-ad8320cac5a5}"; // Komplete 3/4 
+        //return "{0.0.1.00000000}.{828b681b-cc0c-44e1-93c9-5f1f46f5926f}"; // Realtek 
+    }
+
+    std::size_t getNumChunks() const
+    {
+        return mNumChunks;
+    }
+
+    /* return wave lenght in seconds */
+    double getWaveLen() const
+    {
+        return mWaveLen;
+    }
+
+    ci::Color getWaveSelectionColor(size_t waveIdx) const
+    {
+        if (waveIdx == 0){
+            return cinder::Color(243.0f / 255.0f, 6.0f / 255.0f, 62.0f / 255.0f);
+        }
+        else{
+            return cinder::Color(255.0f / 255.0f, 204.0f / 255.0f, 0.0f / 255.0f);
+        }
+    }
+
+    std::size_t getCursorTriggerMessageBufSize() const
+    {
+        return 512;
+    }
+
+    // returns the index of the wave associated to the MIDI channel passed as argument 
+    size_t getWaveForMIDIChannel( unsigned char channelIdx )
+    {
+        return channelIdx;
+        /*for ( int i = 0; i < NUM_WAVES; i++ ){
+            if ( channelIdx == mMidiChannels[i] )
+                return i;
+        }*/
+    }
+
+    double getMaxGrainDurationCoeff() const
+    {
+        return 8.0;
+    }
+
+    double getMaxFilterCutoffFreq() const
+    {
+        return 22050.;
+    }
+
+    double getMinFilterCutoffFreq() const
+    {
+        return 200.;
+    }
+
+    size_t getMaxKeyboardVoices() const
+    {
+        return 6;
+    }
+
+    size_t getMaxSelectionNumChunks() const
+    {
+        return 37;
+    }
+
+    size_t getOscilloscopeNumPointsDivider() const
+    {
+        return 4;
+    }
+
+private:
+
+    void parseWave( const ci::XmlTree &wave, int id );
+
+    std::string mAudioInputDeviceKey;
+    std::size_t mNumChunks;
+    double mWaveLen;
+    std::array< size_t, NUM_WAVES > mMidiChannels; 
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/DrawInfo.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "cinder/Area.h"
+
+class DrawInfo
+{
+public:
+
+    DrawInfo( size_t waveIndex ):
+        mWaveIndex( waveIndex ),
+        mWindowWidth(0),
+        mWindowHeight(0),
+        mSelectionBarHeight(0),
+        mShrinkFactor(1)
+    {}
+
+    void reset( const ci::Area &bounds, float shrinkFactor )
+    {
+        mWindowWidth = bounds.getWidth();
+        mWindowHeight = bounds.getHeight();
+        mSelectionBarHeight = mWindowHeight / NUM_WAVES;
+        mShrinkFactor = shrinkFactor;
+    }
+
+	float audioToHeigt(float audioSample) const {
+        /* clip into range [-1.1] */
+        if (audioSample < -1.0f) {
+            audioSample = -1.0f;
+        }
+        else if ( audioSample > 1.0f ){
+            audioSample = 1.0f;
+        }
+
+        /* map from [-1,1] to [0,1] */
+		float ratio = (audioSample - (-1.0f)) * 0.5f; // 2 = 1 - (-1) 
+
+		/* get bottom and add the scaled height */
+        return ratio * mSelectionBarHeight; //remove  bounds.getY1() bound only needed for size of tier
+	}
+
+    float getMaxChunkHeight() const 
+    {
+        return mSelectionBarHeight * mShrinkFactor;
+    }
+
+    float getSelectionBarHeight() const
+    {
+        return mSelectionBarHeight;
+    }
+
+    int32_t getWaveCenterY() const
+    {
+        if ( mWaveIndex == 0 )
+            return mWindowHeight * 0.75f + 1;
+        else
+            return mWindowHeight / (NUM_WAVES * 2);
+    }
+
+	int flipY(int y) const 
+    {
+        if ( mWaveIndex == 0)
+		    return mWindowHeight - y /*+ 24*/;
+        else
+            return y /*- 24*/;
+	}
+
+	int flipX(int x) const
+    {
+        return x;
+	}
+
+
+    // how much the wave is shrunk on the y axis with respect to the wave's tier 
+    float getShrinkFactor() const 
+    {
+        return mShrinkFactor;
+    }
+
+    int32_t getWindowWidth() const
+    {
+        return mWindowWidth;
+    }
+
+    int32_t getWindowHeight() const
+    {
+        return mWindowHeight;
+    }
+
+    DrawInfo( const DrawInfo &original ) = delete;
+    DrawInfo & operator=( const DrawInfo &original ) = delete;
+
+private:
+    const size_t mWaveIndex;
+
+    int32_t mWindowHeight;
+    int32_t mWindowWidth;
+    int32_t mSelectionBarHeight;
+
+    float mShrinkFactor;
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/EnvASR.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,91 @@
+#pragma once 
+
+namespace collidoscope {
+
+template <typename T>
+class EnvASR
+{
+public:
+
+    enum class State {
+        eAttack,
+        eSustain,
+        eRelease,
+        eIdle // before attack after release  
+    };
+
+    EnvASR( T sustainLevel, T attackTime, T releaseTime, std::size_t sampleRate ) :
+        mSustainLevel( sustainLevel ),
+        mState( State::eIdle ),
+        mValue( 0 )
+        
+    {
+        if ( attackTime <= 0 )
+            attackTime = T( 0.001 );
+
+        if ( releaseTime <= 0 )
+            releaseTime = T( 0.001 );
+        
+        mAttackRate =  T( 1.0 ) / (attackTime * sampleRate);
+        mReleaseRate = T( 1.0 ) / (releaseTime * sampleRate);
+    }
+
+    T tick()
+    {
+
+        switch ( mState )
+        {
+
+        case State::eIdle: {
+            mValue = 0;
+        };
+            break;
+
+        case State::eAttack: {
+            mValue += mAttackRate;
+            if ( mValue >= mSustainLevel ){
+                mValue = mSustainLevel;
+                mState = State::eSustain;
+            }
+        };
+            break;
+
+        case State::eRelease:
+            mValue -= mReleaseRate;
+            if ( mValue <= 0 ){
+                mValue = 0;
+                mState = State::eIdle;
+            }
+            break;
+        default:
+            break;
+        }
+
+        return mValue;
+
+    }
+
+    State getState() const
+    {
+        return mState;
+    }
+
+    void setState( State state )
+    {
+        mState = state;
+    }
+
+private:
+    T mSustainLevel;
+    T mAttackRate;
+    T mReleaseRate;
+
+    // output
+    T mValue;
+
+    State mState;
+
+};
+
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Log.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,8 @@
+#pragma once 
+
+
+
+void logError( const std::string &errorMsg );
+
+
+void logInfo( const std::string &infoMsg );
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/MIDI.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "RtMidi.h"
+#include <memory>
+#include <mutex>
+#include <array>
+
+class Config;
+
+
+namespace collidoscope {
+
+
+class MIDIException : public std::exception
+{
+public:
+
+    MIDIException( std::string message ) : mMessage( message ) {}
+
+    virtual const std::string& getMessage( void ) const { return mMessage; }
+
+#ifdef _WINDOWS
+    const char* what() const override { return mMessage.c_str(); }
+#else
+    const char* what() const noexcept override { return mMessage.c_str(); }
+#endif
+
+protected:
+    std::string mMessage;
+};
+
+class MIDIMessage
+{
+    friend class MIDI;
+public:
+
+    enum class Voice { eNoteOn, eNoteOff, ePitchBend, eControlChange, eIgnore };
+
+    Voice getVoice() { return mVoice; }
+
+    unsigned char getChannel() { return mChannel; }
+
+    unsigned char getData_1() { return mData1; }
+
+    unsigned char getData_2() { return mData2; }
+
+private:
+
+    Voice mVoice = Voice::eIgnore;
+    unsigned char mChannel;
+    unsigned char mData1;
+    unsigned char mData2;
+
+    
+};
+
+
+class MIDI
+{
+
+public:
+
+    MIDI();
+    ~MIDI();
+
+    void setup( const Config& );
+
+    void checkMessages( std::vector< MIDIMessage >&  );
+
+private:
+
+    static void RtMidiInCallback( double deltatime, std::vector<unsigned char> *message, void *userData );
+
+    MIDIMessage parseRtMidiMessage( std::vector<unsigned char> *message );
+
+    // messages to pass to checkMessages caller
+    std::vector< MIDIMessage > mMIDIMessages;
+    std::array< MIDIMessage, NUM_WAVES > mPitchBendMessages;
+    std::array< MIDIMessage, NUM_WAVES > mFilterMessages;
+
+
+
+    std::vector< std::unique_ptr <RtMidiIn> > mInputs;
+    std::mutex mMutex;
+};
+
+
+
+}  // collidsocope } 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Messages.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,77 @@
+#pragma once
+
+
+enum class Command {
+    // message carrying info about one chunk of recorder audio. 
+    WAVE_CHUNK,
+    // message sent when a new recording starts. The gui resets the wave upon receiving it. 
+    WAVE_START,
+
+    TRIGGER_UPDATE,
+    TRIGGER_END,
+
+    NOTE_ON,
+    NOTE_OFF,
+
+    LOOP_ON,
+    LOOP_OFF
+};
+
+/* Messages sent from the audio thread to the graphic wave. 
+   This includes the wave chunks when the audio is recorder in the buffer and 
+   the cursor position when the grains are reset.
+*/
+struct RecordWaveMsg
+{
+    Command cmd;
+    std::size_t index;
+    float arg1;
+    float arg2;
+};
+
+
+inline RecordWaveMsg makeRecordWaveMsg( Command cmd, std::size_t index, float arg1, float arg2 )
+{
+    RecordWaveMsg msg;
+    msg.cmd = cmd;
+    msg.index = index;
+    msg.arg1 = arg1;
+    msg.arg2 = arg2;
+
+    return msg;
+}
+
+
+struct CursorTriggerMsg
+{
+    Command cmd;
+    int synthID;
+};
+
+inline CursorTriggerMsg makeCursorTriggerMsg( Command cmd, std::uint8_t synthID )
+{
+    CursorTriggerMsg msg;
+
+    msg.cmd = cmd;
+    msg.synthID = synthID;
+
+    return msg;
+}
+
+struct NoteMsg
+{
+    Command cmd;
+    int midiNote;
+    double rate;
+};
+
+inline NoteMsg makeNoteMsg( Command cmd, int midiNote, double rate )
+{
+    NoteMsg msg;
+
+    msg.cmd = cmd;
+    msg.midiNote = midiNote;
+    msg.rate = rate;
+
+    return msg;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Oscilloscope.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "cinder/gl/gl.h"
+
+#include "DrawInfo.h"
+
+class Oscilloscope
+{
+
+public:
+
+    Oscilloscope( size_t numPoints ):
+        mNumPoints( numPoints ),
+        mLine( std::vector<ci::vec2>( numPoints, ci::vec2() ) )
+        {}
+
+    void  setPoint( int index, float audioVal, const DrawInfo &di ){
+
+        if ( audioVal > 1.0f ){
+            audioVal = 1.0f;
+        }
+        else if ( audioVal < -1.0f ){
+            audioVal = -1.0f;
+        }
+
+        audioVal *= 0.8f;
+        // this yRatio is for the bottom scope, the top will be drawn with a translation/4
+        // because it's half of the half of the tier where the wave is drawn  
+        float yRatio = ((1 + audioVal) / 2.0f) * (di.getWindowHeight() / NUM_WAVES );
+        float xRatio = index * (di.getWindowWidth() / (float)mLine.size());
+
+        mLine.getPoints()[index].x = float( di.flipX( int(xRatio) ) );
+        mLine.getPoints()[index].y = float( di.flipY( int(yRatio) ) );
+
+        // add the missing line to reach the right of the window
+        // indeed the scope starts from 0 to size -1 and adds xRatio
+        // to each new point to the line from n-1 to n is missing 
+        if (index == mNumPoints - 1){
+            xRatio += ( di.getWindowWidth() / mNumPoints );
+            xRatio = ceil( xRatio ); // ceil because the division might left one pixel out
+
+            mLine.getPoints()[mNumPoints - 1].x = di.flipX( xRatio ); 
+            mLine.getPoints()[mNumPoints - 1].y = di.flipY( yRatio );
+        }
+
+    }
+
+    void draw()
+    {
+        ci::gl::color(1.0f, 1.0f, 1.0f);
+        ci::gl::draw( mLine );
+    }
+
+    size_t getNumPoints() const
+    {
+        return mNumPoints;
+    }
+
+private:
+    size_t mNumPoints;
+    ci::PolyLine2f  mLine;
+
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/PGranular.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,359 @@
+#pragma once
+
+#include <array>
+#include <type_traits>
+
+#include "EnvASR.h"
+
+
+namespace collidoscope {
+
+using std::size_t;
+
+template <typename T, typename RandOffsetFunc, typename TriggerCallbackFunc>
+class PGranular
+{
+
+public:
+    static const size_t kMaxGrains = 32;
+    static const size_t kMinGrainsDuration = 640;
+
+    static inline T interpolateLin( double xn, double xn_1, double decimal )
+    {
+        /* weighted sum interpolation */
+        return static_cast<T> ((1 - decimal) * xn + decimal * xn_1);
+    }
+
+    struct PGrain
+    {
+        double phase;    // read pointer to mBuffer of this grain 
+        double rate;     // rate of the grain. e.g. rate = 2 the grain will play twice as fast
+        bool alive;      // whether this grain is alive. Not alive means it has been processed and can be replanced by another grain
+        size_t age;      // age of this grain in samples 
+        size_t duration; // duration of this grain in samples. minimum = 4
+
+        double b1;       // hann envelope from Ross Becina "Implementing real time Granular Synthesis"
+        double y1;
+        double y2;
+    };
+
+
+
+    PGranular( const T* buffer, size_t bufferLen, size_t sampleRate, RandOffsetFunc & rand, TriggerCallbackFunc & triggerCallback, int ID ) :
+        mBuffer( buffer ),
+        mBufferLen( bufferLen ),
+        mNumAliveGrains( 0 ),
+        mGrainsRate( 1.0 ),
+        mTrigger( 0 ),
+        mTriggerRate( 0 ), // start silent 
+        mGrainsStart( 0 ),
+        mGrainsDuration( kMinGrainsDuration ),
+        mGrainsDurationCoeff( 1 ),
+        mRand( rand ),
+        mTriggerCallback( triggerCallback ),
+        mEnvASR( 1.0f, 0.01f, 0.05f, sampleRate ),
+        mAttenuation( T(0.25118864315096) ),
+        mID( ID )
+    {
+        static_assert(std::is_pod<PGrain>::value, "PGrain must be POD");
+#ifdef _WINDOW
+        static_assert(std::is_same<std::result_of<RandOffsetFunc()>::type, size_t>::value, "Rand must return a size_t");
+#endif
+        /* init the grains */
+        for ( size_t grainIdx = 0; grainIdx < kMaxGrains; grainIdx++ ){
+            mGrains[grainIdx].phase = 0;
+            mGrains[grainIdx].rate = 1;
+            mGrains[grainIdx].alive = false;
+            mGrains[grainIdx].age = 0;
+            mGrains[grainIdx].duration = 1;
+        }
+    }
+
+    ~PGranular(){}
+
+    /* sets multiplier of duration of grains in seconds */
+    void setGrainsDurationCoeff( double coeff )
+    {
+        mGrainsDurationCoeff = coeff;
+
+        mGrainsDuration = std::lround( mTriggerRate * coeff ); // FIXME check if right rounding 
+
+        if ( mGrainsDuration < kMinGrainsDuration )
+            mGrainsDuration = kMinGrainsDuration;
+    }
+
+    /* sets rate of grains. e.g rate = 2 means one octave higer */
+    void setGrainsRate( double rate )
+    {
+        mGrainsRate = rate;
+    }
+
+    // sets trigger rate in samples 
+    void setSelectionStart( size_t start )
+    {
+        mGrainsStart = start;
+    }
+
+    void setSelectionSize( size_t size )
+    {
+
+        if ( size < kMinGrainsDuration )
+            size = kMinGrainsDuration;
+
+        mTriggerRate = size;
+
+        mGrainsDuration = std::lround( size * mGrainsDurationCoeff );
+
+
+    }
+
+    void setAttenuation( T attenuation )
+    {
+        mAttenuation = attenuation;
+    }
+
+    void noteOn( double rate )
+    {
+        if ( mEnvASR.getState() == EnvASR<T>::State::eIdle ){
+            // note on sets triggering top the min value 
+            if ( mTriggerRate < kMinGrainsDuration ){
+                mTriggerRate = kMinGrainsDuration;
+            }
+
+            setGrainsRate( rate );
+            mEnvASR.setState( EnvASR<T>::State::eAttack );
+        }
+    }
+
+    void noteOff()
+    {
+        if ( mEnvASR.getState() != EnvASR<T>::State::eIdle ){
+            mEnvASR.setState( EnvASR<T>::State::eRelease );
+        }
+    }
+
+    bool isIdle()
+    {
+        return mEnvASR.getState() == EnvASR<T>::State::eIdle;
+    }
+
+    void process( T* audioOut, T* tempBuffer, size_t numSamples )
+    {
+        
+        // num samples worth of sound ( due to envelope possibly finishing )
+        size_t envSamples = 0;
+        bool becameIdle = false;
+
+        // do the envelope first and store it in the tempBuffer 
+        for ( size_t i = 0; i < numSamples; i++ ){
+            tempBuffer[i] = mEnvASR.tick();
+            envSamples++;
+
+            if ( isIdle() ){
+                // means that the envelope has stopped 
+                becameIdle = true;
+                break;
+            }
+        }
+
+        processGrains( audioOut, tempBuffer, envSamples );
+
+        if ( becameIdle ){
+            mTriggerCallback( 'e', mID );
+            reset();
+        }
+    }
+
+private:
+
+    void processGrains( T* audioOut, T* envelopeValues, size_t numSamples )
+    {
+
+        /* process all existing alive grains */
+        for ( size_t grainIdx = 0; grainIdx < mNumAliveGrains;  ){
+            synthesizeGrain( mGrains[grainIdx], audioOut, envelopeValues, numSamples );
+
+            if ( !mGrains[grainIdx].alive ){
+                // this grain is dead so copyu the last of the active grains here 
+                // so as to keep all active grains at the beginning of the array 
+                // don't increment grainIdx so the last active grain is processed next cycle
+                // if this grain is the last active grain then mNumAliveGrains is decremented 
+                // and grainIdx = mNumAliveGrains so the loop stops 
+                copyGrain( mNumAliveGrains - 1, grainIdx );
+                mNumAliveGrains--;
+            }
+            else{
+                // go to next grain 
+                grainIdx++;
+            }
+        }
+
+        if ( mTriggerRate == 0 ){
+            return;
+        }
+
+        size_t randOffset =  mRand();
+        bool newGrainWasTriggered = false;
+
+        // trigger new grain and synthesize them as well 
+        while ( mTrigger < numSamples ){
+            
+            // if there is room to accommodate new grains 
+            if ( mNumAliveGrains < kMaxGrains ){
+                // get next grain will be placed at the end of the alive ones 
+                size_t grainIdx = mNumAliveGrains;
+                mNumAliveGrains++;
+
+                // initialize and synthesise the grain 
+                PGrain &grain = mGrains[grainIdx];
+                
+                double phase = mGrainsStart + double( randOffset );
+                if ( phase >= mBufferLen )
+                    phase -= mBufferLen;
+
+                grain.phase = phase;
+                grain.rate = mGrainsRate;
+                grain.alive = true;
+                grain.age = 0;
+                grain.duration = mGrainsDuration;
+
+                const double w = 3.14159265358979323846 / mGrainsDuration;
+                grain.b1 = 2.0 * std::cos( w );
+                grain.y1 = std::sin( w );
+                grain.y2 = 0.0;
+
+                synthesizeGrain( grain, audioOut + mTrigger, envelopeValues + mTrigger, numSamples - mTrigger );
+
+                if ( grain.alive == false ) {
+                    mNumAliveGrains--;
+                }
+
+                newGrainWasTriggered = true;
+            }
+
+            // update trigger even if no new grain was started 
+            mTrigger += mTriggerRate;
+        }
+
+        // prepare trigger for next cycle: init mTrigger with the reminder of the samples from this cycle 
+        mTrigger -= numSamples;
+
+        if ( newGrainWasTriggered ){
+            mTriggerCallback( 't', mID );
+        }
+    }
+
+    // audioOut = pointer to audio block to fill 
+    // numSamples = numpber of samples to process for this block
+    void synthesizeGrain( PGrain &grain, T* audioOut, T* envelopeValues, size_t numSamples )
+    {
+
+        // copy all grain data into local variable for faster porcessing
+        const auto rate = grain.rate;
+        auto phase = grain.phase;
+        auto age = grain.age;
+        auto duration = grain.duration;
+
+
+        auto b1 = grain.b1;
+        auto y1 = grain.y1;
+        auto y2 = grain.y2;
+
+        // only process minimum between samples of this block and time left to leave for this grain 
+        auto numSamplesToOut = std::min( numSamples, duration - age );
+
+        for ( size_t sampleIdx = 0; sampleIdx < numSamplesToOut; sampleIdx++ ){
+
+            const size_t readIndex = (size_t)phase;
+            const size_t nextReadIndex = (readIndex == mBufferLen - 1) ? 0 : readIndex + 1; // wrap on the read buffer if needed 
+
+            const double decimal = phase - readIndex;
+
+            T out = interpolateLin( mBuffer[readIndex], mBuffer[nextReadIndex], decimal );
+            
+            // apply raised cosine bell envelope 
+            auto y0 = b1 * y1 - y2;
+            y2 = y1;
+            y1 = y0;
+            out *= T(y0);
+
+            audioOut[sampleIdx] += out * envelopeValues[sampleIdx] * mAttenuation;
+
+            // increment age one sample 
+            age++;
+            // increment the phase according to the rate of this grain 
+            phase += rate;
+
+            if ( phase >= mBufferLen ){   // wrap the phase if needed 
+                phase -= mBufferLen;
+            }
+        }
+
+        if ( age == duration ){
+            // if it porocessed all the samples left to leave ( numSamplesToOut = duration-age)
+            // then the grain is had finished 
+            grain.alive = false;
+        }
+        else{
+            grain.phase = phase;
+            grain.age = age;
+            grain.y1 = y1;
+            grain.y2 = y2;
+        }
+    }
+
+    void copyGrain( size_t from, size_t to)
+    {
+        mGrains[to] = mGrains[from];
+    }
+
+    void reset()
+    {
+        mTrigger = 0;
+        for ( size_t i = 0; i < mNumAliveGrains; i++ ){
+            mGrains[i].alive = false;
+        }
+
+        mNumAliveGrains = 0;
+    }
+
+    int mID;
+
+    // pointer to (mono) buffer, where the underlying sample is recorder 
+    const T* mBuffer;
+    // length of mBuffer in samples 
+    const size_t mBufferLen;
+
+    // offset in the buffer where the grains start. a.k.a. seleciton start 
+    size_t mGrainsStart;
+
+    // attenuates signal prevents clipping of grains 
+    T mAttenuation;
+
+    // grain duration in samples 
+    double mGrainsDurationCoeff;
+    // duration of grains is selcection size * duration coeff
+    size_t mGrainsDuration;
+    // rate of grain, affects pitch 
+    double mGrainsRate;
+
+    size_t mTrigger;       // next onset
+    size_t mTriggerRate;   // inter onset
+
+    // the array of grains 
+    std::array<PGrain, kMaxGrains> mGrains;
+    // number of alive grains 
+    size_t mNumAliveGrains;
+
+    RandOffsetFunc &mRand;
+    TriggerCallbackFunc &mTriggerCallback;
+
+    EnvASR<T> mEnvASR;
+};
+
+
+
+
+} // namespace collidoscope
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/PGranularNode.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,119 @@
+#pragma once
+
+#include "cinder/Cinder.h"
+#include "cinder/audio/Node.h"
+#include "cinder/audio/dsp/RingBuffer.h"
+#include "boost/optional.hpp"
+#include "Messages.h"
+#include "RingBufferPack.h"
+
+#include <memory>
+
+#include "PGranular.h"
+#include "EnvASR.h"
+
+typedef std::shared_ptr<class PGranularNode> PGranularNodeRef;
+typedef ci::audio::dsp::RingBufferT<CursorTriggerMsg> CursorTriggerMsgRingBuffer;
+
+
+struct RandomGenerator;
+
+/*
+A node in the Cinder audio graph that holds a PGranular 
+*/
+class PGranularNode : public ci::audio::Node
+{
+public:
+    static const size_t kMaxVoices = 6;
+    static const int kNoMidiNote = -50;
+
+    explicit PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer );
+    ~PGranularNode();
+
+    // set selection size in samples 
+    void setSelectionSize( size_t size )
+    {
+        mSelectionSize.set( size );
+    }
+
+    void setSelectionStart( size_t start )
+    {
+        mSelectionStart.set( start );
+    }
+
+    void setGrainsDurationCoeff( double coeff )
+    {
+        mGrainDurationCoeff.set( coeff );
+    }
+
+    // used for trigger callback in PGRanular 
+    void operator()( char msgType, int ID );
+
+    ci::audio::dsp::RingBufferT<NoteMsg>& getNoteRingBuffer() { return mNoteMsgRingBufferPack.getBuffer(); }
+
+protected:
+    
+    void initialize()							override;
+
+    void process( ci::audio::Buffer *buffer )	override;
+
+private:
+
+    template< typename T>
+    class LazyAtomic
+    {
+    public:
+        LazyAtomic( T val ) :
+            mAtomic( val ),
+            mPreviousVal( val )
+        {}
+
+        void set( T val )
+        {
+            mAtomic = val;
+        }
+
+        boost::optional<T> get()
+        {
+            const T val = mAtomic;
+            if ( val != mPreviousVal ){
+                mPreviousVal = val;
+                return val;
+            }
+            else{
+                return boost::none;
+            }
+        }
+
+    private:
+        std::atomic<T> mAtomic;
+        T mPreviousVal;
+    };
+
+    void handleNoteMsg( const NoteMsg &msg );
+
+    // pointer to PGranular object 
+    std::unique_ptr < collidoscope::PGranular<float, RandomGenerator, PGranularNode > > mPGranularLoop;
+    std::array<std::unique_ptr < collidoscope::PGranular<float, RandomGenerator, PGranularNode > >, kMaxVoices> mPGranularNotes;
+    std::array<int, kMaxVoices> mMidiNotes;
+
+    // pointer to the random generator struct passed over to PGranular 
+    std::unique_ptr< RandomGenerator > mRandomOffset;
+    
+    /* buffer containing the recorder audio, to pass to PGranular in initialize() */
+    ci::audio::Buffer *mGrainBuffer;
+
+    ci::audio::BufferRef mTempBuffer;
+
+    CursorTriggerMsgRingBuffer &mTriggerRingBuffer;
+    RingBufferPack<NoteMsg> mNoteMsgRingBufferPack;
+
+    LazyAtomic<size_t> mSelectionSize;
+    
+    LazyAtomic<size_t> mSelectionStart;
+    
+    LazyAtomic<double> mGrainDurationCoeff;
+
+
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/ParticleController.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "cinder/gl/gl.h"
+#include <vector>
+
+
+class ParticleController {
+
+    struct Particle {
+
+        ci::vec2	mCloudCenter;
+        ci::vec2	mVel;
+        float       mCloudSize;
+
+        int			mAge;
+        int			mLifespan;
+        bool        mFlyOver;
+
+    };
+
+    static const int kMaxParticles = 150;
+
+	std::vector<Particle> mParticles;
+    std::vector< ci::vec2 > mParticlePositions;
+
+    size_t mNumParticles;
+
+    ci::gl::VboRef			mParticleVbo;
+    ci::gl::BatchRef		mParticleBatch;
+
+ public:
+    static const int kMaxParticleAdd = 22;
+
+    ParticleController();
+	void addParticles(int amount, const ci::vec2 &initialLocation, const float cloudSize);
+	
+    void updateParticles();
+
+    inline void draw()
+    {
+        mParticleBatch->draw();
+    }
+	
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Resources.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,7 @@
+#pragma once
+#include "cinder/CinderResources.h"
+
+//#define RES_MY_RES			CINDER_RESOURCE( ../resources/, image_name.png, 128, IMAGE )
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/RingBufferPack.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "cinder/audio/dsp/RingBuffer.h"
+
+
+/* Packs together a RingBuffer and the erlated array used to exchange data (read/write) with the ring buffer 
+*/
+template <typename T>
+class RingBufferPack {
+
+public:
+
+    RingBufferPack( size_t size ) :
+        mSize( size ),
+        mBuffer( size )
+    {
+        mArray = new T[size];
+    }
+
+    ~RingBufferPack()
+    {
+        delete[]  mArray;
+    }
+
+    // no copy
+    RingBufferPack( const RingBufferPack &copy ) = delete;
+    RingBufferPack & operator=(const RingBufferPack &copy) = delete;
+
+    ci::audio::dsp::RingBufferT<T> & getBuffer() { return mBuffer; }
+
+    T* getExchangeArray() { return mArray; }
+
+    std::size_t getSize() { return mSize;  }
+
+private:
+    size_t mSize;
+
+    ci::audio::dsp::RingBufferT<T> mBuffer;
+
+    T* mArray;
+
+
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/RtMidi.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,763 @@
+/**********************************************************************/
+/*! \class RtMidi
+    \brief An abstract base class for realtime MIDI input/output.
+
+    This class implements some common functionality for the realtime
+    MIDI input/output subclasses RtMidiIn and RtMidiOut.
+
+    RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
+
+    RtMidi: realtime MIDI i/o C++ classes
+    Copyright (c) 2003-2016 Gary P. Scavone
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation files
+    (the "Software"), to deal in the Software without restriction,
+    including without limitation the rights to use, copy, modify, merge,
+    publish, distribute, sublicense, and/or sell copies of the Software,
+    and to permit persons to whom the Software is furnished to do so,
+    subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    Any person wishing to distribute modifications to the Software is
+    asked to send the modifications to the original developer so that
+    they can be incorporated into the canonical version.  This is,
+    however, not a binding provision of this license.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**********************************************************************/
+
+/*!
+  \file RtMidi.h
+ */
+
+#ifndef RTMIDI_H
+#define RTMIDI_H
+
+#define RTMIDI_VERSION "2.1.1"
+
+#include <exception>
+#include <iostream>
+#include <string>
+#include <vector>
+
+/************************************************************************/
+/*! \class RtMidiError
+    \brief Exception handling class for RtMidi.
+
+    The RtMidiError class is quite simple but it does allow errors to be
+    "caught" by RtMidiError::Type. See the RtMidi documentation to know
+    which methods can throw an RtMidiError.
+*/
+/************************************************************************/
+
+class RtMidiError : public std::exception
+{
+ public:
+  //! Defined RtMidiError types.
+  enum Type {
+    WARNING,           /*!< A non-critical error. */
+    DEBUG_WARNING,     /*!< A non-critical error which might be useful for debugging. */
+    UNSPECIFIED,       /*!< The default, unspecified error type. */
+    NO_DEVICES_FOUND,  /*!< No devices found on system. */
+    INVALID_DEVICE,    /*!< An invalid device ID was specified. */
+    MEMORY_ERROR,      /*!< An error occured during memory allocation. */
+    INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */
+    INVALID_USE,       /*!< The function was called incorrectly. */
+    DRIVER_ERROR,      /*!< A system driver error occured. */
+    SYSTEM_ERROR,      /*!< A system error occured. */
+    THREAD_ERROR       /*!< A thread error occured. */
+  };
+
+  //! The constructor.
+  RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {}
+ 
+  //! The destructor.
+  virtual ~RtMidiError( void ) throw() {}
+
+  //! Prints thrown error message to stderr.
+  virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; }
+
+  //! Returns the thrown error message type.
+  virtual const Type& getType(void) const throw() { return type_; }
+
+  //! Returns the thrown error message string.
+  virtual const std::string& getMessage(void) const throw() { return message_; }
+
+  //! Returns the thrown error message as a c-style string.
+  virtual const char* what( void ) const throw() { return message_.c_str(); }
+
+ protected:
+  std::string message_;
+  Type type_;
+};
+
+//! RtMidi error callback function prototype.
+/*!
+    \param type Type of error.
+    \param errorText Error description.
+
+    Note that class behaviour is undefined after a critical error (not
+    a warning) is reported.
+ */
+typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData );
+
+class MidiApi;
+
+class RtMidi
+{
+ public:
+
+  //! MIDI API specifier arguments.
+  enum Api {
+    UNSPECIFIED,    /*!< Search for a working compiled API. */
+    MACOSX_CORE,    /*!< Macintosh OS-X Core Midi API. */
+    LINUX_ALSA,     /*!< The Advanced Linux Sound Architecture API. */
+    UNIX_JACK,      /*!< The JACK Low-Latency MIDI Server API. */
+    WINDOWS_MM,     /*!< The Microsoft Multimedia MIDI API. */
+    RTMIDI_DUMMY    /*!< A compilable but non-functional API. */
+  };
+
+  //! A static function to determine the current RtMidi version.
+  static std::string getVersion( void ) throw();
+
+  //! A static function to determine the available compiled MIDI APIs.
+  /*!
+    The values returned in the std::vector can be compared against
+    the enumerated list values.  Note that there can be more than one
+    API compiled for certain operating systems.
+  */
+  static void getCompiledApi( std::vector<RtMidi::Api> &apis ) throw();
+
+  //! Pure virtual openPort() function.
+  virtual void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi" ) ) = 0;
+
+  //! Pure virtual openVirtualPort() function.
+  virtual void openVirtualPort( const std::string portName = std::string( "RtMidi" ) ) = 0;
+
+  //! Pure virtual getPortCount() function.
+  virtual unsigned int getPortCount() = 0;
+
+  //! Pure virtual getPortName() function.
+  virtual std::string getPortName( unsigned int portNumber = 0 ) = 0;
+
+  //! Pure virtual closePort() function.
+  virtual void closePort( void ) = 0;
+
+  //! Returns true if a port is open and false if not.
+  virtual bool isPortOpen( void ) const = 0;
+
+  //! Set an error callback function to be invoked when an error has occured.
+  /*!
+    The callback function will be called whenever an error has occured. It is best
+    to set the error callback function before opening a port.
+  */
+  virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0;
+
+ protected:
+
+  RtMidi();
+  virtual ~RtMidi();
+
+  MidiApi *rtapi_;
+};
+
+/**********************************************************************/
+/*! \class RtMidiIn
+    \brief A realtime MIDI input class.
+
+    This class provides a common, platform-independent API for
+    realtime MIDI input.  It allows access to a single MIDI input
+    port.  Incoming MIDI messages are either saved to a queue for
+    retrieval using the getMessage() function or immediately passed to
+    a user-specified callback function.  Create multiple instances of
+    this class to connect to more than one MIDI device at the same
+    time.  With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also
+    possible to open a virtual input port to which other MIDI software
+    clients can connect.
+
+    by Gary P. Scavone, 2003-2014.
+*/
+/**********************************************************************/
+
+// **************************************************************** //
+//
+// RtMidiIn and RtMidiOut class declarations.
+//
+// RtMidiIn / RtMidiOut are "controllers" used to select an available
+// MIDI input or output interface.  They present common APIs for the
+// user to call but all functionality is implemented by the classes
+// MidiInApi, MidiOutApi and their subclasses.  RtMidiIn and RtMidiOut
+// each create an instance of a MidiInApi or MidiOutApi subclass based
+// on the user's API choice.  If no choice is made, they attempt to
+// make a "logical" API selection.
+//
+// **************************************************************** //
+
+class RtMidiIn : public RtMidi
+{
+ public:
+
+  //! User callback function type definition.
+  typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData);
+
+  //! Default constructor that allows an optional api, client name and queue size.
+  /*!
+    An exception will be thrown if a MIDI system initialization
+    error occurs.  The queue size defines the maximum number of
+    messages that can be held in the MIDI queue (when not using a
+    callback function).  If the queue size limit is reached,
+    incoming messages will be ignored.
+
+    If no API argument is specified and multiple API support has been
+    compiled, the default order of use is ALSA, JACK (Linux) and CORE,
+    JACK (OS-X).
+
+    \param api        An optional API id can be specified.
+    \param clientName An optional client name can be specified. This
+                      will be used to group the ports that are created
+                      by the application.
+    \param queueSizeLimit An optional size of the MIDI input queue can be specified.
+  */
+  RtMidiIn( RtMidi::Api api=UNSPECIFIED,
+            const std::string clientName = std::string( "RtMidi Input Client"),
+            unsigned int queueSizeLimit = 100 );
+
+  //! If a MIDI connection is still open, it will be closed by the destructor.
+  ~RtMidiIn ( void ) throw();
+
+  //! Returns the MIDI API specifier for the current instance of RtMidiIn.
+  RtMidi::Api getCurrentApi( void ) throw();
+
+  //! Open a MIDI input connection given by enumeration number.
+  /*!
+    \param portNumber An optional port number greater than 0 can be specified.
+                      Otherwise, the default or first port found is opened.
+    \param portName An optional name for the application port that is used to connect to portId can be specified.
+  */
+  void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) );
+
+  //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only).
+  /*!
+    This function creates a virtual MIDI input port to which other
+    software applications can connect.  This type of functionality
+    is currently only supported by the Macintosh OS-X, any JACK,
+    and Linux ALSA APIs (the function returns an error for the other APIs).
+
+    \param portName An optional name for the application port that is
+                    used to connect to portId can be specified.
+  */
+  void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) );
+
+  //! Set a callback function to be invoked for incoming MIDI messages.
+  /*!
+    The callback function will be called whenever an incoming MIDI
+    message is received.  While not absolutely necessary, it is best
+    to set the callback function before opening a MIDI port to avoid
+    leaving some messages in the queue.
+
+    \param callback A callback function must be given.
+    \param userData Optionally, a pointer to additional data can be
+                    passed to the callback function whenever it is called.
+  */
+  void setCallback( RtMidiCallback callback, void *userData = 0 );
+
+  //! Cancel use of the current callback function (if one exists).
+  /*!
+    Subsequent incoming MIDI messages will be written to the queue
+    and can be retrieved with the \e getMessage function.
+  */
+  void cancelCallback();
+
+  //! Close an open MIDI connection (if one exists).
+  void closePort( void );
+
+  //! Returns true if a port is open and false if not.
+  virtual bool isPortOpen() const;
+
+  //! Return the number of available MIDI input ports.
+  /*!
+    \return This function returns the number of MIDI ports of the selected API.
+  */
+  unsigned int getPortCount();
+
+  //! Return a string identifier for the specified MIDI input port number.
+  /*!
+    \return The name of the port with the given Id is returned.
+    \retval An empty string is returned if an invalid port specifier is provided.
+  */
+  std::string getPortName( unsigned int portNumber = 0 );
+
+  //! Specify whether certain MIDI message types should be queued or ignored during input.
+  /*!
+    By default, MIDI timing and active sensing messages are ignored
+    during message input because of their relative high data rates.
+    MIDI sysex messages are ignored by default as well.  Variable
+    values of "true" imply that the respective message type will be
+    ignored.
+  */
+  void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true );
+
+  //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds.
+  /*!
+    This function returns immediately whether a new message is
+    available or not.  A valid message is indicated by a non-zero
+    vector size.  An exception is thrown if an error occurs during
+    message retrieval or an input connection was not previously
+    established.
+  */
+  double getMessage( std::vector<unsigned char> *message );
+
+  //! Set an error callback function to be invoked when an error has occured.
+  /*!
+    The callback function will be called whenever an error has occured. It is best
+    to set the error callback function before opening a port.
+  */
+  virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
+
+ protected:
+  void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit );
+
+};
+
+/**********************************************************************/
+/*! \class RtMidiOut
+    \brief A realtime MIDI output class.
+
+    This class provides a common, platform-independent API for MIDI
+    output.  It allows one to probe available MIDI output ports, to
+    connect to one such port, and to send MIDI bytes immediately over
+    the connection.  Create multiple instances of this class to
+    connect to more than one MIDI device at the same time.  With the
+    OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a
+    virtual port to which other MIDI software clients can connect.
+
+    by Gary P. Scavone, 2003-2014.
+*/
+/**********************************************************************/
+
+class RtMidiOut : public RtMidi
+{
+ public:
+
+  //! Default constructor that allows an optional client name.
+  /*!
+    An exception will be thrown if a MIDI system initialization error occurs.
+
+    If no API argument is specified and multiple API support has been
+    compiled, the default order of use is ALSA, JACK (Linux) and CORE,
+    JACK (OS-X).
+  */
+  RtMidiOut( RtMidi::Api api=UNSPECIFIED,
+             const std::string clientName = std::string( "RtMidi Output Client") );
+
+  //! The destructor closes any open MIDI connections.
+  ~RtMidiOut( void ) throw();
+
+  //! Returns the MIDI API specifier for the current instance of RtMidiOut.
+  RtMidi::Api getCurrentApi( void ) throw();
+
+  //! Open a MIDI output connection.
+  /*!
+      An optional port number greater than 0 can be specified.
+      Otherwise, the default or first port found is opened.  An
+      exception is thrown if an error occurs while attempting to make
+      the port connection.
+  */
+  void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Output" ) );
+
+  //! Close an open MIDI connection (if one exists).
+  void closePort( void );
+
+  //! Returns true if a port is open and false if not.
+  virtual bool isPortOpen() const;
+
+  //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only).
+  /*!
+      This function creates a virtual MIDI output port to which other
+      software applications can connect.  This type of functionality
+      is currently only supported by the Macintosh OS-X, Linux ALSA
+      and JACK APIs (the function does nothing with the other APIs).
+      An exception is thrown if an error occurs while attempting to
+      create the virtual port.
+  */
+  void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) );
+
+  //! Return the number of available MIDI output ports.
+  unsigned int getPortCount( void );
+
+  //! Return a string identifier for the specified MIDI port type and number.
+  /*!
+      An empty string is returned if an invalid port specifier is provided.
+  */
+  std::string getPortName( unsigned int portNumber = 0 );
+
+  //! Immediately send a single message out an open MIDI output port.
+  /*!
+      An exception is thrown if an error occurs during output or an
+      output connection was not previously established.
+  */
+  void sendMessage( std::vector<unsigned char> *message );
+
+  //! Set an error callback function to be invoked when an error has occured.
+  /*!
+    The callback function will be called whenever an error has occured. It is best
+    to set the error callback function before opening a port.
+  */
+  virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 );
+
+ protected:
+  void openMidiApi( RtMidi::Api api, const std::string clientName );
+};
+
+
+// **************************************************************** //
+//
+// MidiInApi / MidiOutApi class declarations.
+//
+// Subclasses of MidiInApi and MidiOutApi contain all API- and
+// OS-specific code necessary to fully implement the RtMidi API.
+//
+// Note that MidiInApi and MidiOutApi are abstract base classes and
+// cannot be explicitly instantiated.  RtMidiIn and RtMidiOut will
+// create instances of a MidiInApi or MidiOutApi subclass.
+//
+// **************************************************************** //
+
+class MidiApi
+{
+ public:
+
+  MidiApi();
+  virtual ~MidiApi();
+  virtual RtMidi::Api getCurrentApi( void ) = 0;
+  virtual void openPort( unsigned int portNumber, const std::string portName ) = 0;
+  virtual void openVirtualPort( const std::string portName ) = 0;
+  virtual void closePort( void ) = 0;
+
+  virtual unsigned int getPortCount( void ) = 0;
+  virtual std::string getPortName( unsigned int portNumber ) = 0;
+
+  inline bool isPortOpen() const { return connected_; }
+  void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData );
+
+  //! A basic error reporting function for RtMidi classes.
+  void error( RtMidiError::Type type, std::string errorString );
+
+protected:
+  virtual void initialize( const std::string& clientName ) = 0;
+
+  void *apiData_;
+  bool connected_;
+  std::string errorString_;
+  RtMidiErrorCallback errorCallback_;
+  bool firstErrorOccurred_;
+  void *errorCallbackUserData_;
+};
+
+class MidiInApi : public MidiApi
+{
+ public:
+
+  MidiInApi( unsigned int queueSizeLimit );
+  virtual ~MidiInApi( void );
+  void setCallback( RtMidiIn::RtMidiCallback callback, void *userData );
+  void cancelCallback( void );
+  virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense );
+  double getMessage( std::vector<unsigned char> *message );
+
+  // A MIDI structure used internally by the class to store incoming
+  // messages.  Each message represents one and only one MIDI message.
+  struct MidiMessage { 
+    std::vector<unsigned char> bytes; 
+    double timeStamp;
+
+    // Default constructor.
+  MidiMessage()
+  :bytes(0), timeStamp(0.0) {}
+  };
+
+  struct MidiQueue {
+    unsigned int front;
+    unsigned int back;
+    unsigned int size;
+    unsigned int ringSize;
+    MidiMessage *ring;
+
+    // Default constructor.
+  MidiQueue()
+  :front(0), back(0), size(0), ringSize(0) {}
+  };
+
+  // The RtMidiInData structure is used to pass private class data to
+  // the MIDI input handling function or thread.
+  struct RtMidiInData {
+    MidiQueue queue;
+    MidiMessage message;
+    unsigned char ignoreFlags;
+    bool doInput;
+    bool firstMessage;
+    void *apiData;
+    bool usingCallback;
+    RtMidiIn::RtMidiCallback userCallback;
+    void *userData;
+    bool continueSysex;
+
+    // Default constructor.
+  RtMidiInData()
+  : ignoreFlags(7), doInput(false), firstMessage(true),
+      apiData(0), usingCallback(false), userCallback(0), userData(0),
+      continueSysex(false) {}
+  };
+
+ protected:
+  RtMidiInData inputData_;
+};
+
+class MidiOutApi : public MidiApi
+{
+ public:
+
+  MidiOutApi( void );
+  virtual ~MidiOutApi( void );
+  virtual void sendMessage( std::vector<unsigned char> *message ) = 0;
+};
+
+// **************************************************************** //
+//
+// Inline RtMidiIn and RtMidiOut definitions.
+//
+// **************************************************************** //
+
+inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
+inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }
+inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }
+inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); }
+inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); }
+inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); }
+inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); }
+inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); }
+inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
+inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); }
+inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); }
+inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
+
+inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }
+inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }
+inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }
+inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); }
+inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); }
+inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); }
+inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }
+inline void RtMidiOut :: sendMessage( std::vector<unsigned char> *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); }
+inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }
+
+// **************************************************************** //
+//
+// MidiInApi and MidiOutApi subclass prototypes.
+//
+// **************************************************************** //
+
+#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__)
+  #define __RTMIDI_DUMMY__
+#endif
+
+#if defined(__MACOSX_CORE__)
+
+class MidiInCore: public MidiInApi
+{
+ public:
+  MidiInCore( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInCore( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutCore: public MidiOutApi
+{
+ public:
+  MidiOutCore( const std::string clientName );
+  ~MidiOutCore( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__UNIX_JACK__)
+
+class MidiInJack: public MidiInApi
+{
+ public:
+  MidiInJack( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInJack( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  std::string clientName;
+
+  void connect( void );
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutJack: public MidiOutApi
+{
+ public:
+  MidiOutJack( const std::string clientName );
+  ~MidiOutJack( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  std::string clientName;
+
+  void connect( void );
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__LINUX_ALSA__)
+
+class MidiInAlsa: public MidiInApi
+{
+ public:
+  MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInAlsa( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutAlsa: public MidiOutApi
+{
+ public:
+  MidiOutAlsa( const std::string clientName );
+  ~MidiOutAlsa( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__WINDOWS_MM__)
+
+class MidiInWinMM: public MidiInApi
+{
+ public:
+  MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit );
+  ~MidiInWinMM( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+class MidiOutWinMM: public MidiOutApi
+{
+ public:
+  MidiOutWinMM( const std::string clientName );
+  ~MidiOutWinMM( void );
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };
+  void openPort( unsigned int portNumber, const std::string portName );
+  void openVirtualPort( const std::string portName );
+  void closePort( void );
+  unsigned int getPortCount( void );
+  std::string getPortName( unsigned int portNumber );
+  void sendMessage( std::vector<unsigned char> *message );
+
+ protected:
+  void initialize( const std::string& clientName );
+};
+
+#endif
+
+#if defined(__RTMIDI_DUMMY__)
+
+class MidiInDummy: public MidiInApi
+{
+ public:
+ MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
+  void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}
+  void openVirtualPort( const std::string /*portName*/ ) {}
+  void closePort( void ) {}
+  unsigned int getPortCount( void ) { return 0; }
+  std::string getPortName( unsigned int portNumber ) { return ""; }
+
+ protected:
+  void initialize( const std::string& /*clientName*/ ) {}
+};
+
+class MidiOutDummy: public MidiOutApi
+{
+ public:
+  MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }
+  RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }
+  void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}
+  void openVirtualPort( const std::string /*portName*/ ) {}
+  void closePort( void ) {}
+  unsigned int getPortCount( void ) { return 0; }
+  std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }
+  void sendMessage( std::vector<unsigned char> * /*message*/ ) {}
+
+ protected:
+  void initialize( const std::string& /*clientName*/ ) {}
+};
+
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/include/Wave.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,203 @@
+#pragma once
+
+
+#include "cinder/app/App.h"
+#include "cinder/gl/gl.h"
+#include "cinder/gl/Batch.h"
+
+
+#include "Chunk.h"
+#include "DrawInfo.h"
+
+#ifdef USE_PARTICLES
+#include "ParticleController.h"
+#endif 
+
+#include "cinder/Color.h"
+#include "cinder/PolyLine.h"
+#include "cinder/Rand.h"
+
+#include <vector>
+#include <map>
+
+
+class DrawInfo;
+typedef int SynthID;
+
+
+using ci::ivec2;
+using ci::vec2;
+using ci::Color;
+using ci::ColorA;
+
+struct Cursor {
+    static const int kNoPosition = -100;
+    int pos;
+    double lastUpdate;
+};
+
+
+class Wave
+{
+	friend class ParticleController;
+
+public:
+
+	class Selection {
+		
+	public:
+
+        Selection( Wave * w, Color color );
+        
+        void setStart( size_t start );
+
+		void setSize( size_t size );
+		
+        void inline setParticleSpread( float spread ){
+            mParticleSpread = spread;
+		}
+
+		size_t getStart(void) const { return mSelectionStart; }
+		
+        size_t getEnd(void) const { return mSelectionEnd; }
+
+		size_t inline getSize(void) const { 
+			if (mNull)
+				return 0;
+			else
+				return 1 + mSelectionEnd - mSelectionStart; 
+		}
+
+		float inline  getParticleSpread() const { return mParticleSpread; }
+
+		inline void setToNull(){
+            mParticleSpread = 1.0f;
+			mNull = true;
+		}
+
+		inline bool isNull() const{
+			return mNull;
+		}
+
+		inline const Color & getColor() const{
+			return mColor;
+		}
+
+    private:
+
+        size_t mSelectionStart;
+        
+        size_t mSelectionEnd;
+        
+        float mParticleSpread;
+
+        bool mNull = true;
+
+        Color mColor;
+
+        Wave * mWave;
+
+	}; // class Selection
+
+	
+
+#ifdef USE_PARTICLES
+	ParticleController mParticleController;
+#endif 
+
+	
+
+	/* there is one cursor for each Synth being played */
+	std::map < SynthID, Cursor > mCursors;
+	std::vector<int> mCursorsPos;
+
+public:
+	
+    // note used to identify the loop for cursor position 
+    static const int kLoopNote = -1;
+	static const cinder::Color CURSOR_CLR;
+	/* must be in sync with supercollider durationFactor ControlSpec max */
+	static const int MAX_DURATION = 8;
+#ifdef USE_PARTICLES
+	static const int PARTICLESIZE_COEFF = 40;
+#endif
+
+	void reset(bool onlyChunks);
+
+	void setChunk(size_t index, float bottom, float top);
+
+	const Chunk & getChunk(size_t index);
+
+    inline void setCursorPos( SynthID id, int pos, const DrawInfo& di ){
+
+	    Cursor & cursor = mCursors[id];
+	    cursor.pos = pos;
+	    cursor.lastUpdate = ci::app::getElapsedSeconds();
+
+#ifdef USE_PARTICLES
+	    /* if the duration is greater than 1.0 carry on the cursor as a particle
+	    the smaller the selection the more particles
+	    the bigger the duration the more particles */
+	    if (mSelection.getParticleSpread() > 1.0f){
+		    /* amountCoeff ranges from 1/8 to 1 */
+            const float amountCoeff = (mSelection.getParticleSpread() / MAX_DURATION);
+                
+            /* get radom point within seleciton as center of the particle */
+            vec2 centrePoint; // was former getRandomPoint
+            const int randomChunkIndex = ci::Rand::randInt(mSelection.getStart(), mSelection.getEnd() );
+
+            centrePoint.x = di.flipX( 1 + (randomChunkIndex * (2 + Chunk::kWidth)) + Chunk::kWidth / 2 );
+            centrePoint.y = di.flipY( di.audioToHeigt(0.0) );
+
+            const float wavePixelLen = mNumChunks * ( 2 + Chunk::kWidth);
+		    centrePoint.x *= float(di.getWindowWidth()) / wavePixelLen;
+
+		    mParticleController.addParticles(
+                std::max( 1, (int)(amountCoeff * ParticleController::kMaxParticleAdd * mFilterCoeff) ), // amount of particles to add 
+			    centrePoint,
+                mSelection.getParticleSpread() * PARTICLESIZE_COEFF   // size of the cloud 
+			    );
+	    }
+#endif
+
+		
+	}
+
+    void update( double secondsPerChunk, const DrawInfo& di );
+
+    void removeCursor( SynthID id ) { mCursors.erase( id ); }
+
+    // parameter ranges from 0 to 1 
+	inline void setselectionAlpha(float alpha){ mFilterCoeff = alpha;}
+
+    void draw( const DrawInfo& di );
+
+	Selection& getSelection() { return mSelection; };
+
+	size_t getSize() const{ return mChunks.size();  }
+
+	void setScopePoint(int index, float audioVal);
+
+    Wave( size_t numChunks, Color selectionColor );
+
+    // no copies 
+    Wave( const Wave &copy ) = delete;
+    Wave & operator=(const Wave &copy) = delete;
+
+
+private:
+    
+    const size_t mNumChunks;
+
+    std::vector<Chunk> mChunks;
+    
+    Selection mSelection;
+
+    cinder::Color mColor;
+
+    float mFilterCoeff;
+
+    ci::gl::BatchRef mChunkBatch;
+
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/linux/CMakeLists.txt	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,51 @@
+# Basic
+cmake_minimum_required( VERSION 2.8 FATAL_ERROR )
+set( CMAKE_VERBOSE_MAKEFILE on )
+
+get_filename_component( CINDER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE )
+include( ${CINDER_DIR}/linux/cmake/Cinder.cmake )
+
+project( Collidoscope )
+
+get_filename_component( SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../src" ABSOLUTE )
+get_filename_component( INC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../include" ABSOLUTE )
+
+if( NOT TARGET cinder${CINDER_LIB_SUFFIX} )
+    find_package( cinder REQUIRED
+        PATHS ${PROJECT_SOURCE_DIR}/../../../linux/${CMAKE_BUILD_TYPE}/${CINDER_OUT_DIR_PREFIX}
+        $ENV{Cinder_DIR}/linux/${CMAKE_BUILD_TYPE}/${CINDER_OUT_DIR_PREFIX}
+    )
+endif()
+
+add_definitions(-DNUM_WAVES=2)
+add_definitions(-D__LINUX_ALSA__)
+add_definitions(-DUSE_PARTICLES)
+
+# Use PROJECT_NAME since CMAKE_PROJET_NAME returns the top-level project name.
+set( EXE_NAME ${PROJECT_NAME} )
+
+set( SRC_FILES
+    ${SRC_DIR}/CollidoscopeApp.cpp
+	${SRC_DIR}/AudioEngine.cpp
+	${SRC_DIR}/BufferToWaveRecorderNode.cpp
+	${SRC_DIR}/Chunk.cpp
+	${SRC_DIR}/Config.cpp
+	${SRC_DIR}/Log.cpp
+	${SRC_DIR}/MIDI.cpp
+	${SRC_DIR}/PGranularNode.cpp
+	${SRC_DIR}/RtMidi.cpp
+	${SRC_DIR}/Wave.cpp
+    ${SRC_DIR}/ParticleController.cpp
+)
+
+add_executable( "${EXE_NAME}" ${SRC_FILES} )
+
+target_include_directories(
+	"${EXE_NAME}"
+	PUBLIC ${INC_DIR}
+)
+
+target_link_libraries( "${EXE_NAME}" cinder${CINDER_LIB_SUFFIX} )
+target_link_libraries( "${EXE_NAME}" jack )
+target_link_libraries( "${EXE_NAME}" asound )
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/linux/cibuild	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,2 @@
+#!/bin/sh
+../../../tools/linux/cibuilder -app "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/AudioEngine.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,201 @@
+#include "AudioEngine.h"
+#include "cinder/app/App.h"
+//FIXME remove App.h include 
+#include "Log.h"
+
+using namespace ci::audio;
+
+double chromaticRatios[] = { 
+    1, 
+    1.0594630943591, 
+    1.1224620483089, 
+    1.1892071150019, 
+    1.2599210498937, 
+    1.3348398541685, 
+    1.4142135623711, 
+    1.4983070768743, 
+    1.5874010519653, 
+    1.6817928305039, 
+    1.7817974362766, 
+    1.8877486253586 
+};
+
+inline double calculateMidiNoteRatio( int midiNote )
+{
+    int distanceFromCenter = midiNote - 60; // 60 is the central midi note 
+
+    if ( distanceFromCenter < 0 ){
+        int diffAmount = -distanceFromCenter;
+        int octaves = diffAmount / 12;
+        int intervals = diffAmount % 12;
+
+        return std::pow( 0.5, octaves ) / chromaticRatios[intervals];
+    }
+    else{
+        int octaves = distanceFromCenter / 12;
+        int intervals = distanceFromCenter % 12;
+
+        return std::pow( 2, octaves ) * chromaticRatios[intervals];
+    }
+}
+
+
+AudioEngine::AudioEngine() 
+{}
+
+AudioEngine::~AudioEngine()
+{}
+
+void AudioEngine::setup(const Config& config)
+{
+    
+    for ( int i = 0; i < NUM_WAVES; i++ ){
+        mCursorTriggerRingBufferPacks[i].reset( new RingBufferPack<CursorTriggerMsg>( 512 ) ); // FIXME 
+    }
+
+    /* audio context */
+    auto ctx = Context::master();
+
+    /* audio inpu device */
+    auto inputDeviceNode = ctx->createInputDeviceNode( Device::getDefaultInput() );
+ 
+
+    /* route the audio input, which is two channels, to one wave graph for each channel */
+    for ( int chan = 0; chan < NUM_WAVES; chan++ ){
+
+        /* one channel router */
+        mInputRouterNodes[chan] = ctx->makeNode( new ChannelRouterNode( Node::Format().channels( 1 ) ) );
+
+        /* buffer recorders */  
+        mBufferRecorderNodes[chan] = ctx->makeNode( new BufferToWaveRecorderNode( config.getNumChunks(), config.getWaveLen() ) );
+        /* this prevents the node from recording before record is pressed */
+        mBufferRecorderNodes[chan]->setAutoEnabled( false );
+
+        // route the input part of the audio graph. Two channels input goes into
+        // one channel route and to one channel buffer recorder 
+        inputDeviceNode >> mInputRouterNodes[chan]->route( chan, 0, 1 ) >> mBufferRecorderNodes[chan];
+
+
+        // create PGranular loops passing the buffer of the RecorderNode as argument to the contructor 
+        // use -1 as ID 
+        mPGranularNodes[chan] = ctx->makeNode( new PGranularNode( mBufferRecorderNodes[chan]->getRecorderBuffer(), mCursorTriggerRingBufferPacks[chan]->getBuffer() ) );
+
+        // create filter nodes 
+        mLowPassFilterNodes[chan] = ctx->makeNode( new FilterLowPassNode( MonitorNode::Format().channels( 1 ) ) );
+        mLowPassFilterNodes[chan]->setCutoffFreq( config.getMaxFilterCutoffFreq() );
+        mLowPassFilterNodes[chan]->setQ( 0.707f );
+        // create monitor nodes for oscilloscopes 
+        mOutputMonitorNodes[chan] = ctx->makeNode( new MonitorNode( MonitorNode::Format().channels( 1 ) ) );
+
+        // all output goes to the filter 
+        mPGranularNodes[chan] >> mLowPassFilterNodes[chan];
+        
+        mOutputRouterNodes[chan] = ctx->makeNode( new ChannelRouterNode( Node::Format().channels( 2 ) ) );
+
+        // filter goes to output 
+        mLowPassFilterNodes[chan] >> mOutputRouterNodes[chan]->route( 0, chan, 1 ) >> ctx->getOutput();
+        
+        // what goes to output goes to scope 
+        mLowPassFilterNodes[chan] >> mOutputMonitorNodes[chan];
+
+    }
+
+    ctx->getOutput()->enableClipDetection( false );
+    /* enable the whole audio graph */
+    inputDeviceNode->enable();
+    ctx->enable();
+}
+
+size_t AudioEngine::getSampleRate()
+{
+    return Context::master()->getSampleRate();
+}
+
+void AudioEngine::loopOn( size_t waveIdx )
+{
+    NoteMsg msg = makeNoteMsg( Command::LOOP_ON, 1, 1.0 );
+    mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
+}
+
+void AudioEngine::loopOff( size_t waveIdx )
+{
+    NoteMsg msg = makeNoteMsg( Command::LOOP_OFF, 0, 0.0 );
+    mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
+}
+
+void AudioEngine::record( size_t waveIdx )
+{
+    mBufferRecorderNodes[waveIdx]->start();
+}
+
+void AudioEngine::noteOn( size_t waveIdx, int midiNote )
+{
+    
+    double midiAsRate = calculateMidiNoteRatio(midiNote);
+    NoteMsg msg = makeNoteMsg( Command::NOTE_ON, midiNote, midiAsRate );
+
+    mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
+}
+
+void AudioEngine::noteOff( size_t waveIdx, int midiNote )
+{
+    NoteMsg msg = makeNoteMsg( Command::NOTE_OFF, midiNote, 0.0 );
+    mPGranularNodes[waveIdx]->getNoteRingBuffer().write( &msg, 1 );
+}
+
+
+
+void AudioEngine::setSelectionSize( size_t waveIdx, size_t size )
+{
+    mPGranularNodes[waveIdx]->setSelectionSize( size );
+}
+
+void AudioEngine::setSelectionStart( size_t waveIdx, size_t start )
+{
+    mPGranularNodes[waveIdx]->setSelectionStart( start );
+}
+
+void AudioEngine::setGrainDurationCoeff( size_t waveIdx, double coeff )
+{
+    mPGranularNodes[waveIdx]->setGrainsDurationCoeff( coeff );
+}
+
+void AudioEngine::setFilterCutoff( size_t waveIdx, double cutoff )
+{
+    mLowPassFilterNodes[waveIdx]->setCutoffFreq( cutoff );
+}
+
+// ------------------------------------------------------
+// ----- methods for communication with main thread -----
+// ------------------------------------------------------
+
+size_t AudioEngine::getRecordWaveAvailable( size_t waveIdx )
+{
+    return mBufferRecorderNodes[waveIdx]->getRingBuffer().getAvailableRead();
+}
+
+bool AudioEngine::readRecordWave( size_t waveIdx, RecordWaveMsg* buffer, size_t count )
+{
+    return mBufferRecorderNodes[waveIdx]->getRingBuffer().read( buffer, count );
+}
+
+void AudioEngine::checkCursorTriggers( size_t waveIdx, std::vector<CursorTriggerMsg>& cursorTriggers )
+{
+    ci::audio::dsp::RingBufferT<CursorTriggerMsg> &ringBuffer = mCursorTriggerRingBufferPacks[waveIdx]->getBuffer();
+    CursorTriggerMsg* ringBufferReadArray = mCursorTriggerRingBufferPacks[waveIdx]->getExchangeArray();
+
+    size_t availableRead = ringBuffer.getAvailableRead();
+    bool successfulRead = ringBuffer.read( ringBufferReadArray, availableRead );
+
+    if ( successfulRead ){
+        for ( size_t i = 0; i < availableRead; i++ ){
+            cursorTriggers.push_back( ringBufferReadArray[i] );
+        }
+    }
+}
+
+const ci::audio::Buffer& AudioEngine::getAudioOutputBuffer( size_t waveIdx ) const
+{
+    return mOutputMonitorNodes[waveIdx]->getBuffer();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/BufferToWaveRecorderNode.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,259 @@
+#include "BufferToWaveRecorderNode.h"
+#include "cinder/audio/Context.h"
+#include "cinder/audio/Target.h"
+
+
+
+// ----------------------------------------------------------------------------------------------------
+// MARK: - BufferRecorderNode
+// ----------------------------------------------------------------------------------------------------
+
+namespace {
+    
+const size_t DEFAULT_RECORD_BUFFER_FRAMES = 44100;
+
+void resizeBufferAndShuffleChannels(ci::audio::BufferDynamic *buffer, size_t resultNumFrames)
+{
+    const size_t currentNumFrames = buffer->getNumFrames();
+    const size_t sampleSize = sizeof(ci::audio::BufferDynamic::SampleType);
+
+    if (currentNumFrames < resultNumFrames) {
+        // if expanding, resize and then shuffle. Make sure to get the data pointer after the resize.
+        buffer->setNumFrames(resultNumFrames);
+        float *data = buffer->getData();
+
+        for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) {
+            const size_t numZeroFrames = resultNumFrames - currentNumFrames;
+            const float *currentChannel = &data[ch * currentNumFrames];
+            float *resultChannel = &data[ch * resultNumFrames];
+
+            memmove(resultChannel, currentChannel, currentNumFrames * sampleSize);
+            memset(resultChannel - numZeroFrames, 0, numZeroFrames * sampleSize);
+        }
+    }
+    else if (currentNumFrames > resultNumFrames) {
+        // if shrinking, shuffle first and then resize.
+        float *data = buffer->getData();
+
+        for (size_t ch = 1; ch < buffer->getNumChannels(); ch++) {
+            const float *currentChannel = &data[ch * currentNumFrames];
+            float *resultChannel = &data[ch * resultNumFrames];
+
+            memmove(resultChannel, currentChannel, currentNumFrames * sampleSize);
+        }
+
+        const size_t numZeroFrames = (currentNumFrames - resultNumFrames) * buffer->getNumChannels();
+        memset(data + buffer->getSize() - numZeroFrames, 0, numZeroFrames * sampleSize);
+
+        buffer->setNumFrames(resultNumFrames);
+    }
+}
+
+}
+
+
+BufferToWaveRecorderNode::BufferToWaveRecorderNode( std::size_t numChunks, double numSeconds )
+    : SampleRecorderNode( Format().channels( 1 ) ),
+    mLastOverrun( 0 ),
+    mNumChunks( numChunks ),
+    mNumSeconds( numSeconds ),
+    mRingBuffer( numChunks ),
+    mChunkMaxAudioVal( kMinAudioVal ),
+    mChunkMinAudioVal( kMaxAudioVal ),
+    mChunkSampleCounter( 0 ),
+    mChunkIndex( 0 )
+{
+    
+}
+
+void BufferToWaveRecorderNode::initialize()
+{
+    // adjust recorder buffer to match channels once initialized, since they could have changed since construction.
+    bool resize = mRecorderBuffer.getNumFrames() != 0;
+    mRecorderBuffer.setNumChannels( getNumChannels() );
+
+    // lenght of buffer is = number of seconds * sample rate 
+    initBuffers( size_t( mNumSeconds * (double)getSampleRate() ) ); 
+
+    // How many samples each chunk contains. That is it calculates the min and max of 
+    // This is calculated here and not in the initializer list because it uses getNumFrames()
+    // FIXME probably could be done in constructor body 
+    mNumSamplesPerChunk = std::lround( float( getNumFrames() ) / mNumChunks );
+
+    // if the buffer had already been resized, zero out any possibly existing data.
+    if( resize )
+        mRecorderBuffer.zero();
+
+    mEnvRampLen = kRampTime * getSampleRate();
+    mEnvDecayStart = mRecorderBuffer.getNumFrames() - mEnvRampLen;
+    if ( mEnvRampLen <= 0 ){
+        mEnvRampRate = 0;
+    }
+    else{
+        mEnvRampRate = 1.0f / mEnvRampLen;
+    }
+}
+
+void BufferToWaveRecorderNode::initBuffers(size_t numFrames)
+{
+    mRecorderBuffer.setSize( numFrames, getNumChannels() );
+    mCopiedBuffer = std::make_shared<ci::audio::BufferDynamic>( numFrames, getNumChannels() );
+}
+
+void BufferToWaveRecorderNode::start()
+{
+    mWritePos = 0;
+    mChunkIndex = 0;
+    enable();
+}
+
+void BufferToWaveRecorderNode::stop()
+{
+    disable();
+}
+
+void BufferToWaveRecorderNode::setNumSeconds(double numSeconds, bool shrinkToFit)
+{
+    setNumFrames(size_t(numSeconds * (double)getSampleRate()), shrinkToFit);
+}
+
+double BufferToWaveRecorderNode::getNumSeconds() const
+{
+    return (double)getNumFrames() / (double)getSampleRate();
+}
+
+void BufferToWaveRecorderNode::setNumFrames(size_t numFrames, bool shrinkToFit)
+{
+    if (mRecorderBuffer.getNumFrames() == numFrames)
+        return;
+
+    std::lock_guard<std::mutex> lock(getContext()->getMutex());
+
+    if (mWritePos != 0)
+        resizeBufferAndShuffleChannels(&mRecorderBuffer, numFrames);
+    else
+        mRecorderBuffer.setNumFrames(numFrames);
+
+    if (shrinkToFit)
+        mRecorderBuffer.shrinkToFit();
+}
+
+ci::audio::BufferRef BufferToWaveRecorderNode::getRecordedCopy() const
+{
+    // first grab the number of current frames, which may be increasing as the recording continues.
+    size_t numFrames = mWritePos;
+    mCopiedBuffer->setSize(numFrames, mRecorderBuffer.getNumChannels());
+
+    mCopiedBuffer->copy(mRecorderBuffer, numFrames);
+    return mCopiedBuffer;
+}
+
+void BufferToWaveRecorderNode::writeToFile(const ci::fs::path &filePath, ci::audio::SampleType sampleType)
+{
+    size_t currentWritePos = mWritePos;
+    ci::audio::BufferRef copiedBuffer = getRecordedCopy();
+
+    ci::audio::TargetFileRef target = ci::audio::TargetFile::create(filePath, getSampleRate(), getNumChannels(), sampleType);
+    target->write(copiedBuffer.get(), currentWritePos);
+}
+
+uint64_t BufferToWaveRecorderNode::getLastOverrun()
+{
+    uint64_t result = mLastOverrun;
+    mLastOverrun = 0;
+    return result;
+}
+
+
+void BufferToWaveRecorderNode::process(ci::audio::Buffer *buffer)
+{
+    size_t writePos = mWritePos;
+    size_t numWriteFrames = buffer->getNumFrames();
+
+    if ( writePos == 0 ){
+        RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_START, 0, 0, 0 );
+        mRingBuffer.write( &msg, 1 );
+
+        // reset everything
+        mChunkMinAudioVal = kMaxAudioVal;
+        mChunkMaxAudioVal = kMinAudioVal;
+        mChunkSampleCounter = 0;
+        mChunkIndex = 0;
+        mEnvRamp = 0.0f;
+    }
+
+    // if buffer has too many frames (because we're nearly at the end or at the end ) 
+    // of mRecoderBuffer then numWriteFrames becomes the number of samples left to 
+    // fill mRecorderBuffer. Which is 0 if the buffer is at the end.
+    if ( writePos + numWriteFrames > mRecorderBuffer.getNumFrames() )
+        numWriteFrames = mRecorderBuffer.getNumFrames() - writePos;
+
+    if ( numWriteFrames <= 0 )
+        return;
+
+
+    // apply envelope to the buffer at the edges to avoid clicks 
+    if ( writePos < mEnvRampLen ){ // beginning of wave 
+        for ( size_t i = 0; i < std::min( mEnvRampLen, numWriteFrames ); i++ ){
+            buffer->getData()[i] *= mEnvRamp;
+            mEnvRamp += mEnvRampRate;
+            if ( mEnvRamp > 1.0f )
+                mEnvRamp = 1.0f;
+        }
+    }
+    else if ( writePos + numWriteFrames > mEnvDecayStart ){ // end of wave 
+        for ( size_t i = std::max( writePos, mEnvDecayStart ) - writePos; i < numWriteFrames; i++ ){
+            buffer->getData()[i] *= mEnvRamp;
+            mEnvRamp -= mEnvRampRate;
+            if ( mEnvRamp < 0.0f )
+                mEnvRamp = 0.0f;
+        }
+    }
+
+
+    mRecorderBuffer.copyOffset(*buffer, numWriteFrames, writePos, 0);
+
+    if ( numWriteFrames < buffer->getNumFrames() )
+        mLastOverrun = getContext()->getNumProcessedFrames();
+
+    /* find max and minimum of this buffer */
+    for ( size_t i = 0; i < numWriteFrames; i++ ){
+
+        if ( buffer->getData()[i] < mChunkMinAudioVal ){
+            mChunkMinAudioVal = buffer->getData()[i];
+        }
+
+        if ( buffer->getData()[i] > mChunkMaxAudioVal ){
+            mChunkMaxAudioVal = buffer->getData()[i];
+        }
+
+        if ( mChunkSampleCounter >= mNumSamplesPerChunk              // if collected enough samples 
+            || writePos + i >= mRecorderBuffer.getNumFrames() - 1 ){ // or at the end of recorder buffer 
+            // send chunk to GUI
+            size_t chunkIndex = mChunkIndex.fetch_add( 1 );
+
+            RecordWaveMsg msg = makeRecordWaveMsg( Command::WAVE_CHUNK, chunkIndex, mChunkMinAudioVal, mChunkMaxAudioVal );
+            mRingBuffer.write( &msg, 1 );
+            
+            // reset chunk info 
+            mChunkMinAudioVal = kMaxAudioVal;
+            mChunkMaxAudioVal = kMinAudioVal;
+            mChunkSampleCounter = 0;
+        }
+        else{
+            mChunkSampleCounter++;
+        }
+    }
+
+    // check if write position has been reset by the GUI thread, if not write new value
+    const size_t writePosNew = writePos + numWriteFrames;
+    mWritePos.compare_exchange_strong( writePos, writePosNew );
+
+}
+
+
+const float BufferToWaveRecorderNode::kMinAudioVal = -1.0f;
+const float BufferToWaveRecorderNode::kMaxAudioVal = 1.0f; 
+const float BufferToWaveRecorderNode::kRampTime = 0.02;
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/Chunk.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,84 @@
+#include "Chunk.h"
+#include "DrawInfo.h"
+
+
+#include "cinder/gl/gl.h"
+
+
+Chunk::Chunk( size_t index ) :
+    mIndex( index ),
+    mAudioTop(0.0f),
+    mAudioBottom(0.0f)
+    {}
+
+
+void update( const DrawInfo& di )
+{
+
+}
+
+void Chunk::update( const DrawInfo &di )
+{
+    using namespace ci;
+    /* if resetting animate the chunks to shrink to 0 size */
+    if ( mResetting ){
+        if ( mAnimate > 0.0f ){
+            mAnimate -= 0.1f;
+            if ( mAnimate <= 0.0f ){
+                mAnimate = 0.0f;
+                mResetting = false;
+                mAudioTop = 0.0f;
+                mAudioBottom = 0.0f;
+            }
+        }
+    }
+    /* animate makes the chunks pop nicely when they are created */
+    else if ( mAnimate < 1.0f ){
+        mAnimate += 0.3333f; // in three (namely 1/0.333) steps
+        if ( mAnimate > 1.0f ){ // clip to one
+            mAnimate = 1.0f;
+        }
+    }
+
+    mX = di.flipX( 1 + (mIndex * (2 + kWidth)) ); // FIXME this should happen only once when resized 
+}
+
+void Chunk::draw( const DrawInfo& di, ci::gl::BatchRef &batch ){
+    using namespace ci;
+	
+    gl::pushModelMatrix();
+
+    const float chunkHeight = mAnimate * mAudioTop * di.getMaxChunkHeight();
+
+    // place the chunk in the right position brigns back the y of chunkHeight/2 so
+    // so that after scaling the wave is still centered at the wave center 
+    gl::translate( mX, di.getWaveCenterY() - ( chunkHeight / 2 ) - 1 );
+
+    // FIXME todo use max between top and bottom
+    // scale according to audio amplitude 
+    gl::scale( 1.0f, chunkHeight );
+    batch->draw();
+    
+    
+    gl::popModelMatrix();
+}
+
+
+void Chunk::drawBar( const DrawInfo& di, ci::gl::BatchRef &batch ){
+	using namespace ci;
+
+    gl::pushModelMatrix();
+
+    const float barHeight = di.getSelectionBarHeight();
+
+    gl::translate( mX, di.getWaveCenterY() - ( barHeight / 2 ) - 1 );
+    gl::scale( 1.0f, barHeight );
+
+    batch->draw();
+
+    gl::popModelMatrix();
+}
+
+
+const float Chunk::kWidth = 7.0f;
+const float Chunk::kHalfWidth = 3.5f;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/CollidoscopeApp.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,454 @@
+#include "cinder/app/App.h"
+#include "cinder/app/RendererGl.h"
+#include "cinder/gl/gl.h"
+#include "cinder/Exception.h"
+
+
+#include "Config.h"
+#include "Wave.h"
+#include "DrawInfo.h"
+#include "Log.h"
+#include "AudioEngine.h"
+#include "Oscilloscope.h"
+#include "Messages.h"
+#include "MIDI.h"
+
+using namespace ci;
+using namespace ci::app;
+
+using namespace std;
+
+
+class CollidoscopeApp : public App {
+  public:
+
+	void setup() override;
+    void setupGraphics();
+
+    void receiveCommands();
+
+	void keyDown( KeyEvent event ) override;
+	void update() override;
+	void draw() override;
+    void resize() override;
+
+	Config mConfig;
+    collidoscope::MIDI mMIDI;
+    AudioEngine mAudioEngine;
+	
+    array< shared_ptr< Wave >, NUM_WAVES > mWaves;
+    array< shared_ptr< DrawInfo >, NUM_WAVES > mDrawInfos;
+    array< shared_ptr< Oscilloscope >, NUM_WAVES > mOscilloscopes;
+    // buffers to read the wave messages as a new wave gets recorded 
+    array< RecordWaveMsg*, NUM_WAVES> mRecordWaveMessageBuffers;
+    array< vector< CursorTriggerMsg >, NUM_WAVES > mCursorTriggerMessagesBuffers;
+
+    double mSecondsPerChunk;
+
+    ~CollidoscopeApp();
+
+};
+
+
+void CollidoscopeApp::setup()
+{
+    hideCursor();
+    /* setup is logged: setup steps and errors */
+    
+    /*try {
+        mConfig.loadFromFile( "./collidoscope_config.xml" );
+    }
+    catch ( const Exception &e ){
+        logError( string("Exception loading config from file:") + e.what() );
+    }*/
+
+    // setup buffers to read messages from audio thread 
+    for ( size_t i = 0; i < NUM_WAVES; i++ ){
+        mRecordWaveMessageBuffers[i] = new RecordWaveMsg[mConfig.getNumChunks()];
+        mCursorTriggerMessagesBuffers[i].reserve( mConfig.getCursorTriggerMessageBufSize() );
+    }
+
+    mAudioEngine.setup( mConfig );
+
+    setupGraphics();
+
+    mSecondsPerChunk = mConfig.getWaveLen() / mConfig.getNumChunks();
+
+    try {
+        mMIDI.setup( mConfig );
+    }
+    catch ( const collidoscope::MIDIException &e ){
+        logError( string( "Exception opening MIDI input device: " ) + e.getMessage() );
+    }
+
+}
+
+void CollidoscopeApp::setupGraphics()
+{
+    for ( size_t i = 0; i < NUM_WAVES; i++ ){
+
+        mDrawInfos[i] = make_shared< DrawInfo >( i );
+        mWaves[i] = make_shared< Wave >(mConfig.getNumChunks(), mConfig.getWaveSelectionColor(i) );
+        mOscilloscopes[i] = make_shared< Oscilloscope >( mAudioEngine.getAudioOutputBuffer( i ).getNumFrames() / mConfig.getOscilloscopeNumPointsDivider() );
+
+    }
+}
+
+void CollidoscopeApp::keyDown( KeyEvent event )
+{
+    char c = event.getChar();
+
+    switch (c){
+    case 'r' : 
+        mAudioEngine.record( 0 );
+        mAudioEngine.record( 1 );
+        break;
+
+    case 'w': {
+        mWaves[0]->getSelection().setSize(mWaves[0]->getSelection().getSize() + 1);
+
+        size_t numSelectionChunks = mWaves[0]->getSelection().getSize();
+        // how many samples in one selection ?
+        size_t selectionSize = numSelectionChunks * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
+
+        mAudioEngine.setSelectionSize(0, selectionSize);
+    };
+        break;
+
+    case 'e': {
+        mWaves[1]->getSelection().setSize(mWaves[1]->getSelection().getSize() + 1);
+
+        size_t numSelectionChunks = mWaves[1]->getSelection().getSize();
+        // how many samples in one selection ?
+        size_t selectionSize = numSelectionChunks * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
+
+        mAudioEngine.setSelectionSize(1, selectionSize);
+    };
+        break;
+
+    case 's': {
+
+        mWaves[0]->getSelection().setSize( mWaves[0]->getSelection().getSize() - 1 );
+
+        size_t selectionSize = mWaves[0]->getSelection().getSize() *(mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
+        mAudioEngine.setSelectionSize( 0, selectionSize );
+    };
+        break;
+
+    case 'd': {
+
+        for( size_t waveIdx = 0; waveIdx < NUM_WAVES; waveIdx++){
+            size_t selectionStart = mWaves[waveIdx]->getSelection().getStart();
+            mWaves[waveIdx]->getSelection().setStart( selectionStart + 1 );
+
+            selectionStart = mWaves[waveIdx]->getSelection().getStart();
+            mAudioEngine.setSelectionStart( waveIdx, selectionStart * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
+        }
+    };
+
+        break;
+
+    case 'a': {
+        size_t selectionStart = mWaves[0]->getSelection().getStart();
+        
+        if ( selectionStart == 0 )
+            return;
+
+        mWaves[0]->getSelection().setStart( selectionStart - 1 );
+
+        selectionStart = mWaves[0]->getSelection().getStart();
+
+        mAudioEngine.setSelectionStart( 0, selectionStart * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
+    };
+        break;
+
+
+    case 'p':
+
+        mWaves[0]->setCursorPos(  4, mWaves[0]->getSelection().getStart(), *mDrawInfos[0] ) ;
+        break;
+
+    case 'f':
+        setFullScreen( !isFullScreen() );
+        break;
+
+    case ' ': { 
+        static bool isOn = false;
+        isOn = !isOn;
+        if ( isOn ){
+            mAudioEngine.loopOn( 0 );
+            mAudioEngine.loopOn( 1 );
+        }
+        else{
+            mAudioEngine.loopOff( 0 );
+            mAudioEngine.loopOff( 1 );
+        }
+    };
+        break;
+
+	case 'm' :
+		mAudioEngine.setGrainDurationCoeff(0, 8);
+		break;
+
+    case 'n': {
+        mAudioEngine.setGrainDurationCoeff( 0, 1 );
+    };
+        break;
+
+    case '9': {
+        int c = mWaves[0]->getSelection().getParticleSpread();
+        if ( c == 1 )
+            return;
+        else
+            c -= 1;
+
+        mAudioEngine.setGrainDurationCoeff( 0, c );
+        mWaves[0]->getSelection().setParticleSpread( float( c ) );
+        mAudioEngine.setGrainDurationCoeff( 1, c );
+        mWaves[1]->getSelection().setParticleSpread( float( c ) );
+    }; break;
+
+    case '0': {
+        int c = mWaves[0]->getSelection().getParticleSpread();
+        if ( c == 8 )
+            return;
+        else
+            c += 1;
+
+        mAudioEngine.setGrainDurationCoeff( 0, c );
+        mWaves[0]->getSelection().setParticleSpread( float( c ) );
+    }; break;
+
+    }
+
+}
+
+void CollidoscopeApp::update()
+{
+    // check incoming commands 
+    receiveCommands();
+
+    // check new wave chunks from recorder buffer 
+    for ( size_t i = 0; i < NUM_WAVES; i++ ){
+        size_t availableRead = mAudioEngine.getRecordWaveAvailable( i );
+        mAudioEngine.readRecordWave( i, mRecordWaveMessageBuffers[i], availableRead );
+
+        for ( size_t msgIndex = 0; msgIndex < availableRead; msgIndex++ ){
+            const RecordWaveMsg & msg = mRecordWaveMessageBuffers[i][msgIndex];
+
+            if ( msg.cmd == Command::WAVE_CHUNK ){
+                mWaves[i]->setChunk( msg.index, msg.arg1, msg.arg2 );
+            }
+            else if ( msg.cmd == Command::WAVE_START ){
+                mWaves[i]->reset( true ); // reset only chunks but leave selection 
+            }
+            
+        }
+    }
+
+    // check if new cursors have been triggered 
+    for ( size_t i = 0; i < NUM_WAVES; i++ ){
+        
+        mAudioEngine.checkCursorTriggers( i, mCursorTriggerMessagesBuffers[i] );
+        for ( auto & trigger : mCursorTriggerMessagesBuffers[i] ){
+            const int nodeID = trigger.synthID;
+
+            switch ( trigger.cmd ){
+
+            case Command::TRIGGER_UPDATE: {
+                mWaves[i]->setCursorPos( nodeID, mWaves[i]->getSelection().getStart(), *mDrawInfos[i] );
+            };
+                break;
+
+            case Command::TRIGGER_END: {
+                mWaves[i]->removeCursor( nodeID );
+            };
+                break;
+
+            }
+            
+        }
+        mCursorTriggerMessagesBuffers[i].clear();
+    }
+
+    // update cursors 
+    for ( size_t i = 0; i < NUM_WAVES; i++ ){
+        mWaves[i]->update( mSecondsPerChunk, *mDrawInfos[i] );
+    }
+    
+    // update oscilloscope 
+
+    for ( size_t i = 0; i < NUM_WAVES; i++ ){
+        const audio::Buffer &audioOutBuffer = mAudioEngine.getAudioOutputBuffer( i );
+        // one oscilloscope sample 
+
+        for ( size_t j = 0; j < mOscilloscopes[i]->getNumPoints(); j++ ){
+            mOscilloscopes[i]->setPoint( j, audioOutBuffer.getData()[j], *mDrawInfos[i] );
+        }
+    }
+
+    
+    
+}
+
+void CollidoscopeApp::draw()
+{
+	gl::clear( Color( 0, 0, 0 ) );
+
+    for ( int i = 0; i < NUM_WAVES; i++ ){
+        if ( i == 1 ){
+            /* for the upper wave flip the x over the center of the screen which is
+            the composition of rotate on the y-axis and translate by -screenwidth*/
+            gl::pushModelMatrix();
+            gl::rotate( float(M_PI), ci::vec3( 0, 1, 0 ) );
+            gl::translate( float( -getWindowWidth() ), 0.0f );
+            mOscilloscopes[i]->draw();
+            mWaves[i]->draw( *mDrawInfos[i] );
+            gl::popModelMatrix();
+        }
+        else{
+
+            mOscilloscopes[i]->draw();
+            mWaves[i]->draw( *mDrawInfos[i] );
+        }
+    }
+}
+
+void CollidoscopeApp::resize()
+{
+    App::resize();
+    
+    for ( int i = 0; i < NUM_WAVES; i++ ){
+        // reset the drawing information with the new windows size and same shrink factor  
+        mDrawInfos[i]->reset( getWindow()->getBounds(), 3.0f / 5.0f );
+
+        /* reset the oscilloscope points to zero */
+        for ( int j = 0; j < mOscilloscopes[i]->getNumPoints(); j++ ){
+            mOscilloscopes[i]->setPoint(j, 0.0f, *mDrawInfos[i] );
+        }
+    }
+}
+
+
+
+void CollidoscopeApp::receiveCommands()
+{
+    // check new midi messages 
+    static std::vector<collidoscope::MIDIMessage> midiMessages;
+    mMIDI.checkMessages( midiMessages );
+
+
+    for ( auto &m : midiMessages ){
+        
+        const size_t waveIdx = mConfig.getWaveForMIDIChannel( m.getChannel() );
+        if ( waveIdx >= NUM_WAVES )
+            continue;
+
+        if ( m.getVoice() == collidoscope::MIDIMessage::Voice::eNoteOn ){
+            int midiNote = m.getData_1();
+            mAudioEngine.noteOn( waveIdx, midiNote );
+        }
+        else if ( m.getVoice() == collidoscope::MIDIMessage::Voice::eNoteOff ){
+            int midiNote = m.getData_1();
+            mAudioEngine.noteOff( waveIdx, midiNote );
+        } 
+        else if ( m.getVoice() == collidoscope::MIDIMessage::Voice::ePitchBend ){
+            const uint16_t MSB = m.getData_2() << 7;
+            uint16_t value = m.getData_1(); // LSB 
+
+            value |= MSB;
+            
+
+            // value ranges from 0 to 1050. check boundaries in case sensor gives bad values 
+            if ( value > 149 ){ // FIXME pareametrizer 
+                continue;
+            }
+
+            size_t startChunk = value;
+
+            const size_t selectionSizeBeforeStartUpdate = mWaves[waveIdx]->getSelection().getSize();
+            mWaves[waveIdx]->getSelection().setStart( startChunk );
+
+            mAudioEngine.setSelectionStart( waveIdx, startChunk * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
+            
+            const size_t newSelectionSize = mWaves[waveIdx]->getSelection().getSize();
+            if ( selectionSizeBeforeStartUpdate != newSelectionSize ){
+                mAudioEngine.setSelectionSize( waveIdx, newSelectionSize * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks()) );
+            }
+
+
+        }
+        else if ( m.getVoice() == collidoscope::MIDIMessage::Voice::eControlChange ){
+
+            switch ( m.getData_1() ){ //controller number 
+            case 1: { // selection size 
+                const size_t midiVal = m.getData_2();
+                size_t numSelectionChunks = ci::lmap<size_t>( midiVal, 0, 127, 1, mConfig.getMaxSelectionNumChunks() );
+
+                mWaves[waveIdx]->getSelection().setSize( numSelectionChunks );
+
+                // how many samples in one selection ?
+				size_t selectionSize = mWaves[waveIdx]->getSelection().getSize() * (mConfig.getWaveLen() * mAudioEngine.getSampleRate() / mConfig.getNumChunks());
+                mAudioEngine.setSelectionSize( waveIdx, selectionSize );
+
+            };
+                break;
+
+            case 4: { // loop on off
+                unsigned char midiVal = m.getData_2();
+
+                if ( midiVal > 0 )
+                    mAudioEngine.loopOn( waveIdx );
+                else
+                    mAudioEngine.loopOff( waveIdx );
+            };
+                break;
+
+            case 5: // trigger record
+                mAudioEngine.record( waveIdx );
+                break;
+
+            case 2: { // duration 
+                const double midiVal = m.getData_2(); // 0-127
+                const double coeff = ci::lmap<double>( midiVal, 0.0, 127, 1.0, mConfig.getMaxGrainDurationCoeff() );
+                mAudioEngine.setGrainDurationCoeff( waveIdx, coeff );
+                mWaves[waveIdx]->getSelection().setParticleSpread( float( coeff ) );
+            };
+                break;
+
+            case 7: { // filter 
+                const double midiVal = m.getData_2(); // 0-127
+                const double minCutoff = mConfig.getMinFilterCutoffFreq();
+                const double maxCutoff = mConfig.getMaxFilterCutoffFreq();
+                const double cutoff = pow( maxCutoff / 200., midiVal / 127.0 ) * minCutoff;
+                mAudioEngine.setFilterCutoff( waveIdx, cutoff );
+                const float alpha = ci::lmap<double>( midiVal, 0.0f, 127.0f, 0.f, 1.f );
+                mWaves[waveIdx]->setselectionAlpha( alpha );
+            };
+                break;
+
+
+                
+            }
+        }
+    }
+
+    midiMessages.clear();
+}
+
+
+
+CollidoscopeApp::~CollidoscopeApp()
+{
+    for ( int chan = 0; chan < NUM_WAVES; chan++ ){
+        /* delete the array for wave messages from audio thread */
+        delete[] mRecordWaveMessageBuffers[chan];
+    }
+}
+
+CINDER_APP( CollidoscopeApp, RendererGl, [] ( App::Settings *settings) {
+        settings->setWindowSize( 1920, 1080 );
+        settings->setMultiTouchEnabled( false );
+        settings->disableFrameRate();
+
+} )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/Config.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,68 @@
+#include "Config.h"
+
+
+#include "cinder/Exception.h"
+#include "boost/algorithm/string/trim.hpp"
+
+using ci::DataSourceRef;
+using ci::XmlTree;
+using ci::loadFile;
+
+
+Config::Config() :
+    mAudioInputDeviceKey( "" ),
+    mNumChunks(150),
+    mWaveLen(2.0)
+{
+
+}
+
+
+void Config::loadFromFile( std::string&& path )
+{
+    try {
+        XmlTree doc( loadFile( path ) );
+
+        XmlTree collidoscope = doc.getChild( "collidoscope" );
+
+        // audio input device 
+        mAudioInputDeviceKey = collidoscope.getChild( "audioInputDeviceKey" ).getValue();
+        boost::trim( mAudioInputDeviceKey );
+
+        // wave len in seconds 
+        std::string waveLenStr = collidoscope.getChild("wave_len").getValue();
+        boost::trim(waveLenStr);
+        mWaveLen = ci::fromString<double>(waveLenStr);
+
+        // channel for each wave 
+        XmlTree waves = collidoscope.getChild( "waves" );
+
+        for ( int i = 0; i < NUM_WAVES; i++ ){
+            for ( auto &wave : waves.getChildren() ){
+                int id = ci::fromString<int>( wave->getAttribute( "id" ) );
+                if ( id == i ){
+                    parseWave( *wave, id );
+                    break;
+                }
+            }
+        }
+
+    }
+    catch ( std::exception &e ) {
+        throw ci::Exception( e.what() );
+    }
+    
+
+
+}
+
+// thows exception captured in loadFromFile 
+void Config::parseWave( const XmlTree &wave, int id )
+{
+    // midi channel 
+    std::string midiChannelStr = wave.getChild( "midiChannel" ).getValue();
+    boost::trim( midiChannelStr );
+
+    mMidiChannels[id] = ci::fromString<size_t>( midiChannelStr );
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/Log.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,36 @@
+
+#include "cinder/Log.h"
+
+bool fileLoggerCreated = false;
+
+void logError( const std::string &errorMsg )
+{
+    using namespace ci::log;
+
+    if ( !fileLoggerCreated ){
+        makeLogger<ci::log::LoggerFile>( "./collidoscope_error.log" );
+        fileLoggerCreated = true;
+    }
+
+    LogManager *log = LogManager::instance();
+
+    Metadata logMeta;
+    logMeta.mLevel = LEVEL_ERROR;
+
+    log->write( logMeta, errorMsg );
+
+}
+
+void logInfo( const std::string &infoMsg )
+{
+#ifdef _DEBUG
+    using namespace ci::log;
+
+    LogManager *log = LogManager::instance();
+
+    Metadata logMeta;
+    logMeta.mLevel = LEVEL_INFO;
+
+    log->write( logMeta, infoMsg );
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/MIDI.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,134 @@
+#include "MIDI.h"
+#include "Config.h"
+
+
+collidoscope::MIDI::MIDI()
+{
+}
+
+
+collidoscope::MIDI::~MIDI()
+{
+    // FIXME check cause destructor of RTInput might throw 
+    for ( auto & input : mInputs )
+        input->closePort();
+}
+
+void collidoscope::MIDI::RtMidiInCallback( double deltatime, std::vector<unsigned char> *message, void *userData )
+{
+    collidoscope::MIDI* midi = ((collidoscope::MIDI*)userData);
+    std::lock_guard< std::mutex > lock( midi->mMutex );
+
+    MIDIMessage msg = midi->parseRtMidiMessage( message );
+
+    switch ( msg.getVoice() ){
+    case MIDIMessage::Voice::ePitchBend:
+        midi->mPitchBendMessages[msg.getChannel()] = msg;
+        break;
+
+    case MIDIMessage::Voice::eControlChange:
+        if ( msg.getChannel() == 7 ) // FIXME no harcoded 
+            midi->mFilterMessages[msg.getChannel()] = msg;
+        else
+            midi->mMIDIMessages.push_back( msg );
+        break;
+
+    default:
+        midi->mMIDIMessages.push_back( msg );
+        break;
+    }
+}
+
+void collidoscope::MIDI::setup( const Config& config )
+{
+    unsigned int numPorts = 0; 
+
+    try {
+        RtMidiIn in;
+        numPorts = in.getPortCount();
+    }
+    catch ( RtMidiError &error ) {
+        throw MIDIException( error.getMessage() );
+    }
+
+    if ( numPorts == 0 ){
+        throw MIDIException(" no MIDI input found ");
+    }
+
+    for ( unsigned int portNum = 0; portNum < numPorts; portNum++ ) {
+        try {
+            std::unique_ptr< RtMidiIn > input ( new RtMidiIn() );
+            input->openPort( portNum, "Collidoscope Input" );
+            input->setCallback( &RtMidiInCallback, this );
+            mInputs.push_back( std::move(input) );
+
+        }
+        catch ( RtMidiError &error ) {
+            throw MIDIException( error.getMessage() );
+        }
+    }
+}
+
+
+void collidoscope::MIDI::checkMessages( std::vector<MIDIMessage> & midiMessages )
+{
+    std::lock_guard<std::mutex> lock( mMutex );
+    midiMessages.swap( mMIDIMessages );
+    
+    for ( int i = 0; i < NUM_WAVES; i++ ){
+        if ( mPitchBendMessages[i].mVoice != MIDIMessage::Voice::eIgnore ){
+            midiMessages.push_back( mPitchBendMessages[i] );
+            mPitchBendMessages[i].mVoice = MIDIMessage::Voice::eIgnore;
+        }
+
+        if ( mFilterMessages[i].mVoice != MIDIMessage::Voice::eIgnore ){
+            midiMessages.push_back( mFilterMessages[i] );
+            mFilterMessages[i].mVoice = MIDIMessage::Voice::eIgnore;
+        }
+    }
+}
+
+// only call this function when the size of mRtMidiMessage != 0
+collidoscope::MIDIMessage collidoscope::MIDI::parseRtMidiMessage( std::vector<unsigned char> *rtMidiMessage )
+{
+    collidoscope::MIDIMessage msg;
+
+    size_t numBytes = rtMidiMessage->size();
+    
+    // voice is the 4 most significant bits
+    unsigned char voice = (*rtMidiMessage)[0] >> 4;
+
+    switch ( voice ){
+    case 0x9 :
+        msg.mVoice = MIDIMessage::Voice::eNoteOn;
+        break;
+
+    case 0x8:
+        msg.mVoice = MIDIMessage::Voice::eNoteOff;
+        break;
+
+    case 0xB:
+        msg.mVoice = MIDIMessage::Voice::eControlChange;
+        break;
+
+    case 0xE:
+        msg.mVoice = MIDIMessage::Voice::ePitchBend;
+        break;
+
+    default:
+        msg.mVoice = MIDIMessage::Voice::eIgnore;
+        return msg;
+    }
+
+    // channel is 4 less significant bits
+    unsigned char channel = (*rtMidiMessage)[0] & 0x0f;
+    msg.mChannel = channel;
+    
+    //data byte 1
+    msg.mData1 = (*rtMidiMessage)[1];
+
+    // data byte 2 if it exists 
+    msg.mData2 = (numBytes == 3 ? (*rtMidiMessage)[2] : 0);
+
+    return msg;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/PGranularNode.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,185 @@
+#include "PGranularNode.h"
+
+#include "cinder/audio/Context.h"
+
+#include "cinder/Rand.h"
+
+// generate random numbers from 0 to max 
+// it's passed to PGranular to randomize the phase offset at grain creation 
+struct RandomGenerator
+{
+
+    RandomGenerator( size_t max ) : mMax( max )
+    {}
+
+    size_t operator()() const {
+        return ci::Rand::randUint( mMax );
+    }
+
+    size_t mMax;
+};
+// FIXME maybe use only one random gen 
+
+PGranularNode::PGranularNode( ci::audio::Buffer *grainBuffer, CursorTriggerMsgRingBuffer &triggerRingBuffer ) :
+    Node( Format().channels( 1 ) ),
+    mGrainBuffer(grainBuffer),
+    mSelectionStart( 0 ),
+    mSelectionSize( 0 ),
+    mGrainDurationCoeff( 1 ),
+    mTriggerRingBuffer( triggerRingBuffer ),
+    mNoteMsgRingBufferPack( 128 )
+{
+    for ( int i = 0; i < kMaxVoices; i++ ){
+        mMidiNotes[i] = kNoMidiNote;
+
+    }
+}
+
+
+PGranularNode::~PGranularNode()
+{
+}
+
+void PGranularNode::initialize()
+{
+    mTempBuffer = std::make_shared< ci::audio::Buffer >( getFramesPerBlock() );
+
+    mRandomOffset.reset( new RandomGenerator( getSampleRate() / 100 ) ); // divided by 100 corresponds to times 0.01 in the time domain 
+
+    /* create the PGranular object for looping */
+    mPGranularLoop.reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, -1 ) );
+
+    /* create the PGranular object for notes */
+    for ( size_t i = 0; i < kMaxVoices; i++ ){
+        mPGranularNotes[i].reset( new collidoscope::PGranular<float, RandomGenerator, PGranularNode>( mGrainBuffer->getData(), mGrainBuffer->getNumFrames(), getSampleRate(), *mRandomOffset, *this, i ) );
+    }
+
+}
+
+void PGranularNode::process (ci::audio::Buffer *buffer )
+{
+    // only update PGranular if the atomic value has changed from the previous time
+
+    const boost::optional<size_t> selectionSize = mSelectionSize.get();
+    if ( selectionSize ){
+        mPGranularLoop->setSelectionSize( *selectionSize );
+        for ( size_t i = 0; i < kMaxVoices; i++ ){
+            mPGranularNotes[i]->setSelectionSize( *selectionSize );
+        }
+    }
+
+    const boost::optional<size_t> selectionStart = mSelectionStart.get();
+    if ( selectionStart ){
+        mPGranularLoop->setSelectionStart( *selectionStart );
+        for ( size_t i = 0; i < kMaxVoices; i++ ){
+            mPGranularNotes[i]->setSelectionStart( *selectionStart );
+        }
+    }
+
+    const boost::optional<double> grainDurationCoeff = mGrainDurationCoeff.get();
+    if ( grainDurationCoeff ){
+        mPGranularLoop->setGrainsDurationCoeff( *grainDurationCoeff );
+        for ( size_t i = 0; i < kMaxVoices; i++ ){
+            mPGranularNotes[i]->setGrainsDurationCoeff( *grainDurationCoeff );
+        }
+    }
+
+    // check messages to start/stop notes or loop 
+    size_t availableRead = mNoteMsgRingBufferPack.getBuffer().getAvailableRead();
+    mNoteMsgRingBufferPack.getBuffer().read( mNoteMsgRingBufferPack.getExchangeArray(), availableRead );
+    for ( size_t i = 0; i < availableRead; i++ ){
+        handleNoteMsg( mNoteMsgRingBufferPack.getExchangeArray()[i] );
+    }
+
+    // process loop if not idle 
+    if ( !mPGranularLoop->isIdle() ){
+        /* buffer is one channel only so I can use getData */
+        mPGranularLoop->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
+    }
+
+    // process notes if not idle 
+    for ( size_t i = 0; i < kMaxVoices; i++ ){
+        if ( mPGranularNotes[i]->isIdle() )
+            continue;
+
+        mPGranularNotes[i]->process( buffer->getData(), mTempBuffer->getData(), buffer->getSize() );
+
+        if ( mPGranularNotes[i]->isIdle() ){
+            // this note became idle so update mMidiNotes
+            mMidiNotes[i] = kNoMidiNote;
+        }
+            
+    }
+}
+
+void PGranularNode::operator()( char msgType, int ID ) {
+
+    switch ( msgType ){
+    case 't':  { // trigger 
+        CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_UPDATE, ID ); // put ID 
+        mTriggerRingBuffer.write( &msg, 1 );
+    };
+        break;
+
+    case 'e': // end envelope 
+        CursorTriggerMsg msg = makeCursorTriggerMsg( Command::TRIGGER_END, ID ); // put ID 
+        mTriggerRingBuffer.write( &msg, 1 );
+        break;
+    }
+
+    
+}
+
+void PGranularNode::handleNoteMsg( const NoteMsg &msg )
+{
+    switch ( msg.cmd ){
+    case Command::NOTE_ON: {
+        bool synthFound = false;
+
+        for ( int i = 0; i < kMaxVoices; i++ ){
+            // note was already on, so re-attack
+            if ( mMidiNotes[i] == msg.midiNote ){
+                mPGranularNotes[i]->noteOn( msg.rate );
+                synthFound = true;
+                break;
+            }
+        }
+
+        if ( !synthFound ){
+            // then look for a free synth 
+            for ( int i = 0; i < kMaxVoices; i++ ){
+
+                if ( mMidiNotes[i] == kNoMidiNote ){
+                    mPGranularNotes[i]->noteOn( msg.rate );
+                    mMidiNotes[i] = msg.midiNote;
+                    synthFound = true;
+                    break;
+                }
+            }
+        }
+    };
+        break;
+
+    case Command::NOTE_OFF: {
+        for ( int i = 0; i < kMaxVoices; i++ ){
+            if ( !mPGranularNotes[i]->isIdle() && mMidiNotes[i] == msg.midiNote ){
+                mPGranularNotes[i]->noteOff();
+                break;
+            }
+        }
+    };
+        break;
+
+    case Command::LOOP_ON: {
+        mPGranularLoop->noteOn( 1.0 );
+    };
+        break;
+
+    case Command::LOOP_OFF: {
+        mPGranularLoop->noteOff();
+    };
+        break;
+    default:
+        break;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/ParticleController.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,136 @@
+#include "ParticleController.h"
+#include "cinder/Rand.h"
+
+#include <type_traits>
+
+using namespace ci;
+using std::list;
+
+ParticleController::ParticleController() :
+mNumParticles( 0 )
+
+{
+    mParticles.assign( kMaxParticles, Particle() );
+    mParticlePositions.assign( kMaxParticles, vec2( -1, -1 ) );
+
+    mParticleVbo = gl::Vbo::create( GL_ARRAY_BUFFER, mParticlePositions, GL_DYNAMIC_DRAW );
+
+    geom::BufferLayout particleLayout;
+    particleLayout.append( geom::Attrib::POSITION, 2, sizeof( vec2 ), 0 );
+
+    auto mesh = gl::VboMesh::create( mParticlePositions.size(), GL_POINTS, { { particleLayout, mParticleVbo } } );
+
+#if ! defined( CINDER_GL_ES )
+    auto glsl = gl::GlslProg::create( gl::GlslProg::Format()
+        .vertex( CI_GLSL( 150,
+            uniform mat4	ciModelViewProjection;
+            in vec4			ciPosition;
+
+            void main( void ) {
+                gl_Position = ciModelViewProjection * ciPosition;
+                gl_PointSize = 1.0;
+            }
+            ) )
+        .fragment( CI_GLSL( 150,
+            out vec4 oColor;
+
+            void main( void ) {
+                oColor = vec4( 1.0f, 1.0f, 1.0f, 1.0f );
+            }
+        ) )
+    );
+
+    mParticleBatch = gl::Batch::create( mesh, glsl );
+
+#else
+    auto glsl = gl::GlslProg::create( gl::GlslProg::Format()
+        .vertex( CI_GLSL( 100,
+            uniform mat4	ciModelViewProjection;
+            attribute vec4			ciPosition;
+
+            void main( void ) {
+                gl_Position = ciModelViewProjection * ciPosition;
+                gl_PointSize = 1.0;
+            }
+        ) )
+        .fragment( CI_GLSL( 100,
+            precision highp float;
+
+            void main( void ) {
+                gl_FragColor = vec4( 1, 1, 1, 1 );
+            }
+        ) ) 
+    );
+
+    mParticleBatch = gl::Batch::create( mesh, glsl );
+#endif
+
+
+}
+
+void ParticleController::updateParticles()
+{
+    for ( size_t i = 0; i < mNumParticles; i++ ){
+
+        Particle &particle = mParticles[i];
+        vec2 &pos = mParticlePositions[i];
+
+        particle.mAge++;
+
+
+        if ( (!particle.mFlyOver && particle.mAge > particle.mLifespan) 
+                || (particle.mFlyOver && particle.mAge >= 300) ){
+            // dispose particle 
+            mParticles[i] = mParticles[mNumParticles - 1];
+            mParticlePositions[i] = mParticlePositions[mNumParticles - 1];
+            mParticlePositions[mNumParticles - 1].x = -1.0f;
+            mParticlePositions[mNumParticles - 1].y = -1.0f;
+            mNumParticles--;
+            continue;
+        }
+
+        pos += particle.mVel;
+        if ( ci::distance( pos, particle.mCloudCenter ) >  particle.mCloudSize && !particle.mFlyOver ){
+            particle.mVel = rotate<float>( particle.mVel, 5 );
+        }
+    }
+
+    void *gpuMem = mParticleVbo->mapReplace();
+    memcpy( gpuMem, mParticlePositions.data(), mParticlePositions.size() * sizeof( vec2 ) );
+    mParticleVbo->unmap();
+}
+
+void ParticleController::addParticles(int amount, const vec2 &initialLocation, const float cloudSize)
+{
+    int reduction = ci::lmap<int>(mNumParticles, 0, kMaxParticles, 0, kMaxParticleAdd);
+    amount -= reduction;
+
+    if ( mNumParticles + amount > kMaxParticles ){
+		//return;
+        amount = kMaxParticles - mNumParticles;
+	}
+
+    if( amount <= 0 )
+        return;
+
+	for( size_t i = 0; i < amount; i++ ){
+        // init new particle 
+        Particle &particle = mParticles[mNumParticles + i];
+        vec2 &pos = mParticlePositions[mNumParticles + i];
+
+        pos = initialLocation + Rand::randVec2() * 5.0f; // find a location nearby the initial location
+        particle.mCloudCenter = pos;
+        particle.mVel = Rand::randVec2() * Rand::randFloat( 1.0f, 5.0f );
+        particle.mCloudSize = cloudSize;
+        particle.mAge = 0;
+        particle.mLifespan = Rand::randInt( 30, 60 );
+        particle.mFlyOver = (Rand::randInt( 500 ) == 0);
+		
+	}
+    
+    mNumParticles += amount ;
+
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/RtMidi.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,2839 @@
+/**********************************************************************/
+/*! \class RtMidi
+    \brief An abstract base class for realtime MIDI input/output.
+
+    This class implements some common functionality for the realtime
+    MIDI input/output subclasses RtMidiIn and RtMidiOut.
+
+    RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
+
+    RtMidi: realtime MIDI i/o C++ classes
+    Copyright (c) 2003-2016 Gary P. Scavone
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation files
+    (the "Software"), to deal in the Software without restriction,
+    including without limitation the rights to use, copy, modify, merge,
+    publish, distribute, sublicense, and/or sell copies of the Software,
+    and to permit persons to whom the Software is furnished to do so,
+    subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    Any person wishing to distribute modifications to the Software is
+    asked to send the modifications to the original developer so that
+    they can be incorporated into the canonical version.  This is,
+    however, not a binding provision of this license.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**********************************************************************/
+
+#include "RtMidi.h"
+#include <sstream>
+
+#if defined(__MACOSX_CORE__)
+  #if TARGET_OS_IPHONE
+    #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime
+    #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos
+  #endif
+#endif
+
+//*********************************************************************//
+//  RtMidi Definitions
+//*********************************************************************//
+
+RtMidi :: RtMidi()
+  : rtapi_(0)
+{
+}
+
+RtMidi :: ~RtMidi()
+{
+  if ( rtapi_ )
+    delete rtapi_;
+  rtapi_ = 0;
+}
+
+std::string RtMidi :: getVersion( void ) throw()
+{
+  return std::string( RTMIDI_VERSION );
+}
+
+void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw()
+{
+  apis.clear();
+
+  // The order here will control the order of RtMidi's API search in
+  // the constructor.
+#if defined(__MACOSX_CORE__)
+  apis.push_back( MACOSX_CORE );
+#endif
+#if defined(__LINUX_ALSA__)
+  apis.push_back( LINUX_ALSA );
+#endif
+#if defined(__UNIX_JACK__)
+  apis.push_back( UNIX_JACK );
+#endif
+#if defined(__WINDOWS_MM__)
+  apis.push_back( WINDOWS_MM );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+  apis.push_back( RTMIDI_DUMMY );
+#endif
+}
+
+//*********************************************************************//
+//  RtMidiIn Definitions
+//*********************************************************************//
+
+void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )
+{
+  if ( rtapi_ )
+    delete rtapi_;
+  rtapi_ = 0;
+
+#if defined(__UNIX_JACK__)
+  if ( api == UNIX_JACK )
+    rtapi_ = new MidiInJack( clientName, queueSizeLimit );
+#endif
+#if defined(__LINUX_ALSA__)
+  if ( api == LINUX_ALSA )
+    rtapi_ = new MidiInAlsa( clientName, queueSizeLimit );
+#endif
+#if defined(__WINDOWS_MM__)
+  if ( api == WINDOWS_MM )
+    rtapi_ = new MidiInWinMM( clientName, queueSizeLimit );
+#endif
+#if defined(__MACOSX_CORE__)
+  if ( api == MACOSX_CORE )
+    rtapi_ = new MidiInCore( clientName, queueSizeLimit );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+  if ( api == RTMIDI_DUMMY )
+    rtapi_ = new MidiInDummy( clientName, queueSizeLimit );
+#endif
+}
+
+RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )
+  : RtMidi()
+{
+  if ( api != UNSPECIFIED ) {
+    // Attempt to open the specified API.
+    openMidiApi( api, clientName, queueSizeLimit );
+    if ( rtapi_ ) return;
+
+    // No compiled support for specified API value.  Issue a warning
+    // and continue as if no API was specified.
+    std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl;
+  }
+
+  // Iterate through the compiled APIs and return as soon as we find
+  // one with at least one port or we reach the end of the list.
+  std::vector< RtMidi::Api > apis;
+  getCompiledApi( apis );
+  for ( unsigned int i=0; i<apis.size(); i++ ) {
+    openMidiApi( apis[i], clientName, queueSizeLimit );
+    if ( rtapi_->getPortCount() ) break;
+  }
+
+  if ( rtapi_ ) return;
+
+  // It should not be possible to get here because the preprocessor
+  // definition __RTMIDI_DUMMY__ is automatically defined if no
+  // API-specific definitions are passed to the compiler. But just in
+  // case something weird happens, we'll throw an error.
+  std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!";
+  throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
+}
+
+RtMidiIn :: ~RtMidiIn() throw()
+{
+}
+
+
+//*********************************************************************//
+//  RtMidiOut Definitions
+//*********************************************************************//
+
+void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName )
+{
+  if ( rtapi_ )
+    delete rtapi_;
+  rtapi_ = 0;
+
+#if defined(__UNIX_JACK__)
+  if ( api == UNIX_JACK )
+    rtapi_ = new MidiOutJack( clientName );
+#endif
+#if defined(__LINUX_ALSA__)
+  if ( api == LINUX_ALSA )
+    rtapi_ = new MidiOutAlsa( clientName );
+#endif
+#if defined(__WINDOWS_MM__)
+  if ( api == WINDOWS_MM )
+    rtapi_ = new MidiOutWinMM( clientName );
+#endif
+#if defined(__MACOSX_CORE__)
+  if ( api == MACOSX_CORE )
+    rtapi_ = new MidiOutCore( clientName );
+#endif
+#if defined(__RTMIDI_DUMMY__)
+  if ( api == RTMIDI_DUMMY )
+    rtapi_ = new MidiOutDummy( clientName );
+#endif
+}
+
+RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName )
+{
+  if ( api != UNSPECIFIED ) {
+    // Attempt to open the specified API.
+    openMidiApi( api, clientName );
+    if ( rtapi_ ) return;
+
+    // No compiled support for specified API value.  Issue a warning
+    // and continue as if no API was specified.
+    std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl;
+  }
+
+  // Iterate through the compiled APIs and return as soon as we find
+  // one with at least one port or we reach the end of the list.
+  std::vector< RtMidi::Api > apis;
+  getCompiledApi( apis );
+  for ( unsigned int i=0; i<apis.size(); i++ ) {
+    openMidiApi( apis[i], clientName );
+    if ( rtapi_->getPortCount() ) break;
+  }
+
+  if ( rtapi_ ) return;
+
+  // It should not be possible to get here because the preprocessor
+  // definition __RTMIDI_DUMMY__ is automatically defined if no
+  // API-specific definitions are passed to the compiler. But just in
+  // case something weird happens, we'll thrown an error.
+  std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!";
+  throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
+}
+
+RtMidiOut :: ~RtMidiOut() throw()
+{
+}
+
+//*********************************************************************//
+//  Common MidiApi Definitions
+//*********************************************************************//
+
+MidiApi :: MidiApi( void )
+  : apiData_( 0 ), connected_( false ), errorCallback_(0), errorCallbackUserData_(0)
+{
+}
+
+MidiApi :: ~MidiApi( void )
+{
+}
+
+void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 )
+{
+    errorCallback_ = errorCallback;
+    errorCallbackUserData_ = userData;
+}
+
+void MidiApi :: error( RtMidiError::Type type, std::string errorString )
+{
+  if ( errorCallback_ ) {
+
+    if ( firstErrorOccurred_ )
+      return;
+
+    firstErrorOccurred_ = true;
+    const std::string errorMessage = errorString;
+
+    errorCallback_( type, errorMessage, errorCallbackUserData_);
+    firstErrorOccurred_ = false;
+    return;
+  }
+
+  if ( type == RtMidiError::WARNING ) {
+    std::cerr << '\n' << errorString << "\n\n";
+  }
+  else if ( type == RtMidiError::DEBUG_WARNING ) {
+#if defined(__RTMIDI_DEBUG__)
+    std::cerr << '\n' << errorString << "\n\n";
+#endif
+  }
+  else {
+    std::cerr << '\n' << errorString << "\n\n";
+    throw RtMidiError( errorString, type );
+  }
+}
+
+//*********************************************************************//
+//  Common MidiInApi Definitions
+//*********************************************************************//
+
+MidiInApi :: MidiInApi( unsigned int queueSizeLimit )
+  : MidiApi()
+{
+  // Allocate the MIDI queue.
+  inputData_.queue.ringSize = queueSizeLimit;
+  if ( inputData_.queue.ringSize > 0 )
+    inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ];
+}
+
+MidiInApi :: ~MidiInApi( void )
+{
+  // Delete the MIDI queue.
+  if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring;
+}
+
+void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData )
+{
+  if ( inputData_.usingCallback ) {
+    errorString_ = "MidiInApi::setCallback: a callback function is already set!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  if ( !callback ) {
+    errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  inputData_.userCallback = callback;
+  inputData_.userData = userData;
+  inputData_.usingCallback = true;
+}
+
+void MidiInApi :: cancelCallback()
+{
+  if ( !inputData_.usingCallback ) {
+    errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  inputData_.userCallback = 0;
+  inputData_.userData = 0;
+  inputData_.usingCallback = false;
+}
+
+void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
+{
+  inputData_.ignoreFlags = 0;
+  if ( midiSysex ) inputData_.ignoreFlags = 0x01;
+  if ( midiTime ) inputData_.ignoreFlags |= 0x02;
+  if ( midiSense ) inputData_.ignoreFlags |= 0x04;
+}
+
+double MidiInApi :: getMessage( std::vector<unsigned char> *message )
+{
+  message->clear();
+
+  if ( inputData_.usingCallback ) {
+    errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";
+    error( RtMidiError::WARNING, errorString_ );
+    return 0.0;
+  }
+
+  if ( inputData_.queue.size == 0 ) return 0.0;
+
+  // Copy queued message to the vector pointer argument and then "pop" it.
+  std::vector<unsigned char> *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes);
+  message->assign( bytes->begin(), bytes->end() );
+  double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp;
+  inputData_.queue.size--;
+  inputData_.queue.front++;
+  if ( inputData_.queue.front == inputData_.queue.ringSize )
+    inputData_.queue.front = 0;
+
+  return deltaTime;
+}
+
+//*********************************************************************//
+//  Common MidiOutApi Definitions
+//*********************************************************************//
+
+MidiOutApi :: MidiOutApi( void )
+  : MidiApi()
+{
+}
+
+MidiOutApi :: ~MidiOutApi( void )
+{
+}
+
+// *************************************************** //
+//
+// OS/API-specific methods.
+//
+// *************************************************** //
+
+#if defined(__MACOSX_CORE__)
+
+// The CoreMIDI API is based on the use of a callback function for
+// MIDI input.  We convert the system specific time stamps to delta
+// time values.
+
+// OS-X CoreMIDI header files.
+#include <CoreMIDI/CoreMIDI.h>
+#include <CoreAudio/HostTime.h>
+#include <CoreServices/CoreServices.h>
+
+// A structure to hold variables related to the CoreMIDI API
+// implementation.
+struct CoreMidiData {
+  MIDIClientRef client;
+  MIDIPortRef port;
+  MIDIEndpointRef endpoint;
+  MIDIEndpointRef destinationId;
+  unsigned long long lastTime;
+  MIDISysexSendRequest sysexreq;
+};
+
+//*********************************************************************//
+//  API: OS-X
+//  Class Definitions: MidiInCore
+//*********************************************************************//
+
+static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ )
+{
+  MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef);
+  CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData);
+
+  unsigned char status;
+  unsigned short nBytes, iByte, size;
+  unsigned long long time;
+
+  bool& continueSysex = data->continueSysex;
+  MidiInApi::MidiMessage& message = data->message;
+
+  const MIDIPacket *packet = &list->packet[0];
+  for ( unsigned int i=0; i<list->numPackets; ++i ) {
+
+    // My interpretation of the CoreMIDI documentation: all message
+    // types, except sysex, are complete within a packet and there may
+    // be several of them in a single packet.  Sysex messages can be
+    // broken across multiple packets and PacketLists but are bundled
+    // alone within each packet (these packets do not contain other
+    // message types).  If sysex messages are split across multiple
+    // MIDIPacketLists, they must be handled by multiple calls to this
+    // function.
+
+    nBytes = packet->length;
+    if ( nBytes == 0 ) continue;
+
+    // Calculate time stamp.
+
+    if ( data->firstMessage ) {
+      message.timeStamp = 0.0;
+      data->firstMessage = false;
+    }
+    else {
+      time = packet->timeStamp;
+      if ( time == 0 ) { // this happens when receiving asynchronous sysex messages
+        time = AudioGetCurrentHostTime();
+      }
+      time -= apiData->lastTime;
+      time = AudioConvertHostTimeToNanos( time );
+      if ( !continueSysex )
+        message.timeStamp = time * 0.000000001;
+    }
+    apiData->lastTime = packet->timeStamp;
+    if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages
+      apiData->lastTime = AudioGetCurrentHostTime();
+    }
+    //std::cout << "TimeStamp = " << packet->timeStamp << std::endl;
+
+    iByte = 0;
+    if ( continueSysex ) {
+      // We have a continuing, segmented sysex message.
+      if ( !( data->ignoreFlags & 0x01 ) ) {
+        // If we're not ignoring sysex messages, copy the entire packet.
+        for ( unsigned int j=0; j<nBytes; ++j )
+          message.bytes.push_back( packet->data[j] );
+      }
+      continueSysex = packet->data[nBytes-1] != 0xF7;
+
+      if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) {
+        // If not a continuing sysex message, invoke the user callback function or queue the message.
+        if ( data->usingCallback ) {
+          RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+          callback( message.timeStamp, &message.bytes, data->userData );
+        }
+        else {
+          // As long as we haven't reached our queue size limit, push the message.
+          if ( data->queue.size < data->queue.ringSize ) {
+            data->queue.ring[data->queue.back++] = message;
+            if ( data->queue.back == data->queue.ringSize )
+              data->queue.back = 0;
+            data->queue.size++;
+          }
+          else
+            std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
+        }
+        message.bytes.clear();
+      }
+    }
+    else {
+      while ( iByte < nBytes ) {
+        size = 0;
+        // We are expecting that the next byte in the packet is a status byte.
+        status = packet->data[iByte];
+        if ( !(status & 0x80) ) break;
+        // Determine the number of bytes in the MIDI message.
+        if ( status < 0xC0 ) size = 3;
+        else if ( status < 0xE0 ) size = 2;
+        else if ( status < 0xF0 ) size = 3;
+        else if ( status == 0xF0 ) {
+          // A MIDI sysex
+          if ( data->ignoreFlags & 0x01 ) {
+            size = 0;
+            iByte = nBytes;
+          }
+          else size = nBytes - iByte;
+          continueSysex = packet->data[nBytes-1] != 0xF7;
+        }
+        else if ( status == 0xF1 ) {
+            // A MIDI time code message
+           if ( data->ignoreFlags & 0x02 ) {
+            size = 0;
+            iByte += 2;
+           }
+           else size = 2;
+        }
+        else if ( status == 0xF2 ) size = 3;
+        else if ( status == 0xF3 ) size = 2;
+        else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {
+          // A MIDI timing tick message and we're ignoring it.
+          size = 0;
+          iByte += 1;
+        }
+        else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {
+          // A MIDI active sensing message and we're ignoring it.
+          size = 0;
+          iByte += 1;
+        }
+        else size = 1;
+
+        // Copy the MIDI data to our vector.
+        if ( size ) {
+          message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );
+          if ( !continueSysex ) {
+            // If not a continuing sysex message, invoke the user callback function or queue the message.
+            if ( data->usingCallback ) {
+              RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+              callback( message.timeStamp, &message.bytes, data->userData );
+            }
+            else {
+              // As long as we haven't reached our queue size limit, push the message.
+              if ( data->queue.size < data->queue.ringSize ) {
+                data->queue.ring[data->queue.back++] = message;
+                if ( data->queue.back == data->queue.ringSize )
+                  data->queue.back = 0;
+                data->queue.size++;
+              }
+              else
+                std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
+            }
+            message.bytes.clear();
+          }
+          iByte += size;
+        }
+      }
+    }
+    packet = MIDIPacketNext(packet);
+  }
+}
+
+MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+MidiInCore :: ~MidiInCore( void )
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Cleanup.
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  MIDIClientDispose( data->client );
+  if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+  delete data;
+}
+
+void MidiInCore :: initialize( const std::string& clientName )
+{
+  // Set up our client.
+  MIDIClientRef client;
+  CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
+  OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
+  if ( result != noErr ) {
+    errorString_ = "MidiInCore::initialize: error creating OS-X MIDI client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Save our api-specific connection information.
+  CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
+  data->client = client;
+  data->endpoint = 0;
+  apiData_ = (void *) data;
+  inputData_.apiData = (void *) data;
+  CFRelease(name);
+}
+
+void MidiInCore :: openPort( unsigned int portNumber, const std::string portName )
+{
+  if ( connected_ ) {
+    errorString_ = "MidiInCore::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  unsigned int nSrc = MIDIGetNumberOfSources();
+  if (nSrc < 1) {
+    errorString_ = "MidiInCore::openPort: no MIDI input sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
+  }
+
+  if ( portNumber >= nSrc ) {
+    std::ostringstream ost;
+    ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
+  }
+
+  MIDIPortRef port;
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  OSStatus result = MIDIInputPortCreate( data->client, 
+                                         CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
+                                         midiInputCallback, (void *)&inputData_, &port );
+  if ( result != noErr ) {
+    MIDIClientDispose( data->client );
+    errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Get the desired input source identifier.
+  MIDIEndpointRef endpoint = MIDIGetSource( portNumber );
+  if ( endpoint == 0 ) {
+    MIDIPortDispose( port );
+    MIDIClientDispose( data->client );
+    errorString_ = "MidiInCore::openPort: error getting MIDI input source reference.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Make the connection.
+  result = MIDIPortConnectSource( port, endpoint, NULL );
+  if ( result != noErr ) {
+    MIDIPortDispose( port );
+    MIDIClientDispose( data->client );
+    errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Save our api-specific port information.
+  data->port = port;
+
+  connected_ = true;
+}
+
+void MidiInCore :: openVirtualPort( const std::string portName )
+{
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+
+  // Create a virtual MIDI input destination.
+  MIDIEndpointRef endpoint;
+  OSStatus result = MIDIDestinationCreate( data->client,
+                                           CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
+                                           midiInputCallback, (void *)&inputData_, &endpoint );
+  if ( result != noErr ) {
+    errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Save our api-specific connection information.
+  data->endpoint = endpoint;
+}
+
+void MidiInCore :: closePort( void )
+{
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+
+  if ( data->endpoint ) {
+    MIDIEndpointDispose( data->endpoint );
+  }
+
+  if ( data->port ) {
+    MIDIPortDispose( data->port );
+  }
+
+  connected_ = false;
+}
+
+unsigned int MidiInCore :: getPortCount()
+{
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  return MIDIGetNumberOfSources();
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal )
+{
+  CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
+  CFStringRef str;
+
+  // Begin with the endpoint's name.
+  str = NULL;
+  MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str );
+  if ( str != NULL ) {
+    CFStringAppend( result, str );
+    CFRelease( str );
+  }
+
+  MIDIEntityRef entity = 0;
+  MIDIEndpointGetEntity( endpoint, &entity );
+  if ( entity == 0 )
+    // probably virtual
+    return result;
+
+  if ( CFStringGetLength( result ) == 0 ) {
+    // endpoint name has zero length -- try the entity
+    str = NULL;
+    MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str );
+    if ( str != NULL ) {
+      CFStringAppend( result, str );
+      CFRelease( str );
+    }
+  }
+  // now consider the device's name
+  MIDIDeviceRef device = 0;
+  MIDIEntityGetDevice( entity, &device );
+  if ( device == 0 )
+    return result;
+
+  str = NULL;
+  MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str );
+  if ( CFStringGetLength( result ) == 0 ) {
+      CFRelease( result );
+      return str;
+  }
+  if ( str != NULL ) {
+    // if an external device has only one entity, throw away
+    // the endpoint name and just use the device name
+    if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) {
+      CFRelease( result );
+      return str;
+    } else {
+      if ( CFStringGetLength( str ) == 0 ) {
+        CFRelease( str );
+        return result;
+      }
+      // does the entity name already start with the device name?
+      // (some drivers do this though they shouldn't)
+      // if so, do not prepend
+        if ( CFStringCompareWithOptions( result, /* endpoint name */
+             str /* device name */,
+             CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) {
+        // prepend the device name to the entity name
+        if ( CFStringGetLength( result ) > 0 )
+          CFStringInsert( result, 0, CFSTR(" ") );
+        CFStringInsert( result, 0, str );
+      }
+      CFRelease( str );
+    }
+  }
+  return result;
+}
+
+// This function was submitted by Douglas Casey Tucker and apparently
+// derived largely from PortMidi.
+static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint )
+{
+  CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
+  CFStringRef str;
+  OSStatus err;
+  int i;
+
+  // Does the endpoint have connections?
+  CFDataRef connections = NULL;
+  int nConnected = 0;
+  bool anyStrings = false;
+  err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections );
+  if ( connections != NULL ) {
+    // It has connections, follow them
+    // Concatenate the names of all connected devices
+    nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID);
+    if ( nConnected ) {
+      const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
+      for ( i=0; i<nConnected; ++i, ++pid ) {
+        MIDIUniqueID id = EndianS32_BtoN( *pid );
+        MIDIObjectRef connObject;
+        MIDIObjectType connObjectType;
+        err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType );
+        if ( err == noErr ) {
+          if ( connObjectType == kMIDIObjectType_ExternalSource  ||
+              connObjectType == kMIDIObjectType_ExternalDestination ) {
+            // Connected to an external device's endpoint (10.3 and later).
+            str = EndpointName( (MIDIEndpointRef)(connObject), true );
+          } else {
+            // Connected to an external device (10.2) (or something else, catch-
+            str = NULL;
+            MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str );
+          }
+          if ( str != NULL ) {
+            if ( anyStrings )
+              CFStringAppend( result, CFSTR(", ") );
+            else anyStrings = true;
+            CFStringAppend( result, str );
+            CFRelease( str );
+          }
+        }
+      }
+    }
+    CFRelease( connections );
+  }
+  if ( anyStrings )
+    return result;
+
+  CFRelease( result );
+
+  // Here, either the endpoint had no connections, or we failed to obtain names 
+  return EndpointName( endpoint, false );
+}
+
+std::string MidiInCore :: getPortName( unsigned int portNumber )
+{
+  CFStringRef nameRef;
+  MIDIEndpointRef portRef;
+  char name[128];
+
+  std::string stringName;
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  if ( portNumber >= MIDIGetNumberOfSources() ) {
+    std::ostringstream ost;
+    ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  portRef = MIDIGetSource( portNumber );
+  nameRef = ConnectedEndpointName(portRef);
+  CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());
+  CFRelease( nameRef );
+
+  return stringName = name;
+}
+
+//*********************************************************************//
+//  API: OS-X
+//  Class Definitions: MidiOutCore
+//*********************************************************************//
+
+MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+MidiOutCore :: ~MidiOutCore( void )
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Cleanup.
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  MIDIClientDispose( data->client );
+  if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
+  delete data;
+}
+
+void MidiOutCore :: initialize( const std::string& clientName )
+{
+  // Set up our client.
+  MIDIClientRef client;
+  CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
+  OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
+  if ( result != noErr ) {
+    errorString_ = "MidiOutCore::initialize: error creating OS-X MIDI client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Save our api-specific connection information.
+  CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
+  data->client = client;
+  data->endpoint = 0;
+  apiData_ = (void *) data;
+  CFRelease( name );
+}
+
+unsigned int MidiOutCore :: getPortCount()
+{
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  return MIDIGetNumberOfDestinations();
+}
+
+std::string MidiOutCore :: getPortName( unsigned int portNumber )
+{
+  CFStringRef nameRef;
+  MIDIEndpointRef portRef;
+  char name[128];
+
+  std::string stringName;
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  if ( portNumber >= MIDIGetNumberOfDestinations() ) {
+    std::ostringstream ost;
+    ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  portRef = MIDIGetDestination( portNumber );
+  nameRef = ConnectedEndpointName(portRef);
+  CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());
+  CFRelease( nameRef );
+  
+  return stringName = name;
+}
+
+void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName )
+{
+  if ( connected_ ) {
+    errorString_ = "MidiOutCore::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
+  unsigned int nDest = MIDIGetNumberOfDestinations();
+  if (nDest < 1) {
+    errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
+  }
+
+  if ( portNumber >= nDest ) {
+    std::ostringstream ost;
+    ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
+  }
+
+  MIDIPortRef port;
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  OSStatus result = MIDIOutputPortCreate( data->client, 
+                                          CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
+                                          &port );
+  if ( result != noErr ) {
+    MIDIClientDispose( data->client );
+    errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Get the desired output port identifier.
+  MIDIEndpointRef destination = MIDIGetDestination( portNumber );
+  if ( destination == 0 ) {
+    MIDIPortDispose( port );
+    MIDIClientDispose( data->client );
+    errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Save our api-specific connection information.
+  data->port = port;
+  data->destinationId = destination;
+  connected_ = true;
+}
+
+void MidiOutCore :: closePort( void )
+{
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+
+  if ( data->endpoint ) {
+    MIDIEndpointDispose( data->endpoint );
+  }
+
+  if ( data->port ) {
+    MIDIPortDispose( data->port );
+  }
+
+  connected_ = false;
+}
+
+void MidiOutCore :: openVirtualPort( std::string portName )
+{
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+
+  if ( data->endpoint ) {
+    errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  // Create a virtual MIDI output source.
+  MIDIEndpointRef endpoint;
+  OSStatus result = MIDISourceCreate( data->client,
+                                      CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
+                                      &endpoint );
+  if ( result != noErr ) {
+    errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Save our api-specific connection information.
+  data->endpoint = endpoint;
+}
+
+void MidiOutCore :: sendMessage( std::vector<unsigned char> *message )
+{
+  // We use the MIDISendSysex() function to asynchronously send sysex
+  // messages.  Otherwise, we use a single CoreMidi MIDIPacket.
+  unsigned int nBytes = message->size();
+  if ( nBytes == 0 ) {
+    errorString_ = "MidiOutCore::sendMessage: no data in message argument!";      
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
+  CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
+  OSStatus result;
+
+  if ( message->at(0) != 0xF0 && nBytes > 3 ) {
+    errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  Byte buffer[nBytes+(sizeof(MIDIPacketList))];
+  ByteCount listSize = sizeof(buffer);
+  MIDIPacketList *packetList = (MIDIPacketList*)buffer;
+  MIDIPacket *packet = MIDIPacketListInit( packetList );
+
+  ByteCount remainingBytes = nBytes;
+  while (remainingBytes && packet) {
+    ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket
+    const Byte* dataStartPtr = (const Byte *) &message->at( nBytes - remainingBytes );
+    packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr);
+    remainingBytes -= bytesForPacket; 
+  }
+
+  if ( !packet ) {
+    errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";      
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Send to any destinations that may have connected to us.
+  if ( data->endpoint ) {
+    result = MIDIReceived( data->endpoint, packetList );
+    if ( result != noErr ) {
+      errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";
+      error( RtMidiError::WARNING, errorString_ );
+    }
+  }
+
+  // And send to an explicit destination port if we're connected.
+  if ( connected_ ) {
+    result = MIDISend( data->port, data->destinationId, packetList );
+    if ( result != noErr ) {
+      errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";
+      error( RtMidiError::WARNING, errorString_ );
+    }
+  }
+}
+
+#endif  // __MACOSX_CORE__
+
+
+//*********************************************************************//
+//  API: LINUX ALSA SEQUENCER
+//*********************************************************************//
+
+// API information found at:
+//   - http://www.alsa-project.org/documentation.php#Library
+
+#if defined(__LINUX_ALSA__)
+
+// The ALSA Sequencer API is based on the use of a callback function for
+// MIDI input.
+//
+// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
+// time stamps and other assorted fixes!!!
+
+// If you don't need timestamping for incoming MIDI events, define the
+// preprocessor definition AVOID_TIMESTAMPING to save resources
+// associated with the ALSA sequencer queues.
+
+#include <pthread.h>
+#include <sys/time.h>
+
+// ALSA header file.
+#include <alsa/asoundlib.h>
+
+// A structure to hold variables related to the ALSA API
+// implementation.
+struct AlsaMidiData {
+  snd_seq_t *seq;
+  unsigned int portNum;
+  int vport;
+  snd_seq_port_subscribe_t *subscription;
+  snd_midi_event_t *coder;
+  unsigned int bufferSize;
+  unsigned char *buffer;
+  pthread_t thread;
+  pthread_t dummy_thread_id;
+  unsigned long long lastTime;
+  int queue_id; // an input queue is needed to get timestamped events
+  int trigger_fds[2];
+};
+
+#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
+
+//*********************************************************************//
+//  API: LINUX ALSA
+//  Class Definitions: MidiInAlsa
+//*********************************************************************//
+
+static void *alsaMidiHandler( void *ptr )
+{
+  MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr);
+  AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);
+
+  long nBytes;
+  unsigned long long time, lastTime;
+  bool continueSysex = false;
+  bool doDecode = false;
+  MidiInApi::MidiMessage message;
+  int poll_fd_count;
+  struct pollfd *poll_fds;
+
+  snd_seq_event_t *ev;
+  int result;
+  apiData->bufferSize = 32;
+  result = snd_midi_event_new( 0, &apiData->coder );
+  if ( result < 0 ) {
+    data->doInput = false;
+    std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";
+    return 0;
+  }
+  unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
+  if ( buffer == NULL ) {
+    data->doInput = false;
+    snd_midi_event_free( apiData->coder );
+    apiData->coder = 0;
+    std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n";
+    return 0;
+  }
+  snd_midi_event_init( apiData->coder );
+  snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages
+
+  poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;
+  poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));
+  snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );
+  poll_fds[0].fd = apiData->trigger_fds[0];
+  poll_fds[0].events = POLLIN;
+
+  while ( data->doInput ) {
+
+    if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {
+      // No data pending
+      if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {
+        if ( poll_fds[0].revents & POLLIN ) {
+          bool dummy;
+          int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );
+          (void) res;
+        }
+      }
+      continue;
+    }
+
+    // If here, there should be data.
+    result = snd_seq_event_input( apiData->seq, &ev );
+    if ( result == -ENOSPC ) {
+      std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";
+      continue;
+    }
+    else if ( result <= 0 ) {
+      std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";
+      perror("System reports");
+      continue;
+    }
+
+    // This is a bit weird, but we now have to decode an ALSA MIDI
+    // event (back) into MIDI bytes.  We'll ignore non-MIDI types.
+    if ( !continueSysex ) message.bytes.clear();
+
+    doDecode = false;
+    switch ( ev->type ) {
+
+    case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+#if defined(__RTMIDI_DEBUG__)
+      std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";
+#endif
+      break;
+
+    case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
+#if defined(__RTMIDI_DEBUG__)
+      std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";
+      std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"
+                << (int) ev->data.connect.sender.port
+                << ", dest = " << (int) ev->data.connect.dest.client << ":"
+                << (int) ev->data.connect.dest.port
+                << std::endl;
+#endif
+      break;
+
+    case SND_SEQ_EVENT_QFRAME: // MIDI time code
+      if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+      break;
+
+    case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick
+      if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+      break;
+
+    case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick
+      if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
+      break;
+
+    case SND_SEQ_EVENT_SENSING: // Active sensing
+      if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;
+      break;
+
+		case SND_SEQ_EVENT_SYSEX:
+      if ( (data->ignoreFlags & 0x01) ) break;
+      if ( ev->data.ext.len > apiData->bufferSize ) {
+        apiData->bufferSize = ev->data.ext.len;
+        free( buffer );
+        buffer = (unsigned char *) malloc( apiData->bufferSize );
+        if ( buffer == NULL ) {
+          data->doInput = false;
+          std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";
+          break;
+        }
+      }
+
+    default:
+      doDecode = true;
+    }
+
+    if ( doDecode ) {
+
+      nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );
+      if ( nBytes > 0 ) {
+        // The ALSA sequencer has a maximum buffer size for MIDI sysex
+        // events of 256 bytes.  If a device sends sysex messages larger
+        // than this, they are segmented into 256 byte chunks.  So,
+        // we'll watch for this and concatenate sysex chunks into a
+        // single sysex message if necessary.
+        if ( !continueSysex )
+          message.bytes.assign( buffer, &buffer[nBytes] );
+        else
+          message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
+
+        continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
+        if ( !continueSysex ) {
+
+          // Calculate the time stamp:
+          message.timeStamp = 0.0;
+
+          // Method 1: Use the system time.
+          //(void)gettimeofday(&tv, (struct timezone *)NULL);
+          //time = (tv.tv_sec * 1000000) + tv.tv_usec;
+
+          // Method 2: Use the ALSA sequencer event time data.
+          // (thanks to Pedro Lopez-Cabanillas!).
+          time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );
+          lastTime = time;
+          time -= apiData->lastTime;
+          apiData->lastTime = lastTime;
+          if ( data->firstMessage == true )
+            data->firstMessage = false;
+          else
+            message.timeStamp = time * 0.000001;
+        }
+        else {
+#if defined(__RTMIDI_DEBUG__)
+          std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
+#endif
+        }
+      }
+    }
+
+    snd_seq_free_event( ev );
+    if ( message.bytes.size() == 0 || continueSysex ) continue;
+
+    if ( data->usingCallback ) {
+      RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+      callback( message.timeStamp, &message.bytes, data->userData );
+    }
+    else {
+      // As long as we haven't reached our queue size limit, push the message.
+      if ( data->queue.size < data->queue.ringSize ) {
+        data->queue.ring[data->queue.back++] = message;
+        if ( data->queue.back == data->queue.ringSize )
+          data->queue.back = 0;
+        data->queue.size++;
+      }
+      else
+        std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";
+    }
+  }
+
+  if ( buffer ) free( buffer );
+  snd_midi_event_free( apiData->coder );
+  apiData->coder = 0;
+  apiData->thread = apiData->dummy_thread_id;
+  return 0;
+}
+
+MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+MidiInAlsa :: ~MidiInAlsa()
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Shutdown the input thread.
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( inputData_.doInput ) {
+    inputData_.doInput = false;
+    int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
+    (void) res;
+    if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+      pthread_join( data->thread, NULL );
+  }
+
+  // Cleanup.
+  close ( data->trigger_fds[0] );
+  close ( data->trigger_fds[1] );
+  if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
+#ifndef AVOID_TIMESTAMPING
+  snd_seq_free_queue( data->seq, data->queue_id );
+#endif
+  snd_seq_close( data->seq );
+  delete data;
+}
+
+void MidiInAlsa :: initialize( const std::string& clientName )
+{
+  // Set up the ALSA sequencer client.
+  snd_seq_t *seq;
+  int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
+  if ( result < 0 ) {
+    errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Set client name.
+  snd_seq_set_client_name( seq, clientName.c_str() );
+
+  // Save our api-specific connection information.
+  AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
+  data->seq = seq;
+  data->portNum = -1;
+  data->vport = -1;
+  data->subscription = 0;
+  data->dummy_thread_id = pthread_self();
+  data->thread = data->dummy_thread_id;
+  data->trigger_fds[0] = -1;
+  data->trigger_fds[1] = -1;
+  apiData_ = (void *) data;
+  inputData_.apiData = (void *) data;
+
+   if ( pipe(data->trigger_fds) == -1 ) {
+    errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Create the input queue
+#ifndef AVOID_TIMESTAMPING
+  data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");
+  // Set arbitrary tempo (mm=100) and resolution (240)
+  snd_seq_queue_tempo_t *qtempo;
+  snd_seq_queue_tempo_alloca(&qtempo);
+  snd_seq_queue_tempo_set_tempo(qtempo, 600000);
+  snd_seq_queue_tempo_set_ppq(qtempo, 240);
+  snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo);
+  snd_seq_drain_output(data->seq);
+#endif
+}
+
+// This function is used to count or get the pinfo structure for a given port number.
+unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )
+{
+  snd_seq_client_info_t *cinfo;
+  int client;
+  int count = 0;
+  snd_seq_client_info_alloca( &cinfo );
+
+  snd_seq_client_info_set_client( cinfo, -1 );
+  while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
+    client = snd_seq_client_info_get_client( cinfo );
+    if ( client == 0 ) continue;
+    // Reset query info
+    snd_seq_port_info_set_client( pinfo, client );
+    snd_seq_port_info_set_port( pinfo, -1 );
+    while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
+      unsigned int atyp = snd_seq_port_info_get_type( pinfo );
+      if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) &&
+        ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) ) continue;
+      unsigned int caps = snd_seq_port_info_get_capability( pinfo );
+      if ( ( caps & type ) != type ) continue;
+      if ( count == portNumber ) return 1;
+      ++count;
+    }
+  }
+
+  // If a negative portNumber was used, return the port count.
+  if ( portNumber < 0 ) return count;
+  return 0;
+}
+
+unsigned int MidiInAlsa :: getPortCount()
+{
+  snd_seq_port_info_t *pinfo;
+  snd_seq_port_info_alloca( &pinfo );
+
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
+}
+
+std::string MidiInAlsa :: getPortName( unsigned int portNumber )
+{
+  snd_seq_client_info_t *cinfo;
+  snd_seq_port_info_t *pinfo;
+  snd_seq_client_info_alloca( &cinfo );
+  snd_seq_port_info_alloca( &pinfo );
+
+  std::string stringName;
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
+    int cnum = snd_seq_port_info_get_client( pinfo );
+    snd_seq_get_any_client_info( data->seq, cnum, cinfo );
+    std::ostringstream os;
+    os << snd_seq_client_info_get_name( cinfo );
+    os << " ";                                    // These lines added to make sure devices are listed
+    os << snd_seq_port_info_get_client( pinfo );  // with full portnames added to ensure individual device names
+    os << ":";
+    os << snd_seq_port_info_get_port( pinfo );
+    stringName = os.str();
+    return stringName;
+  }
+
+  // If we get here, we didn't find a match.
+  errorString_ = "MidiInAlsa::getPortName: error looking for port name!";
+  error( RtMidiError::WARNING, errorString_ );
+  return stringName;
+}
+
+void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName )
+{
+  if ( connected_ ) {
+    errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  unsigned int nSrc = this->getPortCount();
+  if ( nSrc < 1 ) {
+    errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
+  }
+
+  snd_seq_port_info_t *src_pinfo;
+  snd_seq_port_info_alloca( &src_pinfo );
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
+    std::ostringstream ost;
+    ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
+  }
+
+  snd_seq_addr_t sender, receiver;
+  sender.client = snd_seq_port_info_get_client( src_pinfo );
+  sender.port = snd_seq_port_info_get_port( src_pinfo );
+  receiver.client = snd_seq_client_id( data->seq );
+
+  snd_seq_port_info_t *pinfo;
+  snd_seq_port_info_alloca( &pinfo );
+  if ( data->vport < 0 ) {
+    snd_seq_port_info_set_client( pinfo, 0 );
+    snd_seq_port_info_set_port( pinfo, 0 );
+    snd_seq_port_info_set_capability( pinfo,
+                                      SND_SEQ_PORT_CAP_WRITE |
+                                      SND_SEQ_PORT_CAP_SUBS_WRITE );
+    snd_seq_port_info_set_type( pinfo,
+                                SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+                                SND_SEQ_PORT_TYPE_APPLICATION );
+    snd_seq_port_info_set_midi_channels(pinfo, 16);
+#ifndef AVOID_TIMESTAMPING
+    snd_seq_port_info_set_timestamping(pinfo, 1);
+    snd_seq_port_info_set_timestamp_real(pinfo, 1);    
+    snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
+#endif
+    snd_seq_port_info_set_name(pinfo,  portName.c_str() );
+    data->vport = snd_seq_create_port(data->seq, pinfo);
+  
+    if ( data->vport < 0 ) {
+      errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+    data->vport = snd_seq_port_info_get_port(pinfo);
+  }
+
+  receiver.port = data->vport;
+
+  if ( !data->subscription ) {
+    // Make subscription
+    if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
+      errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+    snd_seq_port_subscribe_set_sender(data->subscription, &sender);
+    snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
+    if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
+      snd_seq_port_subscribe_free( data->subscription );
+      data->subscription = 0;
+      errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+  }
+
+  if ( inputData_.doInput == false ) {
+    // Start the input queue
+#ifndef AVOID_TIMESTAMPING
+    snd_seq_start_queue( data->seq, data->queue_id, NULL );
+    snd_seq_drain_output( data->seq );
+#endif
+    // Start our MIDI input thread.
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+    pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
+
+    inputData_.doInput = true;
+    int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
+    pthread_attr_destroy(&attr);
+    if ( err ) {
+      snd_seq_unsubscribe_port( data->seq, data->subscription );
+      snd_seq_port_subscribe_free( data->subscription );
+      data->subscription = 0;
+      inputData_.doInput = false;
+      errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
+      error( RtMidiError::THREAD_ERROR, errorString_ );
+      return;
+    }
+  }
+
+  connected_ = true;
+}
+
+void MidiInAlsa :: openVirtualPort( std::string portName )
+{
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( data->vport < 0 ) {
+    snd_seq_port_info_t *pinfo;
+    snd_seq_port_info_alloca( &pinfo );
+    snd_seq_port_info_set_capability( pinfo,
+				      SND_SEQ_PORT_CAP_WRITE |
+				      SND_SEQ_PORT_CAP_SUBS_WRITE );
+    snd_seq_port_info_set_type( pinfo,
+				SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+				SND_SEQ_PORT_TYPE_APPLICATION );
+    snd_seq_port_info_set_midi_channels(pinfo, 16);
+#ifndef AVOID_TIMESTAMPING
+    snd_seq_port_info_set_timestamping(pinfo, 1);
+    snd_seq_port_info_set_timestamp_real(pinfo, 1);    
+    snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
+#endif
+    snd_seq_port_info_set_name(pinfo, portName.c_str());
+    data->vport = snd_seq_create_port(data->seq, pinfo);
+
+    if ( data->vport < 0 ) {
+      errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+    data->vport = snd_seq_port_info_get_port(pinfo);
+  }
+
+  if ( inputData_.doInput == false ) {
+    // Wait for old thread to stop, if still running
+    if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+      pthread_join( data->thread, NULL );
+
+    // Start the input queue
+#ifndef AVOID_TIMESTAMPING
+    snd_seq_start_queue( data->seq, data->queue_id, NULL );
+    snd_seq_drain_output( data->seq );
+#endif
+    // Start our MIDI input thread.
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+    pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
+
+    inputData_.doInput = true;
+    int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
+    pthread_attr_destroy(&attr);
+    if ( err ) {
+      if ( data->subscription ) {
+        snd_seq_unsubscribe_port( data->seq, data->subscription );
+        snd_seq_port_subscribe_free( data->subscription );
+        data->subscription = 0;
+      }
+      inputData_.doInput = false;
+      errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
+      error( RtMidiError::THREAD_ERROR, errorString_ );
+      return;
+    }
+  }
+}
+
+void MidiInAlsa :: closePort( void )
+{
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+
+  if ( connected_ ) {
+    if ( data->subscription ) {
+      snd_seq_unsubscribe_port( data->seq, data->subscription );
+      snd_seq_port_subscribe_free( data->subscription );
+      data->subscription = 0;
+    }
+    // Stop the input queue
+#ifndef AVOID_TIMESTAMPING
+    snd_seq_stop_queue( data->seq, data->queue_id, NULL );
+    snd_seq_drain_output( data->seq );
+#endif
+    connected_ = false;
+  }
+
+  // Stop thread to avoid triggering the callback, while the port is intended to be closed
+  if ( inputData_.doInput ) {
+    inputData_.doInput = false;
+    int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
+    (void) res;
+    if ( !pthread_equal(data->thread, data->dummy_thread_id) )
+      pthread_join( data->thread, NULL );
+  }
+}
+
+//*********************************************************************//
+//  API: LINUX ALSA
+//  Class Definitions: MidiOutAlsa
+//*********************************************************************//
+
+MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+MidiOutAlsa :: ~MidiOutAlsa()
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Cleanup.
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
+  if ( data->coder ) snd_midi_event_free( data->coder );
+  if ( data->buffer ) free( data->buffer );
+  snd_seq_close( data->seq );
+  delete data;
+}
+
+void MidiOutAlsa :: initialize( const std::string& clientName )
+{
+  // Set up the ALSA sequencer client.
+  snd_seq_t *seq;
+  int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
+  if ( result1 < 0 ) {
+    errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+	}
+
+  // Set client name.
+  snd_seq_set_client_name( seq, clientName.c_str() );
+
+  // Save our api-specific connection information.
+  AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
+  data->seq = seq;
+  data->portNum = -1;
+  data->vport = -1;
+  data->bufferSize = 32;
+  data->coder = 0;
+  data->buffer = 0;
+  int result = snd_midi_event_new( data->bufferSize, &data->coder );
+  if ( result < 0 ) {
+    delete data;
+    errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+  data->buffer = (unsigned char *) malloc( data->bufferSize );
+  if ( data->buffer == NULL ) {
+    delete data;
+    errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
+    error( RtMidiError::MEMORY_ERROR, errorString_ );
+    return;
+  }
+  snd_midi_event_init( data->coder );
+  apiData_ = (void *) data;
+}
+
+unsigned int MidiOutAlsa :: getPortCount()
+{
+	snd_seq_port_info_t *pinfo;
+	snd_seq_port_info_alloca( &pinfo );
+
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
+}
+
+std::string MidiOutAlsa :: getPortName( unsigned int portNumber )
+{
+  snd_seq_client_info_t *cinfo;
+  snd_seq_port_info_t *pinfo;
+  snd_seq_client_info_alloca( &cinfo );
+  snd_seq_port_info_alloca( &pinfo );
+
+  std::string stringName;
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {
+    int cnum = snd_seq_port_info_get_client(pinfo);
+    snd_seq_get_any_client_info( data->seq, cnum, cinfo );
+    std::ostringstream os;
+    os << snd_seq_client_info_get_name(cinfo);
+    os << " ";                                    // These lines added to make sure devices are listed
+    os << snd_seq_port_info_get_client( pinfo );  // with full portnames added to ensure individual device names
+    os << ":";
+    os << snd_seq_port_info_get_port(pinfo);
+    stringName = os.str();
+    return stringName;
+  }
+
+  // If we get here, we didn't find a match.
+  errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";
+  error( RtMidiError::WARNING, errorString_ );
+  return stringName;
+}
+
+void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName )
+{
+  if ( connected_ ) {
+    errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  unsigned int nSrc = this->getPortCount();
+  if (nSrc < 1) {
+    errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
+  }
+
+	snd_seq_port_info_t *pinfo;
+	snd_seq_port_info_alloca( &pinfo );
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {
+    std::ostringstream ost;
+    ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
+  }
+
+  snd_seq_addr_t sender, receiver;
+  receiver.client = snd_seq_port_info_get_client( pinfo );
+  receiver.port = snd_seq_port_info_get_port( pinfo );
+  sender.client = snd_seq_client_id( data->seq );
+
+  if ( data->vport < 0 ) {
+    data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
+                                              SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
+                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
+    if ( data->vport < 0 ) {
+      errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+  }
+
+  sender.port = data->vport;
+
+  // Make subscription
+  if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
+    snd_seq_port_subscribe_free( data->subscription );
+    errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+  snd_seq_port_subscribe_set_sender(data->subscription, &sender);
+  snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
+  snd_seq_port_subscribe_set_time_update(data->subscription, 1);
+  snd_seq_port_subscribe_set_time_real(data->subscription, 1);
+  if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
+    snd_seq_port_subscribe_free( data->subscription );
+    errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  connected_ = true;
+}
+
+void MidiOutAlsa :: closePort( void )
+{
+  if ( connected_ ) {
+    AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+    snd_seq_unsubscribe_port( data->seq, data->subscription );
+    snd_seq_port_subscribe_free( data->subscription );
+    connected_ = false;
+  }
+}
+
+void MidiOutAlsa :: openVirtualPort( std::string portName )
+{
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  if ( data->vport < 0 ) {
+    data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
+                                              SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
+                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
+
+    if ( data->vport < 0 ) {
+      errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+    }
+  }
+}
+
+void MidiOutAlsa :: sendMessage( std::vector<unsigned char> *message )
+{
+  int result;
+  AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
+  unsigned int nBytes = message->size();
+  if ( nBytes > data->bufferSize ) {
+    data->bufferSize = nBytes;
+    result = snd_midi_event_resize_buffer ( data->coder, nBytes);
+    if ( result != 0 ) {
+      errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+    free (data->buffer);
+    data->buffer = (unsigned char *) malloc( data->bufferSize );
+    if ( data->buffer == NULL ) {
+    errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
+    error( RtMidiError::MEMORY_ERROR, errorString_ );
+    return;
+    }
+  }
+
+  snd_seq_event_t ev;
+  snd_seq_ev_clear(&ev);
+  snd_seq_ev_set_source(&ev, data->vport);
+  snd_seq_ev_set_subs(&ev);
+  snd_seq_ev_set_direct(&ev);
+  for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message->at(i);
+  result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );
+  if ( result < (int)nBytes ) {
+    errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  // Send the event.
+  result = snd_seq_event_output(data->seq, &ev);
+  if ( result < 0 ) {
+    errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+  snd_seq_drain_output(data->seq);
+}
+
+#endif // __LINUX_ALSA__
+
+
+//*********************************************************************//
+//  API: Windows Multimedia Library (MM)
+//*********************************************************************//
+
+// API information deciphered from:
+//  - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp
+
+// Thanks to Jean-Baptiste Berruchon for the sysex code.
+
+#if defined(__WINDOWS_MM__)
+
+// The Windows MM API is based on the use of a callback function for
+// MIDI input.  We convert the system specific time stamps to delta
+// time values.
+
+// Windows MM MIDI header files.
+#include <windows.h>
+#include <mmsystem.h>
+
+#define  RT_SYSEX_BUFFER_SIZE 1024
+#define  RT_SYSEX_BUFFER_COUNT 4
+
+// A structure to hold variables related to the CoreMIDI API
+// implementation.
+struct WinMidiData {
+  HMIDIIN inHandle;    // Handle to Midi Input Device
+  HMIDIOUT outHandle;  // Handle to Midi Output Device
+  DWORD lastTime;
+  MidiInApi::MidiMessage message;
+  LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];
+  CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
+};
+
+//*********************************************************************//
+//  API: Windows MM
+//  Class Definitions: MidiInWinMM
+//*********************************************************************//
+
+static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,
+                                        UINT inputStatus, 
+                                        DWORD_PTR instancePtr,
+                                        DWORD_PTR midiMessage,
+                                        DWORD timestamp )
+{
+  if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;
+
+  //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr);
+  MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;
+  WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);
+
+  // Calculate time stamp.
+  if ( data->firstMessage == true ) {
+    apiData->message.timeStamp = 0.0;
+    data->firstMessage = false;
+  }
+  else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;
+  apiData->lastTime = timestamp;
+
+  if ( inputStatus == MIM_DATA ) { // Channel or system message
+
+    // Make sure the first byte is a status byte.
+    unsigned char status = (unsigned char) (midiMessage & 0x000000FF);
+    if ( !(status & 0x80) ) return;
+
+    // Determine the number of bytes in the MIDI message.
+    unsigned short nBytes = 1;
+    if ( status < 0xC0 ) nBytes = 3;
+    else if ( status < 0xE0 ) nBytes = 2;
+    else if ( status < 0xF0 ) nBytes = 3;
+    else if ( status == 0xF1 ) {
+      if ( data->ignoreFlags & 0x02 ) return;
+      else nBytes = 2;
+    }
+    else if ( status == 0xF2 ) nBytes = 3;
+    else if ( status == 0xF3 ) nBytes = 2;
+    else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) {
+      // A MIDI timing tick message and we're ignoring it.
+      return;
+    }
+    else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
+      // A MIDI active sensing message and we're ignoring it.
+      return;
+    }
+
+    // Copy bytes to our MIDI message.
+    unsigned char *ptr = (unsigned char *) &midiMessage;
+    for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ );
+  }
+  else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
+    MIDIHDR *sysex = ( MIDIHDR *) midiMessage; 
+    if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) {  
+      // Sysex message and we're not ignoring it
+      for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )
+        apiData->message.bytes.push_back( sysex->lpData[i] );
+    }
+
+    // The WinMM API requires that the sysex buffer be requeued after
+    // input of each sysex message.  Even if we are ignoring sysex
+    // messages, we still need to requeue the buffer in case the user
+    // decides to not ignore sysex messages in the future.  However,
+    // it seems that WinMM calls this function with an empty sysex
+    // buffer when an application closes and in this case, we should
+    // avoid requeueing it, else the computer suddenly reboots after
+    // one or two minutes.
+    if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) {
+      //if ( sysex->dwBytesRecorded > 0 ) {
+      EnterCriticalSection( &(apiData->_mutex) );
+      MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) );
+      LeaveCriticalSection( &(apiData->_mutex) );
+      if ( result != MMSYSERR_NOERROR )
+        std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";
+
+      if ( data->ignoreFlags & 0x01 ) return;
+    }
+    else return;
+  }
+
+  if ( data->usingCallback ) {
+    RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
+    callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData );
+  }
+  else {
+    // As long as we haven't reached our queue size limit, push the message.
+    if ( data->queue.size < data->queue.ringSize ) {
+      data->queue.ring[data->queue.back++] = apiData->message;
+      if ( data->queue.back == data->queue.ringSize )
+        data->queue.back = 0;
+      data->queue.size++;
+    }
+    else
+      std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
+  }
+
+  // Clear the vector for the next input message.
+  apiData->message.bytes.clear();
+}
+
+MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+MidiInWinMM :: ~MidiInWinMM()
+{
+  // Close a connection if it exists.
+  closePort();
+
+  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+  DeleteCriticalSection( &(data->_mutex) );
+
+  // Cleanup.
+  delete data;
+}
+
+void MidiInWinMM :: initialize( const std::string& /*clientName*/ )
+{
+  // We'll issue a warning here if no devices are available but not
+  // throw an error since the user can plugin something later.
+  unsigned int nDevices = midiInGetNumDevs();
+  if ( nDevices == 0 ) {
+    errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available.";
+    error( RtMidiError::WARNING, errorString_ );
+  }
+
+  // Save our api-specific connection information.
+  WinMidiData *data = (WinMidiData *) new WinMidiData;
+  apiData_ = (void *) data;
+  inputData_.apiData = (void *) data;
+  data->message.bytes.clear();  // needs to be empty for first input message
+
+  if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) {
+    errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed.";
+    error( RtMidiError::WARNING, errorString_ );
+  }
+}
+
+void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )
+{
+  if ( connected_ ) {
+    errorString_ = "MidiInWinMM::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  unsigned int nDevices = midiInGetNumDevs();
+  if (nDevices == 0) {
+    errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
+  }
+
+  if ( portNumber >= nDevices ) {
+    std::ostringstream ost;
+    ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
+  }
+
+  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+  MMRESULT result = midiInOpen( &data->inHandle,
+                                portNumber,
+                                (DWORD_PTR)&midiInputCallback,
+                                (DWORD_PTR)&inputData_,
+                                CALLBACK_FUNCTION );
+  if ( result != MMSYSERR_NOERROR ) {
+    errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Allocate and init the sysex buffers.
+  for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {
+    data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
+    data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];
+    data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE;
+    data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator
+    data->sysexBuffer[i]->dwFlags = 0;
+
+    result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
+    if ( result != MMSYSERR_NOERROR ) {
+      midiInClose( data->inHandle );
+      errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader).";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+
+    // Register the buffer.
+    result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
+    if ( result != MMSYSERR_NOERROR ) {
+      midiInClose( data->inHandle );
+      errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer).";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+  }
+
+  result = midiInStart( data->inHandle );
+  if ( result != MMSYSERR_NOERROR ) {
+    midiInClose( data->inHandle );
+    errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  connected_ = true;
+}
+
+void MidiInWinMM :: openVirtualPort( std::string /*portName*/ )
+{
+  // This function cannot be implemented for the Windows MM MIDI API.
+  errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
+  error( RtMidiError::WARNING, errorString_ );
+}
+
+void MidiInWinMM :: closePort( void )
+{
+  if ( connected_ ) {
+    WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+    EnterCriticalSection( &(data->_mutex) );
+    midiInReset( data->inHandle );
+    midiInStop( data->inHandle );
+
+    for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {
+      int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR));
+      delete [] data->sysexBuffer[i]->lpData;
+      delete [] data->sysexBuffer[i];
+      if ( result != MMSYSERR_NOERROR ) {
+        midiInClose( data->inHandle );
+        errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";
+        error( RtMidiError::DRIVER_ERROR, errorString_ );
+        return;
+      }
+    }
+
+    midiInClose( data->inHandle );
+    connected_ = false;
+    LeaveCriticalSection( &(data->_mutex) );
+  }
+}
+
+unsigned int MidiInWinMM :: getPortCount()
+{
+  return midiInGetNumDevs();
+}
+
+std::string MidiInWinMM :: getPortName( unsigned int portNumber )
+{
+  std::string stringName;
+  unsigned int nDevices = midiInGetNumDevs();
+  if ( portNumber >= nDevices ) {
+    std::ostringstream ost;
+    ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  MIDIINCAPS deviceCaps;
+  midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));
+
+#if defined( UNICODE ) || defined( _UNICODE )
+  int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;
+  stringName.assign( length, 0 );
+  length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);
+#else
+  stringName = std::string( deviceCaps.szPname );
+#endif
+
+  // Next lines added to add the portNumber to the name so that 
+  // the device's names are sure to be listed with individual names
+  // even when they have the same brand name
+  std::ostringstream os;
+  os << " ";
+  os << portNumber;
+  stringName += os.str();
+
+  return stringName;
+}
+
+//*********************************************************************//
+//  API: Windows MM
+//  Class Definitions: MidiOutWinMM
+//*********************************************************************//
+
+MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+MidiOutWinMM :: ~MidiOutWinMM()
+{
+  // Close a connection if it exists.
+  closePort();
+
+  // Cleanup.
+  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+  delete data;
+}
+
+void MidiOutWinMM :: initialize( const std::string& /*clientName*/ )
+{
+  // We'll issue a warning here if no devices are available but not
+  // throw an error since the user can plug something in later.
+  unsigned int nDevices = midiOutGetNumDevs();
+  if ( nDevices == 0 ) {
+    errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available.";
+    error( RtMidiError::WARNING, errorString_ );
+  }
+
+  // Save our api-specific connection information.
+  WinMidiData *data = (WinMidiData *) new WinMidiData;
+  apiData_ = (void *) data;
+}
+
+unsigned int MidiOutWinMM :: getPortCount()
+{
+  return midiOutGetNumDevs();
+}
+
+std::string MidiOutWinMM :: getPortName( unsigned int portNumber )
+{
+  std::string stringName;
+  unsigned int nDevices = midiOutGetNumDevs();
+  if ( portNumber >= nDevices ) {
+    std::ostringstream ost;
+    ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+    return stringName;
+  }
+
+  MIDIOUTCAPS deviceCaps;
+  midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS));
+
+#if defined( UNICODE ) || defined( _UNICODE )
+  int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;
+  stringName.assign( length, 0 );
+  length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);
+#else
+  stringName = std::string( deviceCaps.szPname );
+#endif
+
+  // Next lines added to add the portNumber to the name so that 
+  // the device's names are sure to be listed with individual names
+  // even when they have the same brand name
+  std::ostringstream os;
+  os << " ";
+  os << portNumber;
+  stringName += os.str();
+
+  return stringName;
+}
+
+void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )
+{
+  if ( connected_ ) {
+    errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  unsigned int nDevices = midiOutGetNumDevs();
+  if (nDevices < 1) {
+    errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!";
+    error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
+    return;
+  }
+
+  if ( portNumber >= nDevices ) {
+    std::ostringstream ost;
+    ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::INVALID_PARAMETER, errorString_ );
+    return;
+  }
+
+  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+  MMRESULT result = midiOutOpen( &data->outHandle,
+                                 portNumber,
+                                 (DWORD)NULL,
+                                 (DWORD)NULL,
+                                 CALLBACK_NULL );
+  if ( result != MMSYSERR_NOERROR ) {
+    errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port.";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  connected_ = true;
+}
+
+void MidiOutWinMM :: closePort( void )
+{
+  if ( connected_ ) {
+    WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+    midiOutReset( data->outHandle );
+    midiOutClose( data->outHandle );
+    connected_ = false;
+  }
+}
+
+void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ )
+{
+  // This function cannot be implemented for the Windows MM MIDI API.
+  errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
+  error( RtMidiError::WARNING, errorString_ );
+}
+
+void MidiOutWinMM :: sendMessage( std::vector<unsigned char> *message )
+{
+  if ( !connected_ ) return;
+
+  unsigned int nBytes = static_cast<unsigned int>(message->size());
+  if ( nBytes == 0 ) {
+    errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  MMRESULT result;
+  WinMidiData *data = static_cast<WinMidiData *> (apiData_);
+  if ( message->at(0) == 0xF0 ) { // Sysex message
+
+    // Allocate buffer for sysex data.
+    char *buffer = (char *) malloc( nBytes );
+    if ( buffer == NULL ) {
+      errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!";
+      error( RtMidiError::MEMORY_ERROR, errorString_ );
+      return;
+    }
+
+    // Copy data to buffer.
+    for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message->at(i);
+
+    // Create and prepare MIDIHDR structure.
+    MIDIHDR sysex;
+    sysex.lpData = (LPSTR) buffer;
+    sysex.dwBufferLength = nBytes;
+    sysex.dwFlags = 0;
+    result = midiOutPrepareHeader( data->outHandle,  &sysex, sizeof(MIDIHDR) ); 
+    if ( result != MMSYSERR_NOERROR ) {
+      free( buffer );
+      errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+
+    // Send the message.
+    result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) );
+    if ( result != MMSYSERR_NOERROR ) {
+      free( buffer );
+      errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+      return;
+    }
+
+    // Unprepare the buffer and MIDIHDR.
+    while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 );
+    free( buffer );
+  }
+  else { // Channel or system message.
+
+    // Make sure the message size isn't too big.
+    if ( nBytes > 3 ) {
+      errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!";
+      error( RtMidiError::WARNING, errorString_ );
+      return;
+    }
+
+    // Pack MIDI bytes into double word.
+    DWORD packet;
+    unsigned char *ptr = (unsigned char *) &packet;
+    for ( unsigned int i=0; i<nBytes; ++i ) {
+      *ptr = message->at(i);
+      ++ptr;
+    }
+
+    // Send the message immediately.
+    result = midiOutShortMsg( data->outHandle, packet );
+    if ( result != MMSYSERR_NOERROR ) {
+      errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";
+      error( RtMidiError::DRIVER_ERROR, errorString_ );
+    }
+  }
+}
+
+#endif  // __WINDOWS_MM__
+
+
+//*********************************************************************//
+//  API: UNIX JACK
+//
+//  Written primarily by Alexander Svetalkin, with updates for delta
+//  time by Gary Scavone, April 2011.
+//
+//  *********************************************************************//
+
+#if defined(__UNIX_JACK__)
+
+// JACK header files
+#include <jack/jack.h>
+#include <jack/midiport.h>
+#include <jack/ringbuffer.h>
+
+#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer
+
+struct JackMidiData {
+  jack_client_t *client;
+  jack_port_t *port;
+  jack_ringbuffer_t *buffSize;
+  jack_ringbuffer_t *buffMessage;
+  jack_time_t lastTime;
+  MidiInApi :: RtMidiInData *rtMidiIn;
+  };
+
+//*********************************************************************//
+//  API: JACK
+//  Class Definitions: MidiInJack
+//*********************************************************************//
+
+static int jackProcessIn( jack_nframes_t nframes, void *arg )
+{
+  JackMidiData *jData = (JackMidiData *) arg;
+  MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn;
+  jack_midi_event_t event;
+  jack_time_t time;
+
+  // Is port created?
+  if ( jData->port == NULL ) return 0;
+  void *buff = jack_port_get_buffer( jData->port, nframes );
+
+  // We have midi events in buffer
+  int evCount = jack_midi_get_event_count( buff );
+  for (int j = 0; j < evCount; j++) {
+    MidiInApi::MidiMessage message;
+    message.bytes.clear();
+
+    jack_midi_event_get( &event, buff, j );
+
+    for ( unsigned int i = 0; i < event.size; i++ )
+      message.bytes.push_back( event.buffer[i] );
+
+    // Compute the delta time.
+    time = jack_get_time();
+    if ( rtData->firstMessage == true )
+      rtData->firstMessage = false;
+    else
+      message.timeStamp = ( time - jData->lastTime ) * 0.000001;
+
+    jData->lastTime = time;
+
+    if ( !rtData->continueSysex ) {
+      if ( rtData->usingCallback ) {
+        RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback;
+        callback( message.timeStamp, &message.bytes, rtData->userData );
+      }
+      else {
+        // As long as we haven't reached our queue size limit, push the message.
+        if ( rtData->queue.size < rtData->queue.ringSize ) {
+          rtData->queue.ring[rtData->queue.back++] = message;
+          if ( rtData->queue.back == rtData->queue.ringSize )
+            rtData->queue.back = 0;
+          rtData->queue.size++;
+        }
+        else
+          std::cerr << "\nMidiInJack: message queue limit reached!!\n\n";
+      }
+    }
+  }
+
+  return 0;
+}
+
+MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
+{
+  initialize( clientName );
+}
+
+void MidiInJack :: initialize( const std::string& clientName )
+{
+  JackMidiData *data = new JackMidiData;
+  apiData_ = (void *) data;
+
+  data->rtMidiIn = &inputData_;
+  data->port = NULL;
+  data->client = NULL;
+  this->clientName = clientName;
+
+  connect();
+}
+
+void MidiInJack :: connect()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  if ( data->client )
+    return;
+
+  // Initialize JACK client
+  if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
+    errorString_ = "MidiInJack::initialize: JACK server not running?";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  jack_set_process_callback( data->client, jackProcessIn, data );
+  jack_activate( data->client );
+}
+
+MidiInJack :: ~MidiInJack()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  closePort();
+
+  if ( data->client )
+    jack_client_close( data->client );
+  delete data;
+}
+
+void MidiInJack :: openPort( unsigned int portNumber, const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+
+  // Creating new port
+  if ( data->port == NULL)
+    data->port = jack_port_register( data->client, portName.c_str(),
+                                     JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
+
+  if ( data->port == NULL) {
+    errorString_ = "MidiInJack::openPort: JACK error creating port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Connecting to the output
+  std::string name = getPortName( portNumber );
+  jack_connect( data->client, name.c_str(), jack_port_name( data->port ) );
+}
+
+void MidiInJack :: openVirtualPort( const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+  if ( data->port == NULL )
+    data->port = jack_port_register( data->client, portName.c_str(),
+                                     JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
+
+  if ( data->port == NULL ) {
+    errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+  }
+}
+
+unsigned int MidiInJack :: getPortCount()
+{
+  int count = 0;
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  connect();
+  if ( !data->client )
+    return 0;
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
+
+  if ( ports == NULL ) return 0;
+  while ( ports[count] != NULL )
+    count++;
+
+  free( ports );
+
+  return count;
+}
+
+std::string MidiInJack :: getPortName( unsigned int portNumber )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  std::string retStr("");
+
+  connect();
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL,
+                                       JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
+
+  // Check port validity
+  if ( ports == NULL ) {
+    errorString_ = "MidiInJack::getPortName: no ports available!";
+    error( RtMidiError::WARNING, errorString_ );
+    return retStr;
+  }
+
+  if ( ports[portNumber] == NULL ) {
+    std::ostringstream ost;
+    ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+  }
+  else retStr.assign( ports[portNumber] );
+
+  free( ports );
+  return retStr;
+}
+
+void MidiInJack :: closePort()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  if ( data->port == NULL ) return;
+  jack_port_unregister( data->client, data->port );
+  data->port = NULL;
+}
+
+//*********************************************************************//
+//  API: JACK
+//  Class Definitions: MidiOutJack
+//*********************************************************************//
+
+// Jack process callback
+static int jackProcessOut( jack_nframes_t nframes, void *arg )
+{
+  JackMidiData *data = (JackMidiData *) arg;
+  jack_midi_data_t *midiData;
+  int space;
+
+  // Is port created?
+  if ( data->port == NULL ) return 0;
+
+  void *buff = jack_port_get_buffer( data->port, nframes );
+  jack_midi_clear_buffer( buff );
+
+  while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) {
+    jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) );
+    midiData = jack_midi_event_reserve( buff, 0, space );
+
+    jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space );
+  }
+
+  return 0;
+}
+
+MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi()
+{
+  initialize( clientName );
+}
+
+void MidiOutJack :: initialize( const std::string& clientName )
+{
+  JackMidiData *data = new JackMidiData;
+  apiData_ = (void *) data;
+
+  data->port = NULL;
+  data->client = NULL;
+  this->clientName = clientName;
+
+  connect();
+}
+
+void MidiOutJack :: connect()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  if ( data->client )
+    return;
+  
+  // Initialize output ringbuffers  
+  data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
+  data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
+
+  // Initialize JACK client
+  if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
+    errorString_ = "MidiOutJack::initialize: JACK server not running?";
+    error( RtMidiError::WARNING, errorString_ );
+    return;
+  }
+
+  jack_set_process_callback( data->client, jackProcessOut, data );
+  jack_activate( data->client );
+}
+
+MidiOutJack :: ~MidiOutJack()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  closePort();
+  
+  // Cleanup
+  jack_ringbuffer_free( data->buffSize );
+  jack_ringbuffer_free( data->buffMessage );
+  if ( data->client ) {
+    jack_client_close( data->client );
+  }
+
+  delete data;
+}
+
+void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+
+  // Creating new port
+  if ( data->port == NULL )
+    data->port = jack_port_register( data->client, portName.c_str(),
+      JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
+
+  if ( data->port == NULL ) {
+    errorString_ = "MidiOutJack::openPort: JACK error creating port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+    return;
+  }
+
+  // Connecting to the output
+  std::string name = getPortName( portNumber );
+  jack_connect( data->client, jack_port_name( data->port ), name.c_str() );
+}
+
+void MidiOutJack :: openVirtualPort( const std::string portName )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  connect();
+  if ( data->port == NULL )
+    data->port = jack_port_register( data->client, portName.c_str(),
+      JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
+
+  if ( data->port == NULL ) {
+    errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";
+    error( RtMidiError::DRIVER_ERROR, errorString_ );
+  }
+}
+
+unsigned int MidiOutJack :: getPortCount()
+{
+  int count = 0;
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  connect();
+  if ( !data->client )
+    return 0;
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL,
+    JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
+
+  if ( ports == NULL ) return 0;
+  while ( ports[count] != NULL )
+    count++;
+
+  free( ports );
+
+  return count;
+}
+
+std::string MidiOutJack :: getPortName( unsigned int portNumber )
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+  std::string retStr("");
+
+  connect();
+
+  // List of available ports
+  const char **ports = jack_get_ports( data->client, NULL,
+    JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
+
+  // Check port validity
+  if ( ports == NULL) {
+    errorString_ = "MidiOutJack::getPortName: no ports available!";
+    error( RtMidiError::WARNING, errorString_ );
+    return retStr;
+  }
+
+  if ( ports[portNumber] == NULL) {
+    std::ostringstream ost;
+    ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
+    errorString_ = ost.str();
+    error( RtMidiError::WARNING, errorString_ );
+  }
+  else retStr.assign( ports[portNumber] );
+
+  free( ports );
+  return retStr;
+}
+
+void MidiOutJack :: closePort()
+{
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  if ( data->port == NULL ) return;
+  jack_port_unregister( data->client, data->port );
+  data->port = NULL;
+}
+
+void MidiOutJack :: sendMessage( std::vector<unsigned char> *message )
+{
+  int nBytes = message->size();
+  JackMidiData *data = static_cast<JackMidiData *> (apiData_);
+
+  // Write full message to buffer
+  jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0],
+                         message->size() );
+  jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) );
+}
+
+#endif  // __UNIX_JACK__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CollidoscopeApp/src/Wave.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,222 @@
+#include "Wave.h"
+#include "DrawInfo.h"
+
+
+using namespace ci;
+
+Wave::Wave( size_t numChunks, Color selectionColor ):
+    mNumChunks( numChunks ),
+	mSelection( this, selectionColor ),
+	mColor(Color(0.5f, 0.5f, 0.5f)),
+    mFilterCoeff( 1.0f )
+{
+	mChunks.reserve( numChunks );
+
+	for ( size_t i = 0; i < numChunks; i++ ){
+		mChunks.emplace_back( i );
+	}
+
+    auto lambert = gl::ShaderDef().color();
+    gl::GlslProgRef shader = gl::getStockShader( lambert );
+    mChunkBatch = gl::Batch::create( geom::Rect( ci::Rectf( 0, 0, Chunk::kWidth, 1 ) ), shader );
+}
+
+void Wave::reset( bool onlyChunks )
+{
+	for (size_t i = 0; i < getSize(); i++){
+		mChunks[i].reset();
+	}
+
+	if (onlyChunks)
+		return;
+
+	mSelection.setToNull();
+}
+
+
+void Wave::setChunk(size_t index, float bottom, float top)
+{
+	Chunk &c = mChunks[index];
+	c.setTop(top);
+	c.setBottom(bottom);
+}
+
+inline const Chunk & Wave::getChunk(size_t index)
+{
+	return mChunks[index];
+}
+
+void Wave::update( double secondsPerChunk, const DrawInfo& di ) {
+    typedef std::map<int, Cursor>::iterator MapItr;
+
+
+    // update the cursor positions
+    double now = ci::app::getElapsedSeconds();
+    for (MapItr itr = mCursors.begin(); itr != mCursors.end(); ++itr){
+        if (mSelection.isNull()){
+            itr->second.pos = Cursor::kNoPosition;
+        }
+
+        if ( itr->second.pos == Cursor::kNoPosition )
+            continue;
+
+
+        double elapsed = now - itr->second.lastUpdate;
+
+        itr->second.pos = mSelection.getStart() + int( elapsed / secondsPerChunk );
+
+        /* check we don't go too far off */
+        if (itr->second.pos > mSelection.getEnd()){
+            itr->second.pos = Cursor::kNoPosition;
+        }
+    }
+
+    // update chunks for animation 
+    for ( auto &chunk : mChunks ){
+        chunk.update( di );
+    }
+
+#ifdef USE_PARTICLES
+    mParticleController.updateParticles();
+#endif
+
+}
+
+void Wave::draw( const DrawInfo& di ){
+
+
+	/* ########### draw the particles ########## */
+#ifdef USE_PARTICLES
+	mParticleController.draw();
+#endif
+
+	/* ########### draw the wave ########## */
+	/* scale the wave to fit the window */
+	gl::pushModelView(); 
+
+	
+	const float wavePixelLen =  ( mNumChunks * ( 2 + Chunk::kWidth ) );
+	/* scale the x-axis for the wave to fit the window */
+	gl::scale( ((float)di.getWindowWidth() ) / wavePixelLen , 1.0f);
+	/* draw the chunks */
+	if (mSelection.isNull()){
+		/* no selection: all chunks the same color */
+		gl::color(mColor); 
+		for (size_t i = 0; i < getSize(); i++){
+			mChunks[i].draw( di, mChunkBatch );
+		}
+	}
+    else{ 
+        // Selection not null 
+		gl::color(this->mColor); 
+
+        // update the array with cursor positions 
+        mCursorsPos.clear();
+        for ( auto cursor : mCursors ){
+            mCursorsPos.push_back( cursor.second.pos );
+        }
+
+		gl::enableAlphaBlending();
+
+		const float selectionAlpha = 0.5f + mFilterCoeff * 0.5f;
+
+
+		for (size_t i = 0; i < getSize(); i++){
+			/* when in selection use selection color */
+			
+			if (i == mSelection.getStart()){
+				/* draw the selection bar with a transparent selection color */
+				gl::color(mSelection.getColor().r, mSelection.getColor().g, mSelection.getColor().b, 0.5f);
+                mChunks[i].drawBar( di, mChunkBatch );
+
+				/* set the color to the selection */
+				gl::color(mSelection.getColor().r, mSelection.getColor().g, mSelection.getColor().b, selectionAlpha);
+			}
+
+            // check if one of the cursors is positioned in this chunk  
+			if (std::find(mCursorsPos.begin(), mCursorsPos.end(),i) != mCursorsPos.end() ){
+				gl::color(CURSOR_CLR);
+				mChunks[i].draw( di, mChunkBatch );
+				gl::color(mSelection.getColor().r, mSelection.getColor().g, mSelection.getColor().b, selectionAlpha);
+			}
+			else{
+				/* just draw with current color */
+				mChunks[i].draw( di, mChunkBatch );
+			}
+			
+			/* exit selection: go back to wave color */
+			if (i == mSelection.getEnd()){
+				/* draw the selection bar with a transparent selection color */
+				gl::color(mSelection.getColor().r, mSelection.getColor().g, mSelection.getColor().b, 0.5f);
+                mChunks[i].drawBar( di, mChunkBatch );
+				/* set the colo to the wave */
+				gl::color(this->mColor);  
+			}
+		}
+		gl::disableAlphaBlending();
+	}
+	
+
+	gl::popModelView();
+
+}
+
+
+
+//**************** Selection ***************//
+
+Wave::Selection::Selection(Wave * w, Color color) : 
+    mWave( w ), 
+    mSelectionStart( 0 ), 
+    mSelectionEnd( 0 ),
+    mColor( color ),
+    mParticleSpread( 1 )
+{}
+
+
+void Wave::Selection::setStart(size_t start)  {
+
+	/* deselect the previous */
+    mWave->mChunks[mSelectionStart].setAsSelectionStart( false );
+	/* select the next */
+    mWave->mChunks[start].setAsSelectionStart( true );
+	
+	mNull = false;
+
+    size_t size = getSize();
+
+	mSelectionStart = start;
+    mSelectionEnd = start + size - 1;
+    if ( mSelectionEnd > mWave->getSize() - 1 )
+        mSelectionEnd = mWave->getSize() - 1;
+
+}
+
+void Wave::Selection::setSize(size_t size)  {
+
+    if ( size <= 0 ){
+        mNull = true;
+        return;
+    }
+
+    size -= 1;
+
+    // check boundaries: size cannot bring the selection end beyond the end of the wave 
+    if ( mSelectionStart+size >= mWave->mNumChunks ){
+        size = mWave->mNumChunks - mSelectionStart - 1;
+    }
+
+	/* deselect the previous */
+    mWave->mChunks[mSelectionEnd].setAsSelectionEnd( false );
+
+    mSelectionEnd = mSelectionStart + size;
+	/* select the next */
+    mWave->mChunks[mSelectionEnd].setAsSelectionEnd( true );
+
+	mNull = false;
+}
+
+
+const cinder::Color Wave::CURSOR_CLR = Color(1.f, 1.f, 1.f);
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/JackDevice/ContextJack.cpp	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,301 @@
+/*
+ Copyright (c) 2015, The Cinder Project
+
+ This code is intended to be used with the Cinder C++ library, http://libcinder.org
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that
+ the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and
+	the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+	the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "cinder/audio/linux/ContextJack.h"
+#include "cinder/audio/Exception.h"
+
+namespace cinder { namespace audio { namespace linux {
+
+inline void zeroJackPort( jack_port_t *port, jack_nframes_t nframes )
+{   
+  // memset(port, 0, sizeof(jack_default_audio_sample_t) * nframes); 
+}
+
+inline void copyToJackPort(jack_port_t *port, float *source, jack_nframes_t nframes )
+{
+    // dest, source, n 
+    jack_default_audio_sample_t *out;
+    out = (jack_default_audio_sample_t *) jack_port_get_buffer( port, nframes );
+
+    memcpy( out, source, sizeof(jack_default_audio_sample_t) * nframes ) ;
+}
+
+inline void copyFromJackPort(jack_port_t *port, float *dest, jack_nframes_t nframes )
+{
+    // dest, source, n 
+    jack_default_audio_sample_t *in;
+    in = (jack_default_audio_sample_t *) jack_port_get_buffer( port, nframes );
+
+    memcpy( dest, in, sizeof(jack_default_audio_sample_t) * nframes ) ;
+}
+
+
+int OutputDeviceNodeJack::jackCallback(jack_nframes_t nframes, void* userData)
+{
+	RenderData *renderData = static_cast<RenderData *>( userData );
+
+	OutputDeviceNodeJack *outputDeviceNode = static_cast<OutputDeviceNodeJack *>( renderData->outputNode );
+
+	auto ctx = renderData->outputNode->getContext();
+	if( ! ctx ) {
+        for( size_t chan = 0; chan < 2; chan++)
+            // FIXME segfault at shutdown 
+		    zeroJackPort( outputDeviceNode->mOutputPorts[chan], nframes );
+
+		return 0;
+	}
+
+	std::lock_guard<std::mutex> lock( ctx->getMutex() );
+    
+	// verify associated context still exists, which may not be true if we blocked in ~Context() and were then deallocated.
+	ctx = renderData->outputNode->getContext();
+	if( ! ctx ) {
+
+        for( size_t chan = 0; chan < 2; chan++)
+		    zeroJackPort( outputDeviceNode->mOutputPorts[chan], nframes );
+
+		return 0;
+	}
+
+
+	Buffer *internalBuffer = outputDeviceNode->getInternalBuffer();
+	internalBuffer->zero();
+
+	ctx->preProcess();
+	outputDeviceNode->pullInputs( internalBuffer );
+    
+	// if clip detection is enabled and buffer clipped, silence it
+	if( false && outputDeviceNode->checkNotClipping() ){
+        for( size_t chan = 0; chan < 2; chan++)
+		    zeroJackPort( outputDeviceNode->mOutputPorts[chan], nframes );
+	} 
+    else {
+        for( size_t chan = 0; chan < 2; chan++)
+            copyToJackPort( outputDeviceNode->mOutputPorts[chan], internalBuffer->getChannel( chan ), nframes  );
+    }
+
+	ctx->postProcess();
+
+    return 0;
+}
+
+inline void OutputDeviceNodeJack::setInput( InputDeviceNodeRef inputDeviceNode) 
+{
+    mInputDeviceNode = std::static_pointer_cast<InputDeviceNodeJack>(inputDeviceNode);
+}
+
+ContextJack::ContextJack()
+{
+
+}
+
+ContextJack::~ContextJack()
+{
+
+}
+
+
+OutputDeviceNodeRef	ContextJack::createOutputDeviceNode( const DeviceRef &device, const Node::Format &format )
+{
+	
+    if( mOutputDeviceNode  == nullptr ) {
+        auto thisRef = std::static_pointer_cast<ContextJack>( shared_from_this() );
+
+        mOutputDeviceNode = makeNode( new OutputDeviceNodeJack( device, Node::Format().channels(2), thisRef ) ) ;
+
+        if( mInputDeviceNode != nullptr){
+            auto castedOutputDeviceNode = std::static_pointer_cast<OutputDeviceNodeJack>( mOutputDeviceNode );
+            castedOutputDeviceNode->setInput( mInputDeviceNode );   
+        }
+    }
+
+	return mOutputDeviceNode;
+}
+
+InputDeviceNodeRef ContextJack::createInputDeviceNode( const DeviceRef &device, const Node::Format &format  )
+{
+    if( mInputDeviceNode  == nullptr ) {
+        auto thisRef = std::static_pointer_cast<ContextJack>( shared_from_this() );
+
+        mInputDeviceNode = makeNode( new InputDeviceNodeJack( device, Node::Format().channels(2), thisRef ) ) ;
+
+        if( mOutputDeviceNode != nullptr){
+            auto castedOutputDeviceNode = std::static_pointer_cast<OutputDeviceNodeJack>( mOutputDeviceNode );
+            castedOutputDeviceNode->setInput( mInputDeviceNode );   
+        }
+    }
+
+	return mInputDeviceNode;
+}
+
+
+// OutputDeviceNodeJack 
+
+OutputDeviceNodeJack::OutputDeviceNodeJack( const DeviceRef &device, const Format &format, const std::shared_ptr<ContextJack> &context ):
+    OutputDeviceNode( device, format),
+    mCinderContext( context )
+{
+}
+
+void OutputDeviceNodeJack::initialize()
+{
+
+    const char *client_name = "Collidoscope";
+    const char *server_name = NULL;
+    jack_options_t options = JackNullOption;
+    jack_status_t status;
+
+    // connect to JAck server 
+    mClient = jack_client_open (client_name, options, &status, server_name);
+    if( mClient == NULL){
+
+        std::string msg = "jack_client_open() failed. "; 
+        if(status & JackServerFailed) 
+            msg += "Unable to connect to Jack server";
+
+        throw cinder::audio::AudioContextExc(msg);
+    }
+
+    
+    mRenderData.outputNode = this;
+    mRenderData.inputNode = mInputDeviceNode.get();
+    CI_ASSERT(mInputDeviceNode != nullptr);
+    mRenderData.context = static_cast<ContextJack *>( getContext().get() );
+
+    // install callback
+    jack_set_process_callback (mClient, jackCallback, &mRenderData );
+
+    // jack shutdown ? 
+    
+
+    // setup output ports 
+    mOutputPorts[0] = jack_port_register (mClient, "output1",
+                       JACK_DEFAULT_AUDIO_TYPE,
+                       JackPortIsOutput, 0);
+    
+    mOutputPorts[1] = jack_port_register (mClient, "output2",
+                       JACK_DEFAULT_AUDIO_TYPE,
+                       JackPortIsOutput, 0);
+    
+     if ((mOutputPorts[0] == NULL) || (mOutputPorts[0] == NULL)) {
+        throw cinder::audio::AudioContextExc("no more JACK ports available");
+     }
+
+    // setup input ports 
+    mInputDeviceNode->mInputPorts[0] = jack_port_register (mClient, "input1",
+                       JACK_DEFAULT_AUDIO_TYPE,
+                       JackPortIsInput, 0);
+
+    mInputDeviceNode->mInputPorts[1] = jack_port_register (mClient, "input2",
+                       JACK_DEFAULT_AUDIO_TYPE,
+                       JackPortIsInput, 0);
+
+
+    /* Tell the JACK server that we are ready to roll.  Our callback will start running now. */
+    if (jack_activate (mClient)) {
+        throw cinder::audio::AudioContextExc("cannot activate client");
+    }
+
+    // connect input ports to physical device (microphones)
+    const char **mikePorts = jack_get_ports (mClient, NULL, NULL,
+		JackPortIsPhysical|JackPortIsOutput);
+
+	if (mikePorts == NULL) {
+        throw cinder::audio::AudioContextExc("no physical input ports available");
+	}
+
+	if (jack_connect (mClient,  mikePorts[0], jack_port_name (mInputDeviceNode->mInputPorts[0]))) {
+        throw cinder::audio::AudioContextExc("cannot connect input port 0");
+	}
+
+	if (jack_connect (mClient, mikePorts[1], jack_port_name( mInputDeviceNode->mInputPorts[1]) )) {
+        throw cinder::audio::AudioContextExc("cannot connect input port 1");
+	}
+
+    // connect output ports to physical device (audio out )
+    const char **speakerPorts = jack_get_ports (mClient, NULL, NULL,
+				JackPortIsPhysical|JackPortIsInput);
+
+	if (speakerPorts == NULL) {
+        throw cinder::audio::AudioContextExc("no physical output ports available");
+	}
+
+	if (jack_connect (mClient, jack_port_name (mOutputPorts[0]), speakerPorts[0])) {
+        throw cinder::audio::AudioContextExc("cannot connect output port 0");
+	}
+
+	if (jack_connect (mClient, jack_port_name (mOutputPorts[1]), speakerPorts[1])) {
+        throw cinder::audio::AudioContextExc("cannot connect output port 1");
+	}
+
+	jack_free( mikePorts );
+	jack_free( speakerPorts );
+}
+
+
+void OutputDeviceNodeJack::uninitialize()
+{
+    jack_client_close( mClient );
+}
+
+void OutputDeviceNodeJack::enableProcessing()
+{
+}
+
+void OutputDeviceNodeJack::disableProcessing()
+{
+}
+
+
+//-------------------------- InputDeviceNodeJack -------------------------------
+
+
+InputDeviceNodeJack::InputDeviceNodeJack( const DeviceRef &device, const Format &format, const std::shared_ptr<ContextJack> &context ):
+    InputDeviceNode( device, format)
+{
+}
+
+void InputDeviceNodeJack::initialize() 
+{
+}
+
+void InputDeviceNodeJack::uninitialize()
+{
+}
+
+void InputDeviceNodeJack::enableProcessing()
+{
+}
+
+void InputDeviceNodeJack::disableProcessing()
+{
+}
+
+void InputDeviceNodeJack::process( Buffer *buffer )
+{
+    for( size_t chan = 0; chan < 2; chan++){
+       copyFromJackPort(mInputPorts[chan], buffer->getChannel( chan ), buffer->getNumFrames() ); 
+    }
+}
+
+} } } // namespace cinder::audio::linux
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/JackDevice/ContextJack.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,103 @@
+/*
+ Copyright (c) 2015, The Cinder Project
+
+ This code is intended to be used with the Cinder C++ library, http://libcinder.org
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that
+ the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and
+	the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+	the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+
+#include "cinder/audio/Context.h" 
+#include <jack/jack.h>
+
+namespace cinder { namespace audio { namespace linux {
+
+class ContextJack;
+class InputDeviceNodeJack;
+
+class OutputDeviceNodeJack : public OutputDeviceNode {
+  public:
+	OutputDeviceNodeJack( const DeviceRef &device, const Format &format, const std::shared_ptr<ContextJack> &context );
+
+    void setInput(InputDeviceNodeRef inputDeviceNode);
+
+  protected:
+	void initialize()				override;
+	void uninitialize()				override;
+	void enableProcessing()			override;
+	void disableProcessing()		override;
+	bool supportsProcessInPlace() const	override	{ return false; }
+
+  private:
+    static int jackCallback(jack_nframes_t nframes, void* userData);
+
+
+	void renderToBufferFromInputs();
+
+    struct RenderData{
+        RenderData() : inputNode(nullptr), outputNode(nullptr), context(nullptr){}
+        ~RenderData() { inputNode = nullptr; outputNode = nullptr;  context = nullptr; }
+        Node* outputNode;
+        Node* inputNode;
+        ContextJack* context;
+    } mRenderData;
+
+    std::weak_ptr<ContextJack>  mCinderContext;
+
+	jack_client_t *mClient;
+
+    std::array< jack_port_t*, 2 > mOutputPorts;
+
+    std::shared_ptr<InputDeviceNodeJack> mInputDeviceNode;
+};
+
+class InputDeviceNodeJack : public InputDeviceNode {
+  friend OutputDeviceNodeJack;
+
+  public:
+	InputDeviceNodeJack( const DeviceRef &device, const Format &format, const std::shared_ptr<ContextJack> &context );
+
+  protected:
+	void initialize()				override;
+	void uninitialize()				override;
+	void enableProcessing()			override;
+	void disableProcessing()		override;
+	void process( Buffer *buffer )	override;
+
+  private:
+    std::array< jack_port_t*, 2 > mInputPorts;
+};
+
+class ContextJack : public Context {
+  public:
+	ContextJack();
+	virtual ~ContextJack();
+    
+
+	OutputDeviceNodeRef	createOutputDeviceNode( const DeviceRef &device, const Node::Format &format = Node::Format() ) override;
+	InputDeviceNodeRef	createInputDeviceNode( const DeviceRef &device, const Node::Format &format = Node::Format()  ) override;
+
+    OutputDeviceNodeRef	mOutputDeviceNode;
+    InputDeviceNodeRef	mInputDeviceNode;
+
+
+  private:
+};	
+
+} } } // namespace cinder::audio::linux
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/JackDevice/DeviceManagerJack.h	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,61 @@
+/*
+ Copyright (c) 2015, The Cinder Project
+
+ This code is intended to be used with the Cinder C++ library, http://libcinder.org
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that
+ the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and
+	the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+	the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+
+#include "cinder/audio/Device.h"
+
+namespace cinder { namespace audio { namespace linux {
+
+class DeviceManagerJack : public DeviceManager {
+  public:
+
+	DeviceManagerJack();
+	virtual ~DeviceManagerJack();
+
+	const std::vector<DeviceRef>& getDevices()		override;
+	DeviceRef getDefaultOutput()				override;
+	DeviceRef getDefaultInput()				override;
+
+	std::string getName( const DeviceRef &device ) 		override;
+	size_t getNumInputChannels( const DeviceRef &device )	override;
+	size_t getNumOutputChannels( const DeviceRef &device )	override;
+	size_t getSampleRate( const DeviceRef &device )		override;
+	size_t getFramesPerBlock( const DeviceRef &device )	override;
+
+	void setSampleRate( const DeviceRef &device, size_t sampleRate )		override;
+	void setFramesPerBlock( const DeviceRef &device, size_t framesPerBlock )	override;
+
+	//! Returns the hardware's actual frames per block, which might not be a power of two.
+	size_t getFramesPerBlockHardware( const DeviceRef &device );
+
+private:
+
+	std::vector<DeviceRef> mDevices;
+	DeviceRef   mDefaultOutDevice;
+	DeviceRef   mDefaultInDevice;
+    size_t mSampleRate;
+    size_t mBufferSize;
+};	
+
+} } } // namespace cinder::audio::linux 	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TeensyCode/collidoscope_double_knob.ino	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,310 @@
+
+/******************************************************************************
+
+ *  Ben Bengler
+ *  mail@benbengler.com
+ *  02.05.2016
+ *
+ *  Collidoscope 
+ *  
+ *  Teensy 2 ++ pinout: https://www.pjrc.com/teensy/card4b.pdf
+ *  
+ *  ANALOG INPUTS:
+ *  Wavejet   -> F0 (38) [Horizontal rail 1]
+ *  Wavejet   -> F1 (39) [Horizontal rail 2]
+ *  Filter 1  -> F2 (40) [Vertical rail 1]
+ *  Filter 2  -> F4 (42) [Vertical rail 2] 
+ *
+ *  DIGITAL INPUTS [INTERRUPTS]:
+ *  Sel. length 1  -> INT0/INT1 (0, 1)  [Encoder 1]
+ *  Sel. length 2  -> INT2/INT3 (2, 3)  [Encoder 2]
+ *  Duration 1     -> INT4/INT5 (36, 37)[Encoder 3] 
+ *  Duration 2     -> INT6/INT7 (18, 19)[Encoder 4] 
+ *
+ *  DIGITAL INPUTS:
+ *  Play1 toggle  -> B0 (20)
+ *  Record1       -> B1 (21)
+ *  Play2 toggle  -> B3 (22)
+ *  Record2       -> B4 (24)
+ *  
+ *  DIGITAL OUTPUTS:
+ *  Record Button 1 Led -> D4 (4)
+ *  Record Button 2 Led -> D5 (5)
+
+ ******************************************************************************/ 
+
+
+#include <Encoder.h>
+#include <Bounce.h>
+
+///////////////////////////////////////////////////
+//MIDI settings
+const int midi_chan_inst1 = 1; // MIDI channel for Instrument 1
+const int midi_chan_inst2 = 2; // MIDI channel for Instrument 2
+
+const int cc_length = 1;    // MIDI cc controlling selection length
+const int cc_duration = 2;  // MIDI cc controlling duration
+const int cc_filter = 7;    // MIDI cc controlling LP filter
+const int cc_play = 4;      // MIDI cc controlling PLAY
+const int cc_record = 5;    // MIDI cc controlling RECORD
+//const int cc_reset = 100; // MIDI cc controlling instrument RESET
+
+///////////////////////////////////////////////////
+//Default Values:
+const int  Enc_def = 64; //default selection length  
+int MIDI_led_state = LOW;
+//boolean reset1 = true;
+//boolean reset2 = true;
+
+///////////////////////////////////////////////////
+// Interface Inputs
+
+//Buttons:
+
+const int Pin_play1 = 20;       //B0    
+const int Pin_record1 = 21;     //B1
+const int Pin_play2 = 23;       //B3
+const int Pin_record2 = 24;     //B4
+const int Pin_record1_led =  4; //D4
+const int Pin_record2_led =  5; //D5
+const int Pin_MIDIled =  6;
+
+//const int Pin_reset1 = 22;    //B2, not in use
+//const int Pin_reset2 = 25;    //B5, not in use
+
+Bounce button1 = Bounce(Pin_play1, 5); 
+Bounce button2 = Bounce(Pin_record1, 5); 
+Bounce button3 = Bounce(Pin_play2, 5); 
+Bounce button4 = Bounce(Pin_record2, 5); 
+
+ 
+//Encoder
+Encoder Enc1 (0, 1);  //Encoder for section length on Wavejet 1
+Encoder Enc2 (2, 3);  //Encoder for section length on Wavejet 2
+
+
+// Variables
+const int jitter_thresh = 10; //7threshold value for analog INs to suppress sending MIDI due to input jitter 
+
+void setup() {  
+
+pinMode(Pin_play1, INPUT_PULLUP); 
+pinMode(Pin_record1, INPUT_PULLUP);
+pinMode(Pin_play2, INPUT_PULLUP);
+pinMode(Pin_record2, INPUT_PULLUP);
+
+
+pinMode(Pin_MIDIled, OUTPUT); 
+pinMode(Pin_record1_led, OUTPUT);
+pinMode(Pin_record2_led, OUTPUT);
+}
+
+//Store recent values to detect parameter change
+long Enc1_old = -999;
+long Enc2_old = -999;
+
+uint16_t Jet1_old = 0;
+int16_t Jet1_old_MIDI = -1;
+uint16_t Jet2_old = 0;
+int16_t Jet2_old_MIDI = -1;
+
+uint16_t filter1_old = 0;
+int16_t filter1_old_MIDI = -1;
+uint16_t filter2_old = 0;
+int16_t filter2_old_MIDI = -1;
+
+uint16_t dur1_old = 0;
+int16_t dur1_old_MIDI = -1;
+uint16_t dur2_old = 0;
+int16_t dur2_old_MIDI = -1;
+
+void loop() {
+
+  digitalWrite(Pin_MIDIled, LOW);
+  digitalWrite(Pin_record1_led, HIGH);
+  digitalWrite(Pin_record2_led, HIGH);
+  button1.update();
+  button2.update();
+  button3.update();
+  button4.update();
+
+  
+  uint16_t Jet1_new = analogRead(0); //read Wavejet/Rail 1
+  uint16_t Jet2_new = analogRead(1); //read Wavejet/Rail 2
+  uint16_t filter1_new = analogRead(2);   //read filter Instrument 1; ADJUST INPUT RANGE ACCORDING TO SENSOR    
+  uint16_t filter2_new = analogRead(4);   //read filter Instrument 2; ADJUST INPUT RANGE ACCORDING TO SENSOR  
+  uint16_t dur1_new = analogRead(3); 
+  uint16_t dur2_new = analogRead(5);
+
+  
+  //Encoder 1 [Controls selection length of wave 1]
+  long Enc1_new = Enc1.read();
+  Enc1_new = constrain(Enc1_new, 0, 127); //constrain to 7-bit MIDI range
+  
+  //Dynamic reset of counter to MIDI range 
+  if (Enc1_new <= 0){
+    Enc1.write(0); 
+  }
+  else if (Enc1_new >= 127){
+    Enc1.write(127); 
+  }
+     
+  //Encoder 2 [Controls selection length of wave 2]
+  long Enc2_new = Enc2.read();
+  Enc2_new = constrain(Enc2_new, 0, 127); //constrain to 7-bit MIDI range
+  
+  //Dynamic reset of counter to MIDI range 
+  if (Enc2_new <= 0){
+    Enc2.write(0); 
+  }
+  else if (Enc2_new >= 127){
+    Enc2.write(127); 
+  }
+
+ //Instrument 1 Controls//////////////////////////////////////  
+ 
+ //Loop/Keymode Switch Instrument 1 
+   
+ if (button1.risingEdge()) {   
+   //Serial.println("Loop mode");
+   usbMIDI.sendControlChange(cc_play, 1, midi_chan_inst1);
+ }
+   
+ if (button1.fallingEdge()) {   
+   //Serial.println("Keyboardmode mode");
+   usbMIDI.sendControlChange(cc_play, 0, midi_chan_inst1);
+ }
+     
+   
+ //Record Instrument 1
+ if (button2.fallingEdge()) {   
+   //Serial.println("RECORD! Instrument 1");
+   usbMIDI.sendControlChange(cc_record, 1, midi_chan_inst1);
+ }
+
+ //send MIDI Wavejet 1 [Position Instrument 1]
+ 
+ if (Jet1_new > Jet1_old+jitter_thresh || Jet1_new < Jet1_old-jitter_thresh) {
+    
+    int16_t midiVal = constrain( map(Jet1_new, 24, 926, 0, 149), 0, 149 );
+    // int16_t midiVal = constrain( map( Jet1_new, 23, 928, 0, 149 ), 0, 149 ); old collidoscope 
+    if( midiVal != Jet1_old_MIDI ){
+      Jet1_old_MIDI = midiVal;
+      usbMIDI.sendPitchBend( midiVal, midi_chan_inst1 );
+    }
+
+    Jet1_old = Jet1_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+ }
+
+
+ //send MIDI Filter 1 [Filter Instrument 1]
+
+ if ( filter1_new != filter1_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+
+    int16_t midiVal = constrain( map(filter1_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != filter1_old_MIDI){
+      //Serial.println( midiVal );
+      filter1_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_filter, midiVal, midi_chan_inst1);
+    }
+    
+    filter1_old = filter1_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+
+  if ( dur1_new != dur1_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+ 
+    int16_t midiVal = constrain( map(dur1_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != dur1_old_MIDI){
+      //Serial.println( midiVal );
+      dur1_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_duration, midiVal, midi_chan_inst1);
+    }
+    
+    dur1_old = dur1_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+
+  //send MIDI Encoder 1 [Selection length Instrument 1]
+  if (Enc1_new != Enc1_old) {
+    Enc1_old = Enc1_new;
+    //Serial.println("Encoder 1: ");
+    //Serial.println(Enc1_new);
+    usbMIDI.sendControlChange(cc_length, Enc1_new, midi_chan_inst1);
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+
+  
+  
+  //Instrument 2 Controls//////////////////////////////////////  
+  
+  //Loop/Keymode Switch Instrument 2
+   
+  if (button3.risingEdge()) {   
+    //Serial.println("Loop mode");
+    usbMIDI.sendControlChange(cc_play, 1, midi_chan_inst2);
+  }
+  
+  if (button3.fallingEdge()) {   
+    //Serial.println("Keyboardmode mode");
+    usbMIDI.sendControlChange(cc_play, 0, midi_chan_inst2);
+  }
+  
+  //Record Instrument 2
+  if (button4.fallingEdge()) {   
+    //Serial.println("RECORD! Instrument 2");
+    usbMIDI.sendControlChange(cc_record, 1, midi_chan_inst2);
+  }
+
+  //send MIDI Wavejet 2 [Position Instrument 2]
+  
+  if (Jet2_new > Jet2_old+jitter_thresh || Jet2_new < Jet2_old-jitter_thresh) {
+    //Serial.println("RECORD! Instrument 2");
+    int16_t midiVal = constrain( map( Jet2_new, 925, 18, 149, 0 ), 0, 149 );
+    if( midiVal != Jet2_old_MIDI ){
+      Jet2_old_MIDI = midiVal;
+      usbMIDI.sendPitchBend( midiVal, midi_chan_inst2 );
+    }
+
+    Jet2_old = Jet2_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+  
+ //Serial.println(filter2_new);
+   if ( filter2_new != filter2_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+    int16_t midiVal = constrain( map(filter2_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != filter2_old_MIDI){
+      //Serial.println( midiVal );
+      filter2_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_filter, midiVal, midi_chan_inst2);
+    }
+    
+    filter2_old = filter2_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+   }
+
+   if ( dur2_new != dur2_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+    int16_t midiVal = constrain( map(dur2_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != dur2_old_MIDI){
+      //Serial.println( midiVal );
+      dur2_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_duration, midiVal, midi_chan_inst2);
+    }
+    
+    dur2_old = dur2_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+   }
+   
+  
+  //send MIDI Encoder 2 [Selection length Instrument 2]
+  if (Enc2_new != Enc2_old) {
+    Enc2_old = Enc2_new;
+    //Serial.println("Encoder 2: ");
+    //Serial.println(Enc2_new);
+    usbMIDI.sendControlChange(cc_length, Enc2_new, midi_chan_inst2);
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TeensyCode/collidoscope_single_knob.ino	Thu Jun 30 14:50:06 2016 +0200
@@ -0,0 +1,310 @@
+
+/******************************************************************************
+
+ *  Ben Bengler
+ *  mail@benbengler.com
+ *  02.05.2016
+ *
+ *  Collidoscope 
+ *  
+ *  Teensy 2 ++ pinout: https://www.pjrc.com/teensy/card4b.pdf
+ *  
+ *  ANALOG INPUTS:
+ *  Wavejet   -> F0 (38) [Horizontal rail 1]
+ *  Wavejet   -> F1 (39) [Horizontal rail 2]
+ *  Filter 1  -> F2 (40) [Vertical rail 1]
+ *  Filter 2  -> F4 (42) [Vertical rail 2] 
+ *
+ *  DIGITAL INPUTS [INTERRUPTS]:
+ *  Sel. length 1  -> INT0/INT1 (0, 1)  [Encoder 1]
+ *  Sel. length 2  -> INT2/INT3 (2, 3)  [Encoder 2]
+ *  Duration 1     -> INT4/INT5 (36, 37)[Encoder 3] 
+ *  Duration 2     -> INT6/INT7 (18, 19)[Encoder 4] 
+ *
+ *  DIGITAL INPUTS:
+ *  Play1 toggle  -> B0 (20)
+ *  Record1       -> B1 (21)
+ *  Play2 toggle  -> B3 (22)
+ *  Record2       -> B4 (24)
+ *  
+ *  DIGITAL OUTPUTS:
+ *  Record Button 1 Led -> D4 (4)
+ *  Record Button 2 Led -> D5 (5)
+
+ ******************************************************************************/ 
+
+
+#include <Encoder.h>
+#include <Bounce.h>
+
+///////////////////////////////////////////////////
+//MIDI settings
+const int midi_chan_inst1 = 1; // MIDI channel for Instrument 1
+const int midi_chan_inst2 = 2; // MIDI channel for Instrument 2
+
+const int cc_length = 1;    // MIDI cc controlling selection length
+const int cc_duration = 2;  // MIDI cc controlling duration
+const int cc_filter = 7;    // MIDI cc controlling LP filter
+const int cc_play = 4;      // MIDI cc controlling PLAY
+const int cc_record = 5;    // MIDI cc controlling RECORD
+//const int cc_reset = 100; // MIDI cc controlling instrument RESET
+
+///////////////////////////////////////////////////
+//Default Values:
+const int  Enc_def = 64; //default selection length  
+int MIDI_led_state = LOW;
+//boolean reset1 = true;
+//boolean reset2 = true;
+
+///////////////////////////////////////////////////
+// Interface Inputs
+
+//Buttons:
+
+const int Pin_play1 = 20;       //B0    
+const int Pin_record1 = 21;     //B1
+const int Pin_play2 = 23;       //B3
+const int Pin_record2 = 24;     //B4
+const int Pin_record1_led =  4; //D4
+const int Pin_record2_led =  5; //D5
+const int Pin_MIDIled =  6;
+
+//const int Pin_reset1 = 22;    //B2, not in use
+//const int Pin_reset2 = 25;    //B5, not in use
+
+Bounce button1 = Bounce(Pin_play1, 5); 
+Bounce button2 = Bounce(Pin_record1, 5); 
+Bounce button3 = Bounce(Pin_play2, 5); 
+Bounce button4 = Bounce(Pin_record2, 5); 
+
+ 
+//Encoder
+Encoder Enc1 (0, 1);  //Encoder for section length on Wavejet 1
+Encoder Enc2 (2, 3);  //Encoder for section length on Wavejet 2
+
+
+// Variables
+const int jitter_thresh = 10; //7threshold value for analog INs to suppress sending MIDI due to input jitter 
+
+void setup() {  
+
+pinMode(Pin_play1, INPUT_PULLUP); 
+pinMode(Pin_record1, INPUT_PULLUP);
+pinMode(Pin_play2, INPUT_PULLUP);
+pinMode(Pin_record2, INPUT_PULLUP);
+
+
+pinMode(Pin_MIDIled, OUTPUT); 
+pinMode(Pin_record1_led, OUTPUT);
+pinMode(Pin_record2_led, OUTPUT);
+}
+
+//Store recent values to detect parameter change
+long Enc1_old = -999;
+long Enc2_old = -999;
+
+uint16_t Jet1_old = 0;
+int16_t Jet1_old_MIDI = -1;
+uint16_t Jet2_old = 0;
+int16_t Jet2_old_MIDI = -1;
+
+uint16_t filter1_old = 0;
+int16_t filter1_old_MIDI = -1;
+uint16_t filter2_old = 0;
+int16_t filter2_old_MIDI = -1;
+
+uint16_t dur1_old = 0;
+int16_t dur1_old_MIDI = -1;
+uint16_t dur2_old = 0;
+int16_t dur2_old_MIDI = -1;
+
+void loop() {
+
+  digitalWrite(Pin_MIDIled, LOW);
+  digitalWrite(Pin_record1_led, HIGH);
+  digitalWrite(Pin_record2_led, HIGH);
+  button1.update();
+  button2.update();
+  button3.update();
+  button4.update();
+
+  
+  uint16_t Jet1_new = analogRead(0); //read Wavejet/Rail 1
+  uint16_t Jet2_new = analogRead(1); //read Wavejet/Rail 2
+  uint16_t filter1_new = analogRead(2);   //read filter Instrument 1; ADJUST INPUT RANGE ACCORDING TO SENSOR    
+  uint16_t filter2_new = analogRead(4);   //read filter Instrument 2; ADJUST INPUT RANGE ACCORDING TO SENSOR  
+  uint16_t dur1_new = analogRead(3); 
+  uint16_t dur2_new = analogRead(5);
+
+  
+  //Encoder 1 [Controls selection length of wave 1]
+  long Enc1_new = Enc1.read();
+  Enc1_new = constrain(Enc1_new, 0, 127); //constrain to 7-bit MIDI range
+  
+  //Dynamic reset of counter to MIDI range 
+  if (Enc1_new <= 0){
+    Enc1.write(0); 
+  }
+  else if (Enc1_new >= 127){
+    Enc1.write(127); 
+  }
+     
+  //Encoder 2 [Controls selection length of wave 2]
+  long Enc2_new = Enc2.read();
+  Enc2_new = constrain(Enc2_new, 0, 127); //constrain to 7-bit MIDI range
+  
+  //Dynamic reset of counter to MIDI range 
+  if (Enc2_new <= 0){
+    Enc2.write(0); 
+  }
+  else if (Enc2_new >= 127){
+    Enc2.write(127); 
+  }
+
+ //Instrument 1 Controls//////////////////////////////////////  
+ 
+ //Loop/Keymode Switch Instrument 1 
+   
+ if (button1.risingEdge()) {   
+   //Serial.println("Loop mode");
+   usbMIDI.sendControlChange(cc_play, 1, midi_chan_inst1);
+ }
+   
+ if (button1.fallingEdge()) {   
+   //Serial.println("Keyboardmode mode");
+   usbMIDI.sendControlChange(cc_play, 0, midi_chan_inst1);
+ }
+     
+   
+ //Record Instrument 1
+ if (button2.fallingEdge()) {   
+   //Serial.println("RECORD! Instrument 1");
+   usbMIDI.sendControlChange(cc_record, 1, midi_chan_inst1);
+ }
+
+ //send MIDI Wavejet 1 [Position Instrument 1]
+ 
+ if (Jet1_new > Jet1_old+jitter_thresh || Jet1_new < Jet1_old-jitter_thresh) {
+    
+    int16_t midiVal = constrain( map(Jet1_new, 24, 926, 0, 149), 0, 149 );
+    // int16_t midiVal = constrain( map( Jet1_new, 23, 928, 0, 149 ), 0, 149 ); old collidoscope 
+    if( midiVal != Jet1_old_MIDI ){
+      Jet1_old_MIDI = midiVal;
+      usbMIDI.sendPitchBend( midiVal, midi_chan_inst1 );
+    }
+
+    Jet1_old = Jet1_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+ }
+
+
+ //send MIDI Filter 1 [Filter Instrument 1]
+
+ if ( filter1_new != filter1_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+
+    int16_t midiVal = constrain( map(filter1_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != filter1_old_MIDI){
+      //Serial.println( midiVal );
+      filter1_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_filter, midiVal, midi_chan_inst1);
+    }
+    
+    filter1_old = filter1_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+
+  if ( dur1_new != dur1_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+ 
+    int16_t midiVal = constrain( map(dur1_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != dur1_old_MIDI){
+      //Serial.println( midiVal );
+      dur1_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_duration, midiVal, midi_chan_inst1);
+    }
+    
+    dur1_old = dur1_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+
+  //send MIDI Encoder 1 [Selection length Instrument 1]
+  if (Enc1_new != Enc1_old) {
+    Enc1_old = Enc1_new;
+    //Serial.println("Encoder 1: ");
+    //Serial.println(Enc1_new);
+    usbMIDI.sendControlChange(cc_length, Enc1_new, midi_chan_inst1);
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+
+  
+  
+  //Instrument 2 Controls//////////////////////////////////////  
+  
+  //Loop/Keymode Switch Instrument 2
+   
+  if (button3.risingEdge()) {   
+    //Serial.println("Loop mode");
+    usbMIDI.sendControlChange(cc_play, 1, midi_chan_inst2);
+  }
+  
+  if (button3.fallingEdge()) {   
+    //Serial.println("Keyboardmode mode");
+    usbMIDI.sendControlChange(cc_play, 0, midi_chan_inst2);
+  }
+  
+  //Record Instrument 2
+  if (button4.fallingEdge()) {   
+    //Serial.println("RECORD! Instrument 2");
+    usbMIDI.sendControlChange(cc_record, 1, midi_chan_inst2);
+  }
+
+  //send MIDI Wavejet 2 [Position Instrument 2]
+  
+  if (Jet2_new > Jet2_old+jitter_thresh || Jet2_new < Jet2_old-jitter_thresh) {
+    //Serial.println("RECORD! Instrument 2");
+    int16_t midiVal = constrain( map( Jet2_new, 925, 18, 149, 0 ), 0, 149 );
+    if( midiVal != Jet2_old_MIDI ){
+      Jet2_old_MIDI = midiVal;
+      usbMIDI.sendPitchBend( midiVal, midi_chan_inst2 );
+    }
+
+    Jet2_old = Jet2_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+  
+ //Serial.println(filter2_new);
+   if ( filter2_new != filter2_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+    int16_t midiVal = constrain( map(filter2_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != filter2_old_MIDI){
+      //Serial.println( midiVal );
+      filter2_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_filter, midiVal, midi_chan_inst2);
+    }
+    
+    filter2_old = filter2_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+   }
+
+   if ( dur2_new != dur2_old ) {         // maybe adding jitter threshold needed, see Jet1_new  
+    int16_t midiVal = constrain( map(dur2_new, 0, 1024, 0, 127), 0, 127 );
+    if( midiVal != dur2_old_MIDI){
+      //Serial.println( midiVal );
+      dur2_old_MIDI = midiVal;
+      usbMIDI.sendControlChange(cc_duration, midiVal, midi_chan_inst2);
+    }
+    
+    dur2_old = dur2_new;
+    digitalWrite(Pin_MIDIled, HIGH);
+   }
+   
+  
+  //send MIDI Encoder 2 [Selection length Instrument 2]
+  if (Enc2_new != Enc2_old) {
+    Enc2_old = Enc2_new;
+    //Serial.println("Encoder 2: ");
+    //Serial.println(Enc2_new);
+    usbMIDI.sendControlChange(cc_length, Enc2_new, midi_chan_inst2);
+    digitalWrite(Pin_MIDIled, HIGH);
+  }
+    
+}
+