Mercurial > hg > opencollidoscope
changeset 0:02467299402e
First import
CollidoscopeApp for Raspberry Pi
JackDevice
Teensy code for Collidoscope
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 © ) = delete; + AudioEngine & operator=(const AudioEngine ©) = 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 © ) = delete; + Config & operator=(const Config ©) = 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 © ) = delete; + RingBufferPack & operator=(const RingBufferPack ©) = 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 © ) = delete; + Wave & operator=(const Wave ©) = 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); + } + +} +