diff --git a/Makefile.in b/Makefile.in index 82ad7ad7..4d8dabfd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -9,8 +9,13 @@ OBJECTS = RtMidi.o LIBNAME = librtmidi STATIC = $(LIBNAME).a SHARED = @sharedlib@ +<<<<<<< HEAD RELEASE = 2.1.0 MAJOR = 2 +======= +RELEASE = 3.0.0 +MAJOR = 3 +>>>>>>> Bump library version. LIBRARIES = $(STATIC) $(SHARED) CC = @CXX@ @@ -22,6 +27,8 @@ CFLAGS = @CXXFLAGS@ -Iinclude -fPIC PREFIX = @prefix@ +.PHONY: tests + all : $(LIBRARIES) tests: diff --git a/RtMidi.cpp b/RtMidi.cpp index 2101c377..03488a75 100644 --- a/RtMidi.cpp +++ b/RtMidi.cpp @@ -1,377 +1,420 @@ /**********************************************************************/ -/*! \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-2014 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. +/*! \class Midi + \brief An abstract base class for realtime MIDI input/output. + + This class implements some common functionality for the realtime + MIDI input/output subclasses MidiIn and MidiOut. + + Midi WWW site: http://music.mcgill.ca/~gary/rtmidi/ + + Midi: realtime MIDI i/o C++ classes + Copyright (c) 2003-2014 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 +#include + +namespace rtmidi { + //*********************************************************************// + // Midi Definitions + //*********************************************************************// + std::string Midi :: getVersion( void ) throw() + { + return std::string( RTMIDI_VERSION ); + } -//*********************************************************************// -// 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 &apis ) throw() -{ - apis.clear(); + void Midi :: getCompiledApi( std::vector &apis ) throw() + { + apis.clear(); - // The order here will control the order of RtMidi's API search in - // the constructor. + // The order here will control the order of RtMidi's API search in + // the constructor. #if defined(__MACOSX_CORE__) - apis.push_back( MACOSX_CORE ); + apis.push_back( rtmidi::MACOSX_CORE ); #endif #if defined(__LINUX_ALSA__) - apis.push_back( LINUX_ALSA ); + apis.push_back( rtmidi::LINUX_ALSA ); #endif #if defined(__UNIX_JACK__) - apis.push_back( UNIX_JACK ); + apis.push_back( rtmidi::UNIX_JACK ); #endif #if defined(__WINDOWS_MM__) - apis.push_back( WINDOWS_MM ); + apis.push_back( rtmidi::WINDOWS_MM ); #endif #if defined(__WINDOWS_KS__) - apis.push_back( WINDOWS_KS ); + apis.push_back( rtmidi::WINDOWS_KS ); #endif #if defined(__RTMIDI_DUMMY__) - apis.push_back( RTMIDI_DUMMY ); + apis.push_back( rtmidi::RTMIDI_DUMMY ); #endif -} + } -//*********************************************************************// -// RtMidiIn Definitions -//*********************************************************************// + void Midi :: error( Error::Type type, std::string errorString ) + { +#if 0 + if ( errorCallback_ ) { + static bool firstErrorOccured = false; + + if ( firstErrorOccured ) + return; + + firstErrorOccured = true; + const std::string errorMessage = errorString; + + errorCallback_( type, errorMessage ); + firstErrorOccured = false; + return; + } +#endif + + if ( type == Error::WARNING ) { + std::cerr << '\n' << errorString << "\n\n"; + } + else if ( type == Error::DEBUG_WARNING ) { +#if defined(__RTMIDI_DEBUG__) + std::cerr << '\n' << errorString << "\n\n"; +#endif + } + else { + std::cerr << '\n' << errorString << "\n\n"; + throw Error( errorString, type ); + } + } + + + + + //*********************************************************************// + // MidiIn Definitions + //*********************************************************************// -void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) -{ - if ( rtapi_ ) - delete rtapi_; - rtapi_ = 0; + void MidiIn :: openMidiApi( ApiType 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 ); + if ( api == rtmidi::UNIX_JACK ) + rtapi_ = new MidiInJack( clientName, queueSizeLimit ); #endif #if defined(__LINUX_ALSA__) - if ( api == LINUX_ALSA ) - rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); + if ( api == rtmidi::LINUX_ALSA ) + rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); #endif #if defined(__WINDOWS_MM__) - if ( api == WINDOWS_MM ) - rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); + if ( api == rtmidi::WINDOWS_MM ) + rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); #endif #if defined(__WINDOWS_KS__) - if ( api == WINDOWS_KS ) - rtapi_ = new MidiInWinKS( clientName, queueSizeLimit ); + if ( api == rtmidi::WINDOWS_KS ) + rtapi_ = new MidiInWinKS( clientName, queueSizeLimit ); #endif #if defined(__MACOSX_CORE__) - if ( api == MACOSX_CORE ) - rtapi_ = new MidiInCore( clientName, queueSizeLimit ); + if ( api == rtmidi::MACOSX_CORE ) + rtapi_ = new MidiInCore( clientName, queueSizeLimit ); #endif #if defined(__RTMIDI_DUMMY__) - if ( api == RTMIDI_DUMMY ) - rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); + if ( api == rtmidi::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; igetPortCount() ) 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 ) ); -} + MidiIn :: MidiIn( ApiType api, const std::string clientName, unsigned int queueSizeLimit ) + : Midi() + { + if ( api != rtmidi::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 << "\nMidiIn: 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< ApiType > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; igetPortCount() ) 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 = "MidiIn: no compiled API support found ... critical error!!"; + throw( Error( errorText, Error::UNSPECIFIED ) ); + } -RtMidiIn :: ~RtMidiIn() throw() -{ -} + MidiIn :: ~MidiIn() throw() + { + } -//*********************************************************************// -// RtMidiOut Definitions -//*********************************************************************// + //*********************************************************************// + // MidiOut Definitions + //*********************************************************************// -void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName ) -{ - if ( rtapi_ ) - delete rtapi_; - rtapi_ = 0; + void MidiOut :: openMidiApi( ApiType api, const std::string clientName ) + { + if ( rtapi_ ) + delete rtapi_; + rtapi_ = 0; #if defined(__UNIX_JACK__) - if ( api == UNIX_JACK ) - rtapi_ = new MidiOutJack( clientName ); + if ( api == rtmidi::UNIX_JACK ) + rtapi_ = new MidiOutJack( clientName ); #endif #if defined(__LINUX_ALSA__) - if ( api == LINUX_ALSA ) - rtapi_ = new MidiOutAlsa( clientName ); + if ( api == rtmidi::LINUX_ALSA ) + rtapi_ = new MidiOutAlsa( clientName ); #endif #if defined(__WINDOWS_MM__) - if ( api == WINDOWS_MM ) - rtapi_ = new MidiOutWinMM( clientName ); + if ( api == rtmidi::WINDOWS_MM ) + rtapi_ = new MidiOutWinMM( clientName ); #endif #if defined(__WINDOWS_KS__) - if ( api == WINDOWS_KS ) - rtapi_ = new MidiOutWinKS( clientName ); + if ( api == rtmidi::WINDOWS_KS ) + rtapi_ = new MidiOutWinKS( clientName ); #endif #if defined(__MACOSX_CORE__) - if ( api == MACOSX_CORE ) - rtapi_ = new MidiOutCore( clientName ); + if ( api == rtmidi::MACOSX_CORE ) + rtapi_ = new MidiOutCore( clientName ); #endif #if defined(__RTMIDI_DUMMY__) - if ( api == RTMIDI_DUMMY ) - rtapi_ = new MidiOutDummy( clientName ); + if ( api == rtmidi::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; igetPortCount() ) 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 ) ); -} + MidiOut :: MidiOut( ApiType api, const std::string clientName ) + { + if ( api != rtmidi::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 << "\nMidiOut: 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< ApiType > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; igetPortCount() ) 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 = "MidiOut: no compiled API support found ... critical error!!"; + throw( Error( errorText, Error::UNSPECIFIED ) ); + } -RtMidiOut :: ~RtMidiOut() throw() -{ -} + MidiOut :: ~MidiOut() throw() + { + } -//*********************************************************************// -// Common MidiApi Definitions -//*********************************************************************// + //*********************************************************************// + // Common MidiApi Definitions + //*********************************************************************// -MidiApi :: MidiApi( void ) - : apiData_( 0 ), connected_( false ), errorCallback_(0) -{ -} + MidiApi :: MidiApi( void ) + : apiData_( 0 ), connected_( false ), errorCallback_(0) + { + } -MidiApi :: ~MidiApi( void ) -{ -} + MidiApi :: ~MidiApi( void ) + { + } -void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback ) -{ - errorCallback_ = errorCallback; -} + void MidiApi :: setErrorCallback( ErrorCallback errorCallback ) + { + errorCallback_ = errorCallback; + } -void MidiApi :: error( RtMidiError::Type type, std::string errorString ) -{ - if ( errorCallback_ ) { - static bool firstErrorOccured = false; + void MidiApi :: error( Error::Type type, std::string errorString ) + { + if ( errorCallback_ ) { + static bool firstErrorOccured = false; - if ( firstErrorOccured ) - return; + if ( firstErrorOccured ) + return; - firstErrorOccured = true; - const std::string errorMessage = errorString; + firstErrorOccured = true; + const std::string errorMessage = errorString; - errorCallback_( type, errorMessage ); - firstErrorOccured = false; - return; - } + errorCallback_( type, errorMessage ); + firstErrorOccured = false; + return; + } - if ( type == RtMidiError::WARNING ) { - std::cerr << '\n' << errorString << "\n\n"; - } - else if ( type == RtMidiError::DEBUG_WARNING ) { + if ( type == Error::WARNING ) { + std::cerr << '\n' << errorString << "\n\n"; + } + else if ( type == Error::DEBUG_WARNING ) { #if defined(__RTMIDI_DEBUG__) - std::cerr << '\n' << errorString << "\n\n"; + std::cerr << '\n' << errorString << "\n\n"; #endif - } - else { - std::cerr << '\n' << errorString << "\n\n"; - throw RtMidiError( errorString, type ); - } -} + } + else { + std::cerr << '\n' << errorString << "\n\n"; + throw Error( errorString, type ); + } + } -//*********************************************************************// -// Common MidiInApi Definitions -//*********************************************************************// + //*********************************************************************// + // 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( 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; + } -MidiInApi :: ~MidiInApi( void ) -{ - // Delete the MIDI queue. - if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; -} + void MidiInApi :: setCallback( MidiCallback callback, void *userData ) + { + if ( inputData_.usingCallback ) { + errorString_ = "MidiInApi::setCallback: a callback function is already set!"; + error( Error::WARNING, errorString_ ); + return; + } + + if ( !callback ) { + errorString_ = "MidiIn::setCallback: callback function value is invalid!"; + error( Error::WARNING, errorString_ ); + return; + } + + inputData_.userCallback = callback; + inputData_.userData = userData; + inputData_.usingCallback = true; + } -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_ = "MidiIn::cancelCallback: no callback function was set!"; + error( Error::WARNING, errorString_ ); + return; + } + + inputData_.userCallback = 0; + inputData_.userData = 0; + inputData_.usingCallback = false; + } -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; + } -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 &message ) + { + message.clear(); -double MidiInApi :: getMessage( std::vector *message ) -{ - message->clear(); + if ( inputData_.usingCallback ) { + errorString_ = "MidiIn::getMessage: a user callback is currently set for this port."; + error( Error::WARNING, errorString_ ); + return 0.0; + } - 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; - if ( inputData_.queue.size == 0 ) return 0.0; + // Copy queued message to the vector pointer argument and then "pop" it. + std::vector *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; - // Copy queued message to the vector pointer argument and then "pop" it. - std::vector *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; + } - return deltaTime; -} + //*********************************************************************// + // Common MidiOutApi Definitions + //*********************************************************************// -//*********************************************************************// -// Common MidiOutApi Definitions -//*********************************************************************// + MidiOutApi :: MidiOutApi( void ) + : MidiApi() + { + } + + MidiOutApi :: ~MidiOutApi( void ) + { + } -MidiOutApi :: MidiOutApi( void ) - : MidiApi() -{ -} -MidiOutApi :: ~MidiOutApi( void ) -{ + // trim from start + static inline std::string <rim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; + } + + // trim from end + static inline std::string &rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; + } + + // trim from both ends + static inline std::string &trim(std::string &s) { + return ltrim(rtrim(s)); + } } // *************************************************** // @@ -391,712 +434,1689 @@ MidiOutApi :: ~MidiOutApi( void ) #include #include -// 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 -//*********************************************************************// +namespace rtmidi { + /*! An abstraction layer for the CORE sequencer layer. It provides + the following functionality: + - dynamic allocation of the sequencer + - optionallay avoid concurrent access to the CORE sequencer, + which is not thread proof. This feature is controlled by + the parameter \ref locking. + */ + + // This function was submitted by Douglas Casey Tucker and apparently + // derived largely from PortMidi. + // or copied from the Apple developer Q&A website + // https://developer.apple.com/library/mac/qa/qa1374/_index.html + + 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; + } -static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) -{ - MidiInApi::RtMidiInData *data = static_cast (procRef); - CoreMidiData *apiData = static_cast (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; inumPackets; ++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; jdata[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); - } -} + // This function was submitted by Douglas Casey Tucker and apparently + // derived largely from PortMidi. + // Nearly the same text can be found in the Apple Q&A qa1374: + // https://developer.apple.com/library/mac/qa/qa1374/_index.html + 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 + class CoreSequencer { + public: + CoreSequencer():seq(0) + { + if (locking) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mutex, &attr); + } + } + + CoreSequencer(const std::string & n):seq(0),name(n) + { + if (locking) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mutex, &attr); + } + init(); + } + + ~CoreSequencer() + { + if (seq) { + scoped_lock lock(mutex); + MIDIClientDispose(seq); + seq = 0; + } + if (locking) { + pthread_mutex_destroy(&mutex); + } + } + + bool setName(const std::string & n) { + /* we don't want to rename the client after opening it. */ + if (seq) return false; + name = n; + return true; + } + + static std::string str(CFStringRef s) { + const char * cstr = + CFStringGetCStringPtr(s,kCFStringEncodingUTF8); + if (cstr) return cstr; + + CFIndex len = CFStringGetLength(s); + std::string retval; + retval.resize(CFStringGetMaximumSizeForEncoding(len, + kCFStringEncodingUTF8)+1); + CFStringGetBytes(s, + CFRangeMake(0, len), + kCFStringEncodingUTF8, + 0, + false, + reinterpret_cast(&retval[0]), + retval.size()-1, + &len); + retval.resize(len); + return trim(retval); + } + + +#if 0 +// Obtain the name of an endpoint, following connections. + +// The result should be released by the caller. + + static CFStringRef CreateConnectedEndpointName(MIDIEndpointRef endpoint) + { + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + OSStatus err; + + + // 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 = reinterpret_cast(CFDataGetBytePtr(connections)); + for (int 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(static_cast(connObject), true); + } else { + // Connected to an external device (10.2) (or something else, catch-all) + 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; + else + CFRelease(result); + + // Here, either the endpoint had no connections, or we failed to obtain names for any of them. + return CreateEndpointName(endpoint, false); + } + + + +////////////////////////////////////// + +// Obtain the name of an endpoint without regard for whether it has connections. + +// The result should be released by the caller. + + static CFStringRef CreateEndpointName(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 = NULL; + MIDIEndpointGetEntity(endpoint, &entity); + if (entity == NULL) + // 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 = NULL; + MIDIEntityGetDevice(entity, &device); + if (device == NULL) return result; + + str = NULL; + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &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 { + // 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(str /* device name */, + result /* endpoint 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; + } +#endif -MidiInCore :: ~MidiInCore( void ) -{ - // Close a connection if it exists. - closePort(); + static std::string getConnectionsString(MIDIEndpointRef port) + { + /* This function is derived from + CreateConnectedEndpointName at Apple Q&A */ + std::ostringstream result; + CFDataRef connections = NULL; + OSStatus err = MIDIObjectGetDataProperty(port, + kMIDIPropertyConnectionUniqueID, + &connections); + if (err != noErr) + return result.str(); + + if (!connections) + return result.str(); + CFIndex size = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); + if (!size) { + CFRelease(connections); + return result.str(); + } + + CFStringRef strRef; + const SInt32 *pid + = reinterpret_cast(CFDataGetBytePtr(connections)); + bool anyStrings = false; + for (int i = 0; i < size; ++i, ++pid) { + MIDIUniqueID id = EndianS32_BtoN(*pid); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); + if (err != noErr) + continue; + + if (connObjectType == kMIDIObjectType_ExternalSource || + connObjectType == kMIDIObjectType_ExternalDestination) { + // Connected to an external + // device's endpoint + // (10.3 and later). + strRef = EndpointName(static_cast(connObject), + true); + } else { + // Connected to an external device + // (10.2) (or something else, catch-all) + strRef = NULL; + MIDIObjectGetStringProperty(connObject, + kMIDIPropertyName, &strRef); + } + + if (strRef != NULL) { + if (anyStrings) + result << ", "; + else anyStrings = true; + result << str(strRef); + CFRelease(strRef); + } + } + CFRelease(connections); + return result.str(); + } + + static std::string getPortName(MIDIEndpointRef port, int flags) { +// std::string clientname; + std::string devicename; + std::string portname; + std::string entityname; +// std::string externaldevicename; + std::string connections; + std::string recommendedname; +// bool isVirtual; + bool hasManyEntities = false; + bool hasManyEndpoints = false; + CFStringRef nameRef; + MIDIObjectGetStringProperty(port, + kMIDIPropertyDisplayName, + &nameRef); + recommendedname = str(nameRef); + connections = getConnectionsString(port); + + MIDIObjectGetStringProperty(port, + kMIDIPropertyName, + &nameRef); + portname = str(nameRef); + CFRelease( nameRef ); + + MIDIEntityRef entity = NULL; + MIDIEndpointGetEntity(port, &entity); + // entity == NULL: probably virtual + if (entity != NULL) { + nameRef = NULL; + MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &nameRef); + if (str != NULL) { + entityname = str(nameRef); + CFRelease(nameRef); + } + hasManyEndpoints = + MIDIEntityGetNumberOfSources(entity) >= 2 || + MIDIEntityGetNumberOfDestinations(entity) + >= 2; + + // now consider the device's name + MIDIDeviceRef device = NULL; + MIDIEntityGetDevice(entity, &device); + if (device != NULL) { + hasManyEntities = MIDIDeviceGetNumberOfEntities(device) >= 2; + MIDIObjectGetStringProperty(device, + kMIDIPropertyName, + &nameRef); + devicename = str(nameRef); + CFRelease(nameRef); + } + // does the entity name already start with the device name? + // (some drivers do this though they shouldn't) + if (entityname.substr(0,devicename.length()) + == devicename) { + int start = devicename.length(); + while (isspace(entityname[start])) + start++; + entityname = entityname.substr(start); + } + } + + int naming = flags & PortDescriptor::NAMING_MASK; + + std::ostringstream os; + bool needcolon; + switch (naming) { + case PortDescriptor::SESSION_PATH: + if (flags & PortDescriptor::INCLUDE_API) + os << "CORE:"; + os << port; + break; + case PortDescriptor::STORAGE_PATH: + if (flags & PortDescriptor::INCLUDE_API) + os << "CORE:"; + // os << clientname; + os << devicename; + os << ":" << portname; + os << ":" << entityname; +// os << ":" << externaldevicename; + os << ":" << connections; +// os << ":" << recommendedname; + if (flags & PortDescriptor::UNIQUE_NAME) + os << ";" << port; + break; + case PortDescriptor::LONG_NAME: + needcolon = !devicename.empty(); + os << devicename; + if (hasManyEndpoints || + hasManyEntities || + devicename.empty()) { + if (!entityname.empty()) { + if (needcolon) + os << ": "; + os << entityname; + needcolon = true; + } + if ((hasManyEndpoints + || entityname.empty()) + && !portname.empty()) { + if (needcolon) + os << ": "; + os << portname; + } + } + if (!connections.empty()) { + os << " ⇒ "; + os << connections; + } + if (flags & + (PortDescriptor::INCLUDE_API + | PortDescriptor::UNIQUE_NAME)) { + os << " ("; + if (flags & + PortDescriptor::INCLUDE_API) { + os << "CORE"; + if (flags & PortDescriptor::UNIQUE_NAME) + os << ":"; + } + if (flags & PortDescriptor::UNIQUE_NAME) { + os << port; + } + os << ")"; + } + break; + case PortDescriptor::SHORT_NAME: + default: + if (!recommendedname.empty()) { + os << recommendedname; + } else + if (!connections.empty()) { + os << connections; + } else { + os << devicename; + if (hasManyEntities || + hasManyEndpoints || + devicename.empty()) { + if (!devicename.empty()) + os << " "; + if (!portname.empty()) { + os << portname; + } else if (!entityname.empty()) { + os << entityname; + } else + os << "???"; + } + } + if (flags & + (PortDescriptor::INCLUDE_API + | PortDescriptor::UNIQUE_NAME)) { + os << " ("; + if (flags & + PortDescriptor::INCLUDE_API) { + os << "CORE"; + if (flags & PortDescriptor::UNIQUE_NAME) + os << ":"; + } + if (flags & PortDescriptor::UNIQUE_NAME) { + os << port; + } + os << ")"; + } + break; + } + return os.str(); + } + + int getPortCapabilities(MIDIEndpointRef port) { + int retval = 0; + MIDIEntityRef entity = 0; + OSStatus stat = + MIDIEndpointGetEntity(port,&entity); + if (stat == kMIDIObjectNotFound) { + // plan B for virtual ports + MIDIUniqueID uid; + stat = MIDIObjectGetIntegerProperty (port, + kMIDIPropertyUniqueID, + &uid); + if (stat != noErr) { + throw + Error("CoreSequencer::getPortCapabilties: \ +Could not get the UID of a midi endpoint.", + Error::DRIVER_ERROR); + return 0; + } + MIDIObjectRef obj; + MIDIObjectType type; + stat = MIDIObjectFindByUniqueID (uid, + &obj, + &type); + if (stat != noErr || obj != port) { + throw + Error("CoreSequencer::getPortCapabilties: \ +Could not get the endpoint back from UID of a midi endpoint.", + Error::DRIVER_ERROR); + return 0; + } + if (type == kMIDIObjectType_Source + || type == kMIDIObjectType_ExternalSource) + return PortDescriptor::INPUT; + else if (type == kMIDIObjectType_Destination + || type == kMIDIObjectType_ExternalDestination) + return PortDescriptor::OUTPUT; + else { + return 0; + } + + } else if (stat != noErr) { + throw + Error("CoreSequencer::getPortCapabilties: \ +Could not get the entity of a midi endpoint.", + Error::DRIVER_ERROR); + return 0; + } + /* Theoretically Mac OS X could silently use + the same endpoint reference for input and + output. We might benefit from this + behaviour. + \todo: Find a way to query the object + whether it can act as source or destination. + */ + ItemCount count = + MIDIEntityGetNumberOfDestinations(entity); + for (ItemCount i = 0; i < count ; i++) { + MIDIEndpointRef dest= + MIDIEntityGetDestination(entity,i); + if (dest == port) { + retval |= + PortDescriptor::OUTPUT; + break; + } + } + count = + MIDIEntityGetNumberOfSources(entity); + for (ItemCount i = 0; i < count ; i++) { + MIDIEndpointRef src= + MIDIEntityGetSource(entity,i); + if (src == port) { + retval |= + PortDescriptor::INPUT; + } + } + return retval; + } + +#if 0 + int getNextClient(snd_seq_client_info_t * cinfo ) { + init(); + scoped_lock lock (mutex); + return snd_seq_query_next_client (seq, cinfo); + } + int getNextPort(snd_seq_port_info_t * pinfo ) { + init(); + scoped_lock lock (mutex); + return snd_seq_query_next_port (seq, pinfo); + } +#endif - // Cleanup. - CoreMidiData *data = static_cast (apiData_); - MIDIClientDispose( data->client ); - if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); - delete data; -} + MIDIPortRef createPort (std::string portName, + int flags, + MidiInApi::MidiInData * data = NULL) + { + init(); + scoped_lock lock (mutex); + MIDIPortRef port = 0; + OSStatus result; + switch (flags) { + case PortDescriptor::INPUT: + result = MIDIInputPortCreate(seq, + CFStringCreateWithCString( + NULL, + portName.c_str(), + kCFStringEncodingUTF8 ), + midiInputCallback, + (void *)data, + &port); + break; + case PortDescriptor::OUTPUT: + result + = MIDIOutputPortCreate(seq, + CFStringCreateWithCString( + NULL, + portName.c_str(), + kCFStringEncodingUTF8 ), + &port); + break; + default: + throw Error("CoreSequencer::createPort:\ + Error creating OS X MIDI port because of invalid port flags", + Error::DRIVER_ERROR); + } + if ( result != noErr ) { + throw Error( + "CoreSequencer::createPort:\ + error creating OS-X MIDI port.", + Error::DRIVER_ERROR); + } + return port; + } + + MIDIEndpointRef createVirtualPort (std::string portName, + int flags, + MidiInApi::MidiInData * data = NULL) + { + init(); + scoped_lock lock (mutex); + MIDIEndpointRef port = 0; + OSStatus result; + switch (flags) { + case PortDescriptor::INPUT: + result + = MIDIDestinationCreate(seq, + CFStringCreateWithCString( + NULL, + portName.c_str(), + kCFStringEncodingUTF8 ), + midiInputCallback, + (void *)data, + &port); + break; + case PortDescriptor::OUTPUT: + result + = MIDISourceCreate(seq, + CFStringCreateWithCString( + NULL, + portName.c_str(), + kCFStringEncodingUTF8 ), + &port); + break; + default: + throw Error(Error::DRIVER_ERROR, + "CoreSequencer::createVirtualPort:\ + Error creating OS X MIDI port because of invalid port flags"); + } + if ( result != noErr ) { + throw Error( Error::DRIVER_ERROR, + "CoreSequencer::createVirtualPort: error creating OS-X MIDI port." ); + } + return port; + } + +#if 0 + void deletePort(int port) { + init(); + scoped_lock lock (mutex); + snd_seq_delete_port( seq, port ); + } + + snd_seq_port_subscribe_t * connectPorts(const snd_seq_addr_t & from, + const snd_seq_addr_t & to, + bool real_time) { + init(); + snd_seq_port_subscribe_t *subscription; + + if (snd_seq_port_subscribe_malloc( &subscription ) < 0) { + throw Error("MidiInCore::openPort: CORE error allocation port subscription.", + Error::DRIVER_ERROR ); + return 0; + } + snd_seq_port_subscribe_set_sender(subscription, &from); + snd_seq_port_subscribe_set_dest(subscription, &to); + if (real_time) { + snd_seq_port_subscribe_set_time_update(subscription, 1); + snd_seq_port_subscribe_set_time_real(subscription, 1); + } + { + scoped_lock lock (mutex); + if ( snd_seq_subscribe_port(seq, subscription) ) { + snd_seq_port_subscribe_free( subscription ); + subscription = 0; + throw Error("MidiInCore::openPort: CORE error making port connection.", + Error::DRIVER_ERROR); + return 0; + } + } + return subscription; + } + + void closePort(snd_seq_port_subscribe_t * subscription ) { + init(); + scoped_lock lock(mutex); + snd_seq_unsubscribe_port( seq, subscription ); + } + + + void startQueue(int queue_id) { + init(); + scoped_lock lock(mutex); + snd_seq_start_queue( seq, queue_id, NULL ); + snd_seq_drain_output( seq ); + } +#endif -void MidiInCore :: initialize( const std::string& clientName ) -{ - // Set up our client. - MIDIClientRef client; - OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), 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; -} + /*! Use CoreSequencer like a C pointer. + \note This function breaks the design to control thread safety + by the selection of the \ref locking parameter to the class. + It should be removed as soon as possible in order ensure the + thread policy that has been intended by creating this class. + */ + operator MIDIClientRef () + { + return seq; + } + protected: + struct scoped_lock { + pthread_mutex_t * mutex; + scoped_lock(pthread_mutex_t & m): mutex(&m) + { + if (locking) + pthread_mutex_lock(mutex); + } + ~scoped_lock() + { + if (locking) + pthread_mutex_unlock(mutex); + } + }; + pthread_mutex_t mutex; + MIDIClientRef seq; + std::string name; + +#if 0 + snd_seq_client_info_t * GetClient(int id) { + init(); + snd_seq_client_info_t * cinfo; + scoped_lock lock(mutex); + snd_seq_get_any_client_info(seq,id,cinfo); + return cinfo; + } +#endif -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 (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 init() + { + init (seq); + } + + void init(MIDIClientRef &s) + { + if (s) return; + { + scoped_lock lock(mutex); + OSStatus result = MIDIClientCreate( + CFStringCreateWithCString( NULL, + name.c_str(), + kCFStringEncodingUTF8), + NULL, NULL, &s ); + if ( result != noErr ) { + throw Error( + "CoreSequencer::initialize: \ +Error creating OS-X MIDI client object.", + Error::DRIVER_ERROR); + return; + } + } + } + }; + typedef CoreSequencer<1> LockingCoreSequencer; + typedef CoreSequencer<0> NonLockingCoreSequencer; + + struct CorePortDescriptor:public PortDescriptor { + CorePortDescriptor(const std::string & name):api(0), + clientName(name), + endpoint(0) + { + } + CorePortDescriptor(MIDIEndpointRef p, + const std::string & name):api(0), + clientName(name), + endpoint(p) + { + seq.setName(name); + } + CorePortDescriptor(CorePortDescriptor & + other):PortDescriptor(other), + api(other.api), + clientName(other.clientName), + endpoint(other.endpoint) + { + seq.setName(clientName); + } + ~CorePortDescriptor() {} + + MidiInApi * getInputApi(unsigned int queueSizeLimit = 100) { + if (getCapabilities() & INPUT) + return new MidiInCore(clientName,queueSizeLimit); + else + return 0; + } + + MidiOutApi * getOutputApi() { + if (getCapabilities() & OUTPUT) + return new MidiOutCore(clientName); + else + return 0; + } + + void setEndpoint(MIDIEndpointRef e) + { + endpoint = e; + } + MIDIEndpointRef getEndpoint() const + { + return endpoint; + } + + std::string getName(int flags = SHORT_NAME | UNIQUE_NAME) { + return seq.getPortName(endpoint,flags); + } + + const std::string & getClientName() { + return clientName; + } + int getCapabilities() { + if (!endpoint) return 0; + return seq.getPortCapabilities(endpoint); + } + static PortList getPortList(int capabilities, const std::string & clientName); + protected: + MidiApi * api; + static LockingCoreSequencer seq; + + std::string clientName; + MIDIEndpointRef endpoint; + }; + + LockingCoreSequencer CorePortDescriptor::seq; + + + + PortList CorePortDescriptor :: getPortList(int capabilities, const std::string & clientName) + { + PortList list; + + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + int caps = capabilities & PortDescriptor::INOUTPUT; + bool unlimited = capabilities & PortDescriptor::UNLIMITED; + bool forceInput = PortDescriptor::INPUT & caps; + bool forceOutput = PortDescriptor::OUTPUT & caps; + bool allowOutput = forceOutput || !forceInput; + bool allowInput = forceInput || !forceOutput; + if (allowOutput) { + ItemCount count = + MIDIGetNumberOfDestinations(); + for (ItemCount i = 0 ; i < count; i++) { + MIDIEndpointRef destination = + MIDIGetDestination(i); + if ((seq.getPortCapabilities(destination) + & caps) == caps) + list.push_back(new CorePortDescriptor(destination, + clientName)); + } + // Combined sources and destinations + // should be both occur as destinations and as + // sources. So we have finished the search, here. + } else if (allowInput) { + ItemCount count = + MIDIGetNumberOfSources(); + for (ItemCount i = 0 ; i < count; i++) { + MIDIEndpointRef src = + MIDIGetSource(i); + if ((seq.getPortCapabilities(src) + & caps) == caps) + list.push_back(new CorePortDescriptor(src, + clientName)); + } + } + return list; + } -void MidiInCore :: openVirtualPort( const std::string portName ) -{ - CoreMidiData *data = static_cast (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 ) -{ - if ( connected_ ) { - CoreMidiData *data = static_cast (apiData_); - MIDIPortDispose( data->port ); - connected_ = false; - } -} + // A structure to hold variables related to the CoreMIDI API + // implementation. + struct CoreMidiData:public CorePortDescriptor { + CoreMidiData(std::string clientname):CorePortDescriptor(clientname), + client(clientname), + localEndpoint(0), + localPort(0) {} + ~CoreMidiData() { + if (localEndpoint) + MIDIEndpointDispose(localEndpoint); + localEndpoint = 0; + } + + void openPort(const std::string & name, + int flags, + MidiInApi::MidiInData * data = NULL) { + localPort = client.createPort(name, flags, data); + } + + void setRemote(const CorePortDescriptor & remote) + { + setEndpoint(remote.getEndpoint()); + } + + NonLockingCoreSequencer client; + MIDIEndpointRef localEndpoint; + MIDIPortRef localPort; + unsigned long long lastTime; + MIDISysexSendRequest sysexreq; + }; + + //*********************************************************************// + // API: OS-X + // Class Definitions: MidiInCore + //*********************************************************************// + + static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) + { + MidiInApi::MidiInData *data = static_cast (procRef); + CoreMidiData *apiData = static_cast (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; inumPackets; ++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; jdata[j] ); + } + continueSysex = packet->data[nBytes-1] != 0xF7; + + if ( !( data->ignoreFlags & 0x01 ) ) { + if ( !continueSysex ) { + // If not a continuing sysex message, invoke the user callback function or queue the message. + if ( data->usingCallback ) { + MidiCallback callback = (MidiCallback) 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 ) { + MidiCallback callback = (MidiCallback) 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); + } + } -unsigned int MidiInCore :: getPortCount() -{ - CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); - return MIDIGetNumberOfSources(); -} + MidiInCore :: MidiInCore( const std::string clientName, + unsigned int queueSizeLimit ) : + MidiInApi( queueSizeLimit ) + { + initialize( clientName ); + } -// 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; -} + MidiInCore :: ~MidiInCore( void ) + { + // Close a connection if it exists. + closePort(); -// 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 (apiData_); + delete data; + } -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; -} + void MidiInCore :: initialize( const std::string& clientName ) + { + // Save our api-specific connection information. + CoreMidiData *data = (CoreMidiData *) new CoreMidiData(clientName); + apiData_ = (void *) data; + inputData_.apiData = (void *) data; + } -//*********************************************************************// -// API: OS-X -// Class Definitions: MidiOutCore -//*********************************************************************// + void MidiInCore :: openPort( unsigned int portNumber, + const std::string & portName ) + { + if ( connected_ ) { + errorString_ = "MidiInCore::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + unsigned int nSrc = MIDIGetNumberOfSources(); + if (nSrc < 1) { + errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; + error( Error::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nSrc ) { + std::ostringstream ost; + ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::INVALID_PARAMETER, errorString_ ); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast (apiData_); + OSStatus result = MIDIInputPortCreate( data->client, + CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingUTF8 ), + midiInputCallback, (void *)&inputData_, &port ); + if ( result != noErr ) { + MIDIClientDispose( data->client ); + errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; + error( Error::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( Error::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( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific port information. + data->localPort = port; + data->setEndpoint(endpoint); + + connected_ = true; + } -MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi() -{ - initialize( clientName ); -} + void MidiInCore :: openVirtualPort( const std::string portName ) + { + CoreMidiData *data = static_cast (apiData_); + + // Create a virtual MIDI input destination. + MIDIEndpointRef endpoint; + OSStatus result = MIDIDestinationCreate( data->client, + CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingUTF8 ), + midiInputCallback, (void *)&inputData_, &endpoint ); + if ( result != noErr ) { + errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific connection information. + data->localEndpoint = endpoint; + } -MidiOutCore :: ~MidiOutCore( void ) -{ - // Close a connection if it exists. - closePort(); + void MidiInCore :: openPort( const PortDescriptor & port, + const std::string & portName) + { + CoreMidiData *data = static_cast (apiData_); + const CorePortDescriptor * remote = dynamic_cast(&port); + + if ( !data ) { + errorString_ = "MidiInCore::openPort: Internal error: data has not been allocated!"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + if ( connected_ || data -> localEndpoint) { + errorString_ = "MidiInCore::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + if (!remote) { + errorString_ = "MidiInCore::openPort: an invalid (i.e. non-CORE) port descriptor has been passed to openPort!"; + error( Error::WARNING, errorString_ ); + return; + } + + data->openPort (portName, + PortDescriptor::INPUT, + &inputData_); + data->setRemote(*remote); + OSStatus result = + MIDIPortConnectSource(data->localPort, + data->getEndpoint(), + NULL); + if ( result != noErr ) { + error(Error::DRIVER_ERROR, + "CoreSequencer::createPort:\ + error creating OS-X MIDI port."); + } + + connected_ = true; + } - // Cleanup. - CoreMidiData *data = static_cast (apiData_); - MIDIClientDispose( data->client ); - if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); - delete data; -} + Pointer MidiInCore :: getDescriptor(bool local) + { + CoreMidiData *data = static_cast + (apiData_); + if (!data) { + return NULL; + } + if (local) { + if (data && data->localEndpoint) { + return new + CorePortDescriptor(data->localEndpoint, + data->getClientName()); + } + } else { + if (data->getEndpoint()) { + return new CorePortDescriptor(*data); + } + } + return NULL; + } -void MidiOutCore :: initialize( const std::string& clientName ) -{ - // Set up our client. - MIDIClientRef client; - OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), 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; -} + PortList MidiInCore :: getPortList(int capabilities) + { + CoreMidiData *data = static_cast (apiData_); + return CorePortDescriptor::getPortList(capabilities | PortDescriptor::INPUT, + data->getClientName()); + } -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 MidiInCore :: closePort( void ) + { + if ( connected_ ) { + CoreMidiData *data = static_cast (apiData_); + MIDIPortDispose( data->localPort ); + data->localPort = 0; + connected_ = false; + } + } -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 (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; -} + unsigned int MidiInCore :: getPortCount() + { + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + return MIDIGetNumberOfSources(); + } -void MidiOutCore :: closePort( void ) -{ - if ( connected_ ) { - CoreMidiData *data = static_cast (apiData_); - MIDIPortDispose( data->port ); - connected_ = 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( Error::WARNING, errorString_ ); + return stringName; + } + + portRef = MIDIGetSource( portNumber ); + nameRef = ConnectedEndpointName(portRef); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8); + CFRelease( nameRef ); + + return stringName = name; + } -void MidiOutCore :: openVirtualPort( std::string portName ) -{ - CoreMidiData *data = static_cast (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; -} + //*********************************************************************// + // API: OS-X + // Class Definitions: MidiOutCore + //*********************************************************************// -// Not necessary if we don't treat sysex messages any differently than -// normal messages ... see below. -//static void sysexCompletionProc( MIDISysexSendRequest *sreq ) -//{ -// free( sreq ); -//} - -void MidiOutCore :: sendMessage( std::vector *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; - } - - // unsigned int packetBytes, bytesLeft = nBytes; - // unsigned int messageIndex = 0; - MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); - CoreMidiData *data = static_cast (apiData_); - OSStatus result; - - /* - // I don't think this code is necessary. We can send sysex - // messages through the normal mechanism. In addition, this avoids - // the problem of virtual ports not receiving sysex messages. - - if ( message->at(0) == 0xF0 ) { - - // Apple's fantastic API requires us to free the allocated data in - // the completion callback but trashes the pointer and size before - // we get a chance to free it!! This is a somewhat ugly hack - // submitted by ptarabbia that puts the sysex buffer data right at - // the end of the MIDISysexSendRequest structure. This solution - // does not require that we wait for a previous sysex buffer to be - // sent before sending a new one, which was the old way we did it. - MIDISysexSendRequest *newRequest = (MIDISysexSendRequest *) malloc(sizeof(struct MIDISysexSendRequest) + nBytes); - char * sysexBuffer = ((char *) newRequest) + sizeof(struct MIDISysexSendRequest); - - // Copy data to buffer. - for ( unsigned int i=0; iat(i); - - newRequest->destination = data->destinationId; - newRequest->data = (Byte *)sysexBuffer; - newRequest->bytesToSend = nBytes; - newRequest->complete = 0; - newRequest->completionProc = sysexCompletionProc; - newRequest->completionRefCon = newRequest; - - result = MIDISendSysex(newRequest); - if ( result != noErr ) { - free( newRequest ); - errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; - error( RtMidiError::WARNING, errorString_ ); - return; - } - return; - } - else if ( nBytes > 3 ) { - errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; - error( RtMidiError::WARNING, errorString_ ); - return; - } - */ - - MIDIPacketList packetList; - MIDIPacket *packet = MIDIPacketListInit( &packetList ); - packet = MIDIPacketListAdd( &packetList, sizeof(packetList), packet, timeStamp, nBytes, (const Byte *) &message->at( 0 ) ); - 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_ ); - } - } -} + MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi() + { + initialize( clientName ); + } + + MidiOutCore :: ~MidiOutCore( void ) + { + // Close a connection if it exists. + closePort(); + + // Cleanup. + CoreMidiData *data = static_cast (apiData_); + delete data; + } + + void MidiOutCore :: initialize( const std::string& clientName ) + { + // Save our api-specific connection information. + CoreMidiData *data = (CoreMidiData *) new CoreMidiData(clientName); + apiData_ = (void *) data; + } + + 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( Error::WARNING, errorString_ ); + return stringName; + } + + portRef = MIDIGetDestination( portNumber ); + nameRef = ConnectedEndpointName(portRef); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8); + 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( Error::WARNING, errorString_ ); + return; + } + + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + unsigned int nDest = MIDIGetNumberOfDestinations(); + if (nDest < 1) { + errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; + error( Error::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDest ) { + std::ostringstream ost; + ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::INVALID_PARAMETER, errorString_ ); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast (apiData_); + OSStatus result = MIDIOutputPortCreate( data->client, + CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingUTF8 ), + &port ); + if ( result != noErr ) { + MIDIClientDispose( data->client ); + errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; + error( Error::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( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific connection information. + data->localPort = port; + data->setEndpoint(destination); + connected_ = true; + } + + void MidiOutCore :: closePort( void ) + { + if ( connected_ ) { + CoreMidiData *data = static_cast (apiData_); + MIDIPortDispose( data->localPort ); + connected_ = false; + } + } + + void MidiOutCore :: openVirtualPort( std::string portName ) + { + CoreMidiData *data = static_cast (apiData_); + + if ( data->localEndpoint ) { + errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + + // Create a virtual MIDI output source. + MIDIEndpointRef endpoint; + OSStatus result = MIDISourceCreate( data->client, + CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingUTF8 ), + &endpoint ); + if ( result != noErr ) { + errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific connection information. + data->localEndpoint = endpoint; + } + + void MidiOutCore :: openPort( const PortDescriptor & port, + const std::string & portName) + { + CoreMidiData *data = static_cast (apiData_); + const CorePortDescriptor * remote = dynamic_cast(&port); + + if ( !data ) { + errorString_ = "MidiOutCore::openPort: Internal error: data has not been allocated!"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + if ( connected_ || data -> localEndpoint) { + errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + if (!remote) { + errorString_ = "MidiOutCore::openPort: an invalid (i.e. non-CORE) port descriptor has been passed to openPort!"; + error( Error::WARNING, errorString_ ); + return; + } + + data->openPort (portName, + PortDescriptor::OUTPUT); + data->setRemote(*remote); + connected_ = true; + } + + Pointer MidiOutCore :: getDescriptor(bool local) + { + CoreMidiData *data = static_cast + (apiData_); + if (!data) { + return NULL; + } + if (local) { + if (data && data->localEndpoint) { + return new + CorePortDescriptor(data->localEndpoint, + data->getClientName()); + } + } else { + if (data->getEndpoint()) { + return new CorePortDescriptor(*data); + } + } + return NULL; + } + + PortList MidiOutCore :: getPortList(int capabilities) + { + CoreMidiData *data = static_cast (apiData_); + return CorePortDescriptor::getPortList(capabilities | PortDescriptor::OUTPUT, + data->getClientName()); + } + + + // Not necessary if we don't treat sysex messages any differently than + // normal messages ... see below. + //static void sysexCompletionProc( MIDISysexSendRequest *sreq ) + //{ + // free( sreq ); + //} + + void MidiOutCore :: sendMessage( std::vector &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( Error::WARNING, errorString_ ); + return; + } + + // unsigned int packetBytes, bytesLeft = nBytes; + // unsigned int messageIndex = 0; + MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); + CoreMidiData *data = static_cast (apiData_); + OSStatus result; + + /* + // I don't think this code is necessary. We can send sysex + // messages through the normal mechanism. In addition, this avoids + // the problem of virtual ports not receiving sysex messages. + + if ( message.at(0) == 0xF0 ) { + + // Apple's fantastic API requires us to free the allocated data in + // the completion callback but trashes the pointer and size before + // we get a chance to free it!! This is a somewhat ugly hack + // submitted by ptarabbia that puts the sysex buffer data right at + // the end of the MIDISysexSendRequest structure. This solution + // does not require that we wait for a previous sysex buffer to be + // sent before sending a new one, which was the old way we did it. + MIDISysexSendRequest *newRequest = (MIDISysexSendRequest *) malloc(sizeof(struct MIDISysexSendRequest) + nBytes); + char * sysexBuffer = ((char *) newRequest) + sizeof(struct MIDISysexSendRequest); + + // Copy data to buffer. + for ( unsigned int i=0; idestination = data->destinationId; + newRequest->data = (Byte *)sysexBuffer; + newRequest->bytesToSend = nBytes; + newRequest->complete = 0; + newRequest->completionProc = sysexCompletionProc; + newRequest->completionRefCon = newRequest; + + result = MIDISendSysex(newRequest); + if ( result != noErr ) { + free( newRequest ); + errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; + error( Error::WARNING, errorString_ ); + return; + } + return; + } + else if ( nBytes > 3 ) { + errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; + error( Error::WARNING, errorString_ ); + return; + } + */ + + MIDIPacketList packetList; + MIDIPacket *packet = MIDIPacketListInit( &packetList ); + packet = MIDIPacketListAdd( &packetList, sizeof(packetList), packet, timeStamp, nBytes, (const Byte *) &message.at( 0 ) ); + if ( !packet ) { + errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Send to any destinations that may have connected to us. + if ( data->localEndpoint ) { + result = MIDIReceived( data->localEndpoint, &packetList ); + if ( result != noErr ) { + errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; + error( Error::WARNING, errorString_ ); + } + } + + // And send to an explicit destination port if we're connected. + if ( connected_ ) { + result = MIDISend( data->localPort, data->getEndpoint(), &packetList ); + if ( result != noErr ) { + errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; + error( Error::WARNING, errorString_ ); + } + } + } +} #endif // __MACOSX_CORE__ -//*********************************************************************// -// API: LINUX ALSA SEQUENCER -//*********************************************************************// + //*********************************************************************// + // API: LINUX ALSA SEQUENCER + //*********************************************************************// -// API information found at: -// - http://www.alsa-project.org/documentation.php#Library + // API information found at: + // - http://www.alsa-project.org/documentation.php#Library #if defined(__LINUX_ALSA__) @@ -1116,800 +2136,1399 @@ void MidiOutCore :: sendMessage( std::vector *message ) // ALSA header file. #include -// 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]; -}; +namespace rtmidi { + struct AlsaMidiData; + + /*! An abstraction layer for the ALSA sequencer layer. It provides + the following functionality: + - dynamic allocation of the sequencer + - optionallay avoid concurrent access to the ALSA sequencer, + which is not thread proof. This feature is controlled by + the parameter \ref locking. + */ + + template + class AlsaSequencer { + public: + AlsaSequencer():seq(0) + { + if (locking) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mutex, &attr); + } + } + + AlsaSequencer(const std::string & n):seq(0),name(n) + { + if (locking) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mutex, &attr); + } + init(); + { + scoped_lock lock(mutex); + snd_seq_set_client_name( seq, name.c_str() ); + } + } + + ~AlsaSequencer() + { + if (seq) { + scoped_lock lock(mutex); + snd_seq_close(seq); + seq = 0; + } + if (locking) { + pthread_mutex_destroy(&mutex); + } + } + + bool setName(const std::string & n) { + /* we don't want to rename the client after opening it. */ + if (seq) return false; + name = n; + return true; + } + + std::string GetPortName(int client, int port, int flags) { + init(); + snd_seq_client_info_t *cinfo; + snd_seq_client_info_alloca( &cinfo ); + { + scoped_lock lock (mutex); + snd_seq_get_any_client_info(seq,client,cinfo); + } + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + { + scoped_lock lock (mutex); + snd_seq_get_any_port_info(seq,client,port,pinfo); + } + + int naming = flags & PortDescriptor::NAMING_MASK; + + std::ostringstream os; + switch (naming) { + case PortDescriptor::SESSION_PATH: + if (flags & PortDescriptor::INCLUDE_API) + os << "ALSA:"; + os << client << ":" << port; + break; + case PortDescriptor::STORAGE_PATH: + if (flags & PortDescriptor::INCLUDE_API) + os << "ALSA:"; + os << snd_seq_client_info_get_name(cinfo); + os << ":"; + os << snd_seq_port_info_get_name(pinfo); + if (flags & PortDescriptor::UNIQUE_NAME) + os << ";" << client << ":" << port; + break; + case PortDescriptor::LONG_NAME: + os << snd_seq_client_info_get_name( cinfo ); + if (flags & PortDescriptor::UNIQUE_NAME) { + os << " " << client; + } + os << ":"; + if (flags & PortDescriptor::UNIQUE_NAME) { + os << port; + } + + os << " " << snd_seq_port_info_get_name(pinfo); + if (flags & PortDescriptor::INCLUDE_API) + os << " (ALSA)"; + break; + case PortDescriptor::SHORT_NAME: + default: + os << snd_seq_client_info_get_name( cinfo ); + if (flags & PortDescriptor::UNIQUE_NAME) { + os << " "; + os << client; + } + os << ":" << port; + if (flags & PortDescriptor::INCLUDE_API) + os << " (ALSA)"; + + break; + } + return os.str(); + } + + int getPortCapabilities(int client, int port) { + init(); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + { + scoped_lock lock (mutex); + snd_seq_get_any_port_info(seq,client,port,pinfo); + } + unsigned int caps = snd_seq_port_info_get_capability(pinfo); + int retval = (caps & (SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ))? + PortDescriptor::INPUT:0; + if (caps & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + retval |= PortDescriptor::OUTPUT; + return retval; + } + + int getNextClient(snd_seq_client_info_t * cinfo ) { + init(); + scoped_lock lock (mutex); + return snd_seq_query_next_client (seq, cinfo); + } + int getNextPort(snd_seq_port_info_t * pinfo ) { + init(); + scoped_lock lock (mutex); + return snd_seq_query_next_port (seq, pinfo); + } + + int createPort (snd_seq_port_info_t *pinfo) { + init(); + scoped_lock lock (mutex); + return snd_seq_create_port(seq, pinfo); + } + + void deletePort(int port) { + init(); + scoped_lock lock (mutex); + snd_seq_delete_port( seq, port ); + } + + snd_seq_port_subscribe_t * connectPorts(const snd_seq_addr_t & from, + const snd_seq_addr_t & to, + bool real_time) { + init(); + snd_seq_port_subscribe_t *subscription; + + if (snd_seq_port_subscribe_malloc( &subscription ) < 0) { + throw Error("MidiInAlsa::openPort: ALSA error allocation port subscription.", + Error::DRIVER_ERROR ); + return 0; + } + snd_seq_port_subscribe_set_sender(subscription, &from); + snd_seq_port_subscribe_set_dest(subscription, &to); + if (real_time) { + snd_seq_port_subscribe_set_time_update(subscription, 1); + snd_seq_port_subscribe_set_time_real(subscription, 1); + } + { + scoped_lock lock (mutex); + if ( snd_seq_subscribe_port(seq, subscription) ) { + snd_seq_port_subscribe_free( subscription ); + subscription = 0; + throw Error("MidiInAlsa::openPort: ALSA error making port connection.", + Error::DRIVER_ERROR); + return 0; + } + } + return subscription; + } + + void closePort(snd_seq_port_subscribe_t * subscription ) { + init(); + scoped_lock lock(mutex); + snd_seq_unsubscribe_port( seq, subscription ); + } + + void startQueue(int queue_id) { + init(); + scoped_lock lock(mutex); + snd_seq_start_queue( seq, queue_id, NULL ); + snd_seq_drain_output( seq ); + } + + /*! Use AlsaSequencer like a C pointer. + \note This function breaks the design to control thread safety + by the selection of the \ref locking parameter to the class. + It should be removed as soon as possible in order ensure the + thread policy that has been intended by creating this class. + */ + operator snd_seq_t * () + { + return seq; + } + protected: + struct scoped_lock { + pthread_mutex_t * mutex; + scoped_lock(pthread_mutex_t & m): mutex(&m) + { + if (locking) + pthread_mutex_lock(mutex); + } + ~scoped_lock() + { + if (locking) + pthread_mutex_unlock(mutex); + } + }; + pthread_mutex_t mutex; + snd_seq_t * seq; + std::string name; + + + snd_seq_client_info_t * GetClient(int id) { + init(); + snd_seq_client_info_t * cinfo; + scoped_lock lock(mutex); + snd_seq_get_any_client_info(seq,id,cinfo); + return cinfo; + } + + void init() + { + init (seq); + } + + void init(snd_seq_t * &s) + { + if (s) return; + { + scoped_lock lock(mutex); + int result = snd_seq_open(&s, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); + if ( result < 0 ) { + throw Error( "MidiInAlsa::initialize: error creating ALSA sequencer client object.", + Error::DRIVER_ERROR ); + return; + } + snd_seq_set_client_name( seq, name.c_str() ); + } + } + }; + typedef AlsaSequencer<1> LockingAlsaSequencer; + typedef AlsaSequencer<0> NonLockingAlsaSequencer; + + struct AlsaPortDescriptor:public PortDescriptor, + public snd_seq_addr_t + { + MidiApi * api; + static LockingAlsaSequencer seq; + AlsaPortDescriptor(const std::string & name):api(0),clientName(name) + { + client = 0; + port = 0; + } + AlsaPortDescriptor(int c, int p, const std::string & name):api(0),clientName(name) + { + client = c; + port = p; + seq.setName(name); + } + AlsaPortDescriptor(snd_seq_addr_t & other, + const std::string & name):snd_seq_addr_t(other), + clientName(name) { + seq.setName(name); + } + ~AlsaPortDescriptor() {} + MidiInApi * getInputApi(unsigned int queueSizeLimit = 100) { + if (getCapabilities() & INPUT) + return new MidiInAlsa(clientName,queueSizeLimit); + else + return 0; + } + MidiOutApi * getOutputApi() { + if (getCapabilities() & OUTPUT) + return new MidiOutAlsa(clientName); + else + return 0; + } + std::string getName(int flags = SHORT_NAME | UNIQUE_NAME) { + return seq.GetPortName(client,port,flags); + } + + const std::string & getClientName() { + return clientName; + } + int getCapabilities() { + if (!client) return 0; + return seq.getPortCapabilities(client,port); + } + static PortList getPortList(int capabilities, const std::string & clientName); + protected: + std::string clientName; + }; + + LockingAlsaSequencer AlsaPortDescriptor::seq; + + + + PortList AlsaPortDescriptor :: getPortList(int capabilities, const std::string & clientName) + { + PortList list; + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + int client; + snd_seq_client_info_alloca( &cinfo ); + snd_seq_port_info_alloca( &pinfo ); + + snd_seq_client_info_set_client( cinfo, -1 ); + while ( seq.getNextClient(cinfo ) >= 0 ) { + client = snd_seq_client_info_get_client( cinfo ); + // ignore default device (it is included in the following results again) + if ( client == 0 ) continue; + // Reset query info + snd_seq_port_info_set_client( pinfo, client ); + snd_seq_port_info_set_port( pinfo, -1 ); + while ( seq.getNextPort( pinfo ) >= 0 ) { + unsigned int atyp = snd_seq_port_info_get_type( pinfo ); + // otherwise we get ports without any + if ( !(capabilities & UNLIMITED) && + !( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) ) continue; + unsigned int caps = snd_seq_port_info_get_capability( pinfo ); + if (capabilities & INPUT) { + /* we need both READ and SUBS_READ */ + if ((caps & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)) + != (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)) + continue; + } + if (capabilities & OUTPUT) { + /* we need both WRITE and SUBS_WRITE */ + if ((caps & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + != (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE)) + continue; + } + list.push_back(new AlsaPortDescriptor(client,snd_seq_port_info_get_port(pinfo),clientName)); + } + } + return list; + } -#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) + static void *alsaMidiHandler( void *ptr ); + + + /*! A structure to hold variables related to the ALSA API + implementation. + + \note After all sequencer handling is covered by the \ref + AlsaSequencer class, we should make seq to be a pointer in order + to allow a common client implementation. + */ + + struct AlsaMidiData:public AlsaPortDescriptor { + /* + AlsaMidiData():seq() + { + init(); + } + */ + AlsaMidiData(const std::string &clientName):AlsaPortDescriptor(clientName), + seq(clientName) + { + init(); + } + ~AlsaMidiData() + { + if (local.client) + deletePort(); + } + void init () { + local.port = 0; + local.client = 0; + port = -1; + subscription = 0; + coder = 0; + bufferSize = 32; + buffer = 0; + dummy_thread_id = pthread_self(); + thread = dummy_thread_id; + trigger_fds[0] = -1; + trigger_fds[1] = -1; + } + snd_seq_addr_t local; /*!< Our port and client id. If client = 0 (default) this means we didn't aquire a port so far. */ + NonLockingAlsaSequencer seq; + // unsigned int portNum; + 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]; + + void setRemote(const AlsaPortDescriptor * remote) { + port = remote->port; + client = remote->client; + } + void connectPorts(const snd_seq_addr_t &from, + const snd_seq_addr_t &to, + bool real_time) { + subscription = seq.connectPorts(from, to, real_time); + } + + int openPort(int alsaCapabilities, + const std::string & portName) { + if (subscription) { + api->error( Error::DRIVER_ERROR, + "MidiInAlsa::openPort: ALSA error allocation port subscription." ); + return -99; + } + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + + snd_seq_port_info_set_client( pinfo, 0 ); + snd_seq_port_info_set_port( pinfo, 0 ); + snd_seq_port_info_set_capability( pinfo, + alsaCapabilities); + 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, queue_id); +#endif + snd_seq_port_info_set_name(pinfo, portName.c_str() ); + int createok = seq.createPort(pinfo); + + if ( createok < 0 ) { + api->error( Error::DRIVER_ERROR, + "MidiInAlsa::openPort: ALSA error creating input port." ); + return createok; + } + + local.client = snd_seq_port_info_get_client( pinfo ); + local.port = snd_seq_port_info_get_port(pinfo); + return 0; + } + + void deletePort() { + seq.deletePort(local.port); + local.client = 0; + local.port = 0; + } + + void closePort() { + seq.closePort(subscription ); + snd_seq_port_subscribe_free( subscription ); + subscription = 0; + } + + bool startQueue(void * userdata) { + // Start the input queue +#ifndef AVOID_TIMESTAMPING + seq.startQueue(queue_id); +#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); + + int err = pthread_create(&thread, &attr, alsaMidiHandler, userdata); + pthread_attr_destroy(&attr); + if ( err ) { + closePort(); + api->error( Error::THREAD_ERROR, + "MidiInAlsa::openPort: error starting MIDI input thread!" ); + return false; + } + return true; + } + }; -//*********************************************************************// -// API: LINUX ALSA -// Class Definitions: MidiInAlsa -//*********************************************************************// +#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) -static void *alsaMidiHandler( void *ptr ) -{ - MidiInApi::RtMidiInData *data = static_cast (ptr); - AlsaMidiData *apiData = static_cast (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: + //*********************************************************************// + // API: LINUX ALSA + // Class Definitions: MidiInAlsa + //*********************************************************************// + + static void *alsaMidiHandler( void *ptr ) + { + MidiInApi::MidiInData *data = static_cast (ptr); + AlsaMidiData *apiData = static_cast (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"; + std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; #endif - break; + break; - case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: + 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; + 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 { + 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"; + 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; -} + } + } + } + + snd_seq_free_event( ev ); + if ( message.bytes.size() == 0 || continueSysex ) continue; + + if ( data->usingCallback ) { + MidiCallback callback = (MidiCallback) 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( 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 (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 ); + MidiInAlsa :: ~MidiInAlsa() + { + // Close a connection if it exists. + closePort(); + + // Shutdown the input thread. + AlsaMidiData *data = static_cast (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->local.client ) data->deletePort(); #ifndef AVOID_TIMESTAMPING - snd_seq_free_queue( data->seq, data->queue_id ); + snd_seq_free_queue( data->seq, data->queue_id ); #endif - snd_seq_close( data->seq ); - delete data; -} + snd_seq_close( data->seq ); + delete data; + } + + void MidiInAlsa :: initialize( const std::string& clientName ) + { +#if 0 + /* this will be done in the AlsaSequencer class */ + // 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( Error::DRIVER_ERROR, errorString_ ); + return; + } +#endif + + // Save our api-specific connection information. + AlsaMidiData *data = new AlsaMidiData (clientName); + // Set client name. + + + + //data->seq = seq; + // data->portNum = -1; + apiData_ = (void *) data; + inputData_.apiData = (void *) 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 + if ( pipe(data->trigger_fds) == -1 ) { + errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; + error( Error::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); + data->queue_id = snd_seq_alloc_named_queue(data->seq, "Midi 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 ) 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; -} + // 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 ) 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 ); + unsigned int MidiInAlsa :: getPortCount() + { + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); - AlsaMidiData *data = static_cast (apiData_); - return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); -} + AlsaMidiData *data = static_cast (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 (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; -} + 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 (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( Error::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 (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 ); - - 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); + void MidiInAlsa :: openPort( unsigned int portNumber, const std::string & portName ) + { + if ( connected_ ) { + errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + + unsigned int nSrc = this->getPortCount(); + if ( nSrc < 1 ) { + errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; + error( Error::NO_DEVICES_FOUND, errorString_ ); + return; + } + + snd_seq_port_info_t *src_pinfo; + snd_seq_port_info_alloca( &src_pinfo ); + AlsaMidiData *data = static_cast (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( Error::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 ); + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + if ( !data->local.client ) { + 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); + 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.client = snd_seq_port_info_get_client( 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 + snd_seq_port_info_set_name(pinfo, portName.c_str() ); + int createok = snd_seq_create_port(data->seq, pinfo); + + if ( createok < 0 ) { + errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + data->local.port = snd_seq_port_info_get_port(pinfo); + data->local.client = snd_seq_port_info_get_client(pinfo); + } + + receiver = data->local; + + if ( !data->subscription ) { + // Make subscription + if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { + errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; + error( Error::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( Error::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 ); + 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; -} + // 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( Error::THREAD_ERROR, errorString_ ); + return; + } + } + + connected_ = true; + } + + void MidiInAlsa :: openPort( const PortDescriptor & port, + const std::string & portName) + { + AlsaMidiData *data = static_cast (apiData_); + const AlsaPortDescriptor * remote = dynamic_cast(&port); + + if ( !data ) { + errorString_ = "MidiInAlsa::openPort: Internal error: data has not been allocated!"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + if ( connected_ ) { + errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + if (data->subscription) { + error( Error::DRIVER_ERROR, + "MidiInAlsa::openPort: ALSA error allocation port subscription." ); + return; + } + if (!remote) { + errorString_ = "MidiInAlsa::openPort: an invalid (i.e. non-ALSA) port descriptor has been passed to openPort!"; + error( Error::WARNING, errorString_ ); + return; + } + + if (!data->local.client) + data->openPort (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, + portName); + data->setRemote(remote); + data->connectPorts(*remote,data->local,false); + + + if ( inputData_.doInput == false ) { + inputData_.doInput = data->startQueue(&inputData_); + } + + connected_ = true; + } -void MidiInAlsa :: openVirtualPort( std::string portName ) -{ - AlsaMidiData *data = static_cast (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); + Pointer MidiInAlsa :: getDescriptor(bool local) + { + AlsaMidiData *data = static_cast (apiData_); + if (local) { + if (data && data->local.client) { + return new AlsaPortDescriptor(data->local,data->getClientName()); + } + } else { + if (data && data->client) { + return new AlsaPortDescriptor(*data,data->getClientName()); + } + } + return NULL; + } + PortList MidiInAlsa :: getPortList(int capabilities) + { + AlsaMidiData *data = static_cast (apiData_); + return AlsaPortDescriptor::getPortList(capabilities | PortDescriptor::INPUT, + data->getClientName()); + } + + + + void MidiInAlsa :: openVirtualPort( std::string portName ) + { + AlsaMidiData *data = static_cast (apiData_); + if ( !data->local.client ) { + 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); + 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 + snd_seq_port_info_set_name(pinfo, portName.c_str()); + int createok = snd_seq_create_port(data->seq, pinfo); + + if ( createok < 0 ) { + errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + data->local.port = snd_seq_port_info_get_port(pinfo); + data->local.client = snd_seq_port_info_get_client(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 ); + 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; - } - } -} + // 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( Error::THREAD_ERROR, errorString_ ); + return; + } + } + } -void MidiInAlsa :: closePort( void ) -{ - AlsaMidiData *data = static_cast (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 + void MidiInAlsa :: closePort( void ) + { + AlsaMidiData *data = static_cast (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 ); + 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 ); - } -} + 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 -//*********************************************************************// + //*********************************************************************// + // API: LINUX ALSA + // Class Definitions: MidiOutAlsa + //*********************************************************************// -MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi() -{ - initialize( clientName ); -} + MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi() + { + initialize( clientName ); + } -MidiOutAlsa :: ~MidiOutAlsa() -{ - // Close a connection if it exists. - closePort(); - - // Cleanup. - AlsaMidiData *data = static_cast (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; -} + MidiOutAlsa :: ~MidiOutAlsa() + { + // Close a connection if it exists. + closePort(); + + // Cleanup. + AlsaMidiData *data = static_cast (apiData_); + if ( data->local.client > 0 ) snd_seq_delete_port( data->seq, data->local.port ); + 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; -} + void MidiOutAlsa :: initialize( const std::string& clientName ) + { +#if 0 + // 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( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Set client name. + snd_seq_set_client_name( seq, clientName.c_str() ); +#endif -unsigned int MidiOutAlsa :: getPortCount() -{ - snd_seq_port_info_t *pinfo; - snd_seq_port_info_alloca( &pinfo ); + // Save our api-specific connection information. + AlsaMidiData *data = new AlsaMidiData(clientName); + // data->seq = seq; + // data->portNum = -1; + + 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( Error::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( Error::MEMORY_ERROR, errorString_ ); + return; + } + snd_midi_event_init( data->coder ); + apiData_ = (void *) data; + } - AlsaMidiData *data = static_cast (apiData_); - return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); -} + unsigned int MidiOutAlsa :: getPortCount() + { + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); -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 (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; -} + AlsaMidiData *data = static_cast (apiData_); + return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); + } -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 (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; -} + 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 (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( Error::WARNING, errorString_ ); + return stringName; + } -void MidiOutAlsa :: closePort( void ) -{ - if ( connected_ ) { - AlsaMidiData *data = static_cast (apiData_); - snd_seq_unsubscribe_port( data->seq, data->subscription ); - snd_seq_port_subscribe_free( data->subscription ); - connected_ = false; - } -} + void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string & portName ) + { + if ( connected_ ) { + errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + + unsigned int nSrc = this->getPortCount(); + if (nSrc < 1) { + errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; + error( Error::NO_DEVICES_FOUND, errorString_ ); + return; + } + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + AlsaMidiData *data = static_cast (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( Error::INVALID_PARAMETER, errorString_ ); + return; + } + + data->client = snd_seq_port_info_get_client( pinfo ); + data->port = snd_seq_port_info_get_port( pinfo ); + data->local.client = snd_seq_client_id( data->seq ); + + if ( !data->local.client ) { + int port = 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 ( port < 0 ) { + errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + + data->local.port = port; + } + + // 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( Error::DRIVER_ERROR, errorString_ ); + return; + } + snd_seq_port_subscribe_set_sender(data->subscription, data); + snd_seq_port_subscribe_set_dest(data->subscription, &data->local); + 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( Error::DRIVER_ERROR, errorString_ ); + return; + } + + connected_ = true; + } -void MidiOutAlsa :: openVirtualPort( std::string portName ) -{ - AlsaMidiData *data = static_cast (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 :: closePort( void ) + { + if ( connected_ ) { + AlsaMidiData *data = static_cast (apiData_); + snd_seq_unsubscribe_port( data->seq, data->subscription ); + snd_seq_port_subscribe_free( data->subscription ); + connected_ = false; + } + } -void MidiOutAlsa :: sendMessage( std::vector *message ) -{ - int result; - AlsaMidiData *data = static_cast (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; ibuffer[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); -} + void MidiOutAlsa :: openVirtualPort( std::string portName ) + { + AlsaMidiData *data = static_cast (apiData_); + if ( !data->local.client ) { + int port = 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 ( port < 0 ) { + errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; + error( Error::DRIVER_ERROR, errorString_ ); + } + data->local.port = port; + data->local.client = snd_seq_client_id(data->seq); + } + } + void MidiOutAlsa :: sendMessage( std::vector &message ) + { + int result; + AlsaMidiData *data = static_cast (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( Error::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( Error::MEMORY_ERROR, errorString_ ); + return; + } + } + + snd_seq_event_t ev; + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, data->local.port); + snd_seq_ev_set_subs(&ev); + snd_seq_ev_set_direct(&ev); + for ( unsigned int i=0; ibuffer[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( Error::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( Error::WARNING, errorString_ ); + return; + } + snd_seq_drain_output(data->seq); + } + + void MidiOutAlsa :: openPort( const PortDescriptor & port, + const std::string & portName) + { + AlsaMidiData *data = static_cast (apiData_); + const AlsaPortDescriptor * remote = dynamic_cast(&port); + + if ( !data ) { + errorString_ = "MidiOutAlsa::openPort: Internal error: data has not been allocated!"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } + if ( connected_ ) { + errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + if (data->subscription) { + error( Error::DRIVER_ERROR, + "MidiOutAlsa::openPort: ALSA error allocation port subscription." ); + return; + } + if (!remote) { + errorString_ = "MidiOutAlsa::openPort: an invalid (i.e. non-ALSA) port descriptor has been passed to openPort!"; + error( Error::WARNING, errorString_ ); + return; + } + + if (!data->local.client) + data->openPort (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ, + portName); + data->setRemote(remote); + data->connectPorts(data->local,*remote,true); + + connected_ = true; + } + Pointer MidiOutAlsa :: getDescriptor(bool local) + { + AlsaMidiData *data = static_cast (apiData_); + if (local) { + if (data && data->local.client) { + return new AlsaPortDescriptor(data->local, data->getClientName()); + } + } else { + if (data && data->client) { + return new AlsaPortDescriptor(*data, data->getClientName()); + } + } + return NULL; + } + PortList MidiOutAlsa :: getPortList(int capabilities) + { + AlsaMidiData *data = static_cast (apiData_); + return AlsaPortDescriptor::getPortList(capabilities | PortDescriptor::OUTPUT, + data->getClientName()); + } +} #endif // __LINUX_ALSA__ @@ -1934,514 +3553,514 @@ void MidiOutAlsa :: sendMessage( std::vector *message ) #define RT_SYSEX_BUFFER_SIZE 1024 #define RT_SYSEX_BUFFER_COUNT 4 +namespace rtmidi{ + // 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::MidiInData *data = static_cast (instancePtr); + MidiInApi::MidiInData *data = (MidiInApi::MidiInData *)instancePtr; + WinMidiData *apiData = static_cast (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; imessage.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 << "\nMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; + + if ( data->ignoreFlags & 0x01 ) return; + } + else return; + } + + if ( data->usingCallback ) { + MidiCallback callback = (MidiCallback) 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 << "\nMidiIn: message queue limit reached!!\n\n"; + } + + // Clear the vector for the next input message. + apiData->message.bytes.clear(); + } -// 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 (instancePtr); - MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; - WinMidiData *apiData = static_cast (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; imessage.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( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) + { + initialize( clientName ); + } -MidiInWinMM :: ~MidiInWinMM() -{ - // Close a connection if it exists. - closePort(); + MidiInWinMM :: ~MidiInWinMM() + { + // Close a connection if it exists. + closePort(); - WinMidiData *data = static_cast (apiData_); - DeleteCriticalSection( &(data->_mutex) ); + WinMidiData *data = static_cast (apiData_); + DeleteCriticalSection( &(data->_mutex) ); - // Cleanup. - delete data; -} + // 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 :: 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( Error::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( Error::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 (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; isysexBuffer[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 :: openPort( unsigned int portNumber, const std::string /*portName*/ ) + { + if ( connected_ ) { + errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + + unsigned int nDevices = midiInGetNumDevs(); + if (nDevices == 0) { + errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; + error( Error::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDevices ) { + std::ostringstream ost; + ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::INVALID_PARAMETER, errorString_ ); + return; + } + + WinMidiData *data = static_cast (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( Error::DRIVER_ERROR, errorString_ ); + return; + } + + // Allocate and init the sysex buffers. + for ( int i=0; isysexBuffer[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( Error::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( Error::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( Error::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 :: 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( Error::WARNING, errorString_ ); + } -void MidiInWinMM :: closePort( void ) -{ - if ( connected_ ) { - WinMidiData *data = static_cast (apiData_); - EnterCriticalSection( &(data->_mutex) ); - midiInReset( data->inHandle ); - midiInStop( data->inHandle ); - - for ( int i=0; iinHandle, 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) ); - } -} + void MidiInWinMM :: closePort( void ) + { + if ( connected_ ) { + WinMidiData *data = static_cast (apiData_); + EnterCriticalSection( &(data->_mutex) ); + midiInReset( data->inHandle ); + midiInStop( data->inHandle ); + + for ( int i=0; iinHandle, 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( Error::DRIVER_ERROR, errorString_ ); + return; + } + } + + midiInClose( data->inHandle ); + connected_ = false; + LeaveCriticalSection( &(data->_mutex) ); + } + } -unsigned int MidiInWinMM :: getPortCount() -{ - return midiInGetNumDevs(); -} + 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)); + 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( Error::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(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); + 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(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); #else - stringName = std::string( deviceCaps.szPname ); + 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(); + // 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; -} + return stringName; + } -//*********************************************************************// -// API: Windows MM -// Class Definitions: MidiOutWinMM -//*********************************************************************// + //*********************************************************************// + // API: Windows MM + // Class Definitions: MidiOutWinMM + //*********************************************************************// -MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi() -{ - initialize( clientName ); -} + MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi() + { + initialize( clientName ); + } -MidiOutWinMM :: ~MidiOutWinMM() -{ - // Close a connection if it exists. - closePort(); + MidiOutWinMM :: ~MidiOutWinMM() + { + // Close a connection if it exists. + closePort(); - // Cleanup. - WinMidiData *data = static_cast (apiData_); - delete data; -} + // Cleanup. + WinMidiData *data = static_cast (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; -} + 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( Error::WARNING, errorString_ ); + } + + // Save our api-specific connection information. + WinMidiData *data = (WinMidiData *) new WinMidiData; + apiData_ = (void *) data; + } -unsigned int MidiOutWinMM :: getPortCount() -{ - return midiOutGetNumDevs(); -} + 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)); + 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( Error::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(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); + 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(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); #else - stringName = std::string( deviceCaps.szPname ); + stringName = std::string( deviceCaps.szPname ); #endif - return stringName; -} + 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 (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 :: openPort( unsigned int portNumber, const std::string /*portName*/ ) + { + if ( connected_ ) { + errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } + + unsigned int nDevices = midiOutGetNumDevs(); + if (nDevices < 1) { + errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; + error( Error::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDevices ) { + std::ostringstream ost; + ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::INVALID_PARAMETER, errorString_ ); + return; + } + + WinMidiData *data = static_cast (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( Error::DRIVER_ERROR, errorString_ ); + return; + } + + connected_ = true; + } -void MidiOutWinMM :: closePort( void ) -{ - if ( connected_ ) { - WinMidiData *data = static_cast (apiData_); - midiOutReset( data->outHandle ); - midiOutClose( data->outHandle ); - connected_ = false; - } -} + void MidiOutWinMM :: closePort( void ) + { + if ( connected_ ) { + WinMidiData *data = static_cast (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 :: 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( Error::WARNING, errorString_ ); + } -void MidiOutWinMM :: sendMessage( std::vector *message ) -{ - if ( !connected_ ) return; - - unsigned int nBytes = static_cast(message->size()); - if ( nBytes == 0 ) { - errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; - error( RtMidiError::WARNING, errorString_ ); - return; - } - - MMRESULT result; - WinMidiData *data = static_cast (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; iat(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; iat(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_ ); - } - } + void MidiOutWinMM :: sendMessage( std::vector *message ) + { + if ( !connected_ ) return; + + unsigned int nBytes = static_cast(message->size()); + if ( nBytes == 0 ) { + errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; + error( Error::WARNING, errorString_ ); + return; + } + + MMRESULT result; + WinMidiData *data = static_cast (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( Error::MEMORY_ERROR, errorString_ ); + return; + } + + // Copy data to buffer. + for ( unsigned int i=0; iat(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( Error::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( Error::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( Error::WARNING, errorString_ ); + return; + } + + // Pack MIDI bytes into double word. + DWORD packet; + unsigned char *ptr = (unsigned char *) &packet; + for ( unsigned int i=0; iat(i); + ++ptr; + } + + // Send the message immediately. + result = midiOutShortMsg( data->outHandle, packet ); + if ( result != MMSYSERR_NOERROR ) { + errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; + error( Error::DRIVER_ERROR, errorString_ ); + } + } + } } - #endif // __WINDOWS_MM__ -// *********************************************************************// -// API: WINDOWS Kernel Streaming -// -// Written by Sebastien Alaiwan, 2012. -// -// NOTE BY GARY: much of the KS-specific code below probably should go in a separate file. -// -// *********************************************************************// + // *********************************************************************// + // API: WINDOWS Kernel Streaming + // + // Written by Sebastien Alaiwan, 2012. + // + // NOTE BY GARY: much of the KS-specific code below probably should go in a separate file. + // + // *********************************************************************// #if defined(__WINDOWS_KS__) @@ -2470,1012 +4089,1012 @@ INSTANTIATE_GUID(KSDATAFORMAT_SUBTYPE_MIDI); INSTANTIATE_GUID(KSDATAFORMAT_SPECIFIER_NONE); #undef INSTANTIATE_GUID +namespace rtmidi { + typedef std::basic_string tstring; -typedef std::basic_string tstring; - -inline bool IsValid(HANDLE handle) -{ - return handle != NULL && handle != INVALID_HANDLE_VALUE; -} + inline bool IsValid(HANDLE handle) + { + return handle != NULL && handle != INVALID_HANDLE_VALUE; + } -class ComException : public std::runtime_error -{ -private: - static std::string MakeString(std::string const& s, HRESULT hr) - { - std::stringstream ss; - ss << "(error 0x" << std::hex << hr << ")"; - return s + ss.str(); - } - -public: - ComException(std::string const& s, HRESULT hr) : - std::runtime_error(MakeString(s, hr)) - { - } -}; - -template -class CKsEnumFilters -{ -public: - ~CKsEnumFilters() - { - DestroyLists(); - } - - void EnumFilters(GUID const* categories, size_t numCategories) - { - DestroyLists(); - - if (categories == 0) - throw std::runtime_error("CKsEnumFilters: invalid argument"); - - // Get a handle to the device set specified by the guid - HDEVINFO hDevInfo = ::SetupDiGetClassDevs(&categories[0], NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (!IsValid(hDevInfo)) - throw std::runtime_error("CKsEnumFilters: no devices found"); - - // Loop through members of the set and get details for each - for ( int iClassMember=0; iClassMember++ ) { - try { - SP_DEVICE_INTERFACE_DATA DID; - DID.cbSize = sizeof(DID); - DID.Reserved = 0; - - bool fRes = ::SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &categories[0], iClassMember, &DID); - if (!fRes) - break; - - // Get filter friendly name - HKEY hRegKey = ::SetupDiOpenDeviceInterfaceRegKey(hDevInfo, &DID, 0, KEY_READ); - if (hRegKey == INVALID_HANDLE_VALUE) - throw std::runtime_error("CKsEnumFilters: interface has no registry"); - - char friendlyName[256]; - DWORD dwSize = sizeof friendlyName; - LONG lval = ::RegQueryValueEx(hRegKey, TEXT("FriendlyName"), NULL, NULL, (LPBYTE)friendlyName, &dwSize); - ::RegCloseKey(hRegKey); - if (lval != ERROR_SUCCESS) - throw std::runtime_error("CKsEnumFilters: interface has no friendly name"); - - // Get details for the device registered in this class - DWORD const cbItfDetails = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + MAX_PATH * sizeof(WCHAR); - std::vector buffer(cbItfDetails); - - SP_DEVICE_INTERFACE_DETAIL_DATA* pDevInterfaceDetails = reinterpret_cast(&buffer[0]); - pDevInterfaceDetails->cbSize = sizeof(*pDevInterfaceDetails); - - SP_DEVINFO_DATA DevInfoData; - DevInfoData.cbSize = sizeof(DevInfoData); - DevInfoData.Reserved = 0; - - fRes = ::SetupDiGetDeviceInterfaceDetail(hDevInfo, &DID, pDevInterfaceDetails, cbItfDetails, NULL, &DevInfoData); - if (!fRes) - throw std::runtime_error("CKsEnumFilters: could not get interface details"); - - // check additional category guids which may (or may not) have been supplied - for (size_t i=1; i < numCategories; ++i) { - SP_DEVICE_INTERFACE_DATA DIDAlias; - DIDAlias.cbSize = sizeof(DIDAlias); - DIDAlias.Reserved = 0; - - fRes = ::SetupDiGetDeviceInterfaceAlias(hDevInfo, &DID, &categories[i], &DIDAlias); - if (!fRes) - throw std::runtime_error("CKsEnumFilters: could not get interface alias"); - - // Check if the this interface alias is enabled. - if (!DIDAlias.Flags || (DIDAlias.Flags & SPINT_REMOVED)) - throw std::runtime_error("CKsEnumFilters: interface alias is not enabled"); - } - - std::auto_ptr pFilter(new TFilterType(pDevInterfaceDetails->DevicePath, friendlyName)); - - pFilter->Instantiate(); - pFilter->FindMidiPins(); - pFilter->Validate(); - - m_Filters.push_back(pFilter.release()); - } - catch (std::runtime_error const& e) { - } - } - - ::SetupDiDestroyDeviceInfoList(hDevInfo); - } - -private: - void DestroyLists() - { - for (size_t i=0;i < m_Filters.size();++i) - delete m_Filters[i]; - m_Filters.clear(); - } - -public: - // TODO: make this private. - std::vector m_Filters; -}; - -class CKsObject -{ -public: - CKsObject(HANDLE handle) : m_handle(handle) - { - } - -protected: - HANDLE m_handle; - - void SetProperty(REFGUID guidPropertySet, ULONG nProperty, void* pvValue, ULONG cbValue) - { - KSPROPERTY ksProperty; - memset(&ksProperty, 0, sizeof ksProperty); - ksProperty.Set = guidPropertySet; - ksProperty.Id = nProperty; - ksProperty.Flags = KSPROPERTY_TYPE_SET; - - HRESULT hr = DeviceIoControlKsProperty(ksProperty, pvValue, cbValue); - if (FAILED(hr)) - throw ComException("CKsObject::SetProperty: could not set property", hr); - } - -private: - - HRESULT DeviceIoControlKsProperty(KSPROPERTY& ksProperty, void* pvValue, ULONG cbValue) - { - ULONG ulReturned; - return ::DeviceIoControl( - m_handle, - IOCTL_KS_PROPERTY, - &ksProperty, - sizeof(ksProperty), - pvValue, - cbValue, - &ulReturned, - NULL); - } -}; - -class CKsPin; - -class CKsFilter : public CKsObject -{ - friend class CKsPin; - -public: - CKsFilter(tstring const& name, std::string const& sFriendlyName); - virtual ~CKsFilter(); - - virtual void Instantiate(); - - template - T GetPinProperty(ULONG nPinId, ULONG nProperty) - { - ULONG ulReturned = 0; - T value; - - KSP_PIN ksPProp; - ksPProp.Property.Set = KSPROPSETID_Pin; - ksPProp.Property.Id = nProperty; - ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; - ksPProp.PinId = nPinId; - ksPProp.Reserved = 0; - - HRESULT hr = ::DeviceIoControl( - m_handle, - IOCTL_KS_PROPERTY, - &ksPProp, - sizeof(KSP_PIN), - &value, - sizeof(value), - &ulReturned, - NULL); - if (FAILED(hr)) - throw ComException("CKsFilter::GetPinProperty: failed to retrieve property", hr); - - return value; - } - - void GetPinPropertyMulti(ULONG nPinId, REFGUID guidPropertySet, ULONG nProperty, PKSMULTIPLE_ITEM* ppKsMultipleItem) - { - HRESULT hr; - - KSP_PIN ksPProp; - ksPProp.Property.Set = guidPropertySet; - ksPProp.Property.Id = nProperty; - ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; - ksPProp.PinId = nPinId; - ksPProp.Reserved = 0; - - ULONG cbMultipleItem = 0; - hr = ::DeviceIoControl(m_handle, - IOCTL_KS_PROPERTY, - &ksPProp.Property, - sizeof(KSP_PIN), - NULL, - 0, - &cbMultipleItem, - NULL); - if (FAILED(hr)) - throw ComException("CKsFilter::GetPinPropertyMulti: cannot get property", hr); - - *ppKsMultipleItem = (PKSMULTIPLE_ITEM) new BYTE[cbMultipleItem]; - - ULONG ulReturned = 0; - hr = ::DeviceIoControl( - m_handle, - IOCTL_KS_PROPERTY, - &ksPProp, - sizeof(KSP_PIN), - (PVOID)*ppKsMultipleItem, - cbMultipleItem, - &ulReturned, - NULL); - if (FAILED(hr)) - throw ComException("CKsFilter::GetPinPropertyMulti: cannot get property", hr); - } - - std::string const& GetFriendlyName() const - { - return m_sFriendlyName; - } - -protected: - - std::vector m_Pins; // this list owns the pins. - - std::vector m_RenderPins; - std::vector m_CapturePins; - -private: - std::string const m_sFriendlyName; // friendly name eg "Virus TI Synth" - tstring const m_sName; // Filter path, eg "\\?\usb#vid_133e&pid_0815...\vtimidi02" -}; - -class CKsPin : public CKsObject -{ -public: - CKsPin(CKsFilter* pFilter, ULONG nId); - virtual ~CKsPin(); - - virtual void Instantiate(); - - void ClosePin(); - - void SetState(KSSTATE ksState); - - void WriteData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED); - void ReadData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED); - - KSPIN_DATAFLOW GetDataFlow() const - { - return m_DataFlow; - } - - bool IsSink() const - { - return m_Communication == KSPIN_COMMUNICATION_SINK - || m_Communication == KSPIN_COMMUNICATION_BOTH; - } - - -protected: - PKSPIN_CONNECT m_pKsPinConnect; // creation parameters of pin - CKsFilter* const m_pFilter; - - ULONG m_cInterfaces; - PKSIDENTIFIER m_pInterfaces; - PKSMULTIPLE_ITEM m_pmiInterfaces; - - ULONG m_cMediums; - PKSIDENTIFIER m_pMediums; - PKSMULTIPLE_ITEM m_pmiMediums; - - ULONG m_cDataRanges; - PKSDATARANGE m_pDataRanges; - PKSMULTIPLE_ITEM m_pmiDataRanges; - - KSPIN_DATAFLOW m_DataFlow; - KSPIN_COMMUNICATION m_Communication; -}; - -CKsFilter::CKsFilter(tstring const& sName, std::string const& sFriendlyName) : - CKsObject(INVALID_HANDLE_VALUE), - m_sFriendlyName(sFriendlyName), - m_sName(sName) -{ - if (sName.empty()) - throw std::runtime_error("CKsFilter::CKsFilter: name can't be empty"); -} + class ComException : public std::runtime_error + { + private: + static std::string MakeString(std::string const& s, HRESULT hr) + { + std::stringstream ss; + ss << "(error 0x" << std::hex << hr << ")"; + return s + ss.str(); + } + + public: + ComException(std::string const& s, HRESULT hr) : + std::runtime_error(MakeString(s, hr)) + { + } + }; + + template + class CKsEnumFilters + { + public: + ~CKsEnumFilters() + { + DestroyLists(); + } + + void EnumFilters(GUID const* categories, size_t numCategories) + { + DestroyLists(); + + if (categories == 0) + throw std::runtime_error("CKsEnumFilters: invalid argument"); + + // Get a handle to the device set specified by the guid + HDEVINFO hDevInfo = ::SetupDiGetClassDevs(&categories[0], NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (!IsValid(hDevInfo)) + throw std::runtime_error("CKsEnumFilters: no devices found"); + + // Loop through members of the set and get details for each + for ( int iClassMember=0; iClassMember++ ) { + try { + SP_DEVICE_INTERFACE_DATA DID; + DID.cbSize = sizeof(DID); + DID.Reserved = 0; + + bool fRes = ::SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &categories[0], iClassMember, &DID); + if (!fRes) + break; + + // Get filter friendly name + HKEY hRegKey = ::SetupDiOpenDeviceInterfaceRegKey(hDevInfo, &DID, 0, KEY_READ); + if (hRegKey == INVALID_HANDLE_VALUE) + throw std::runtime_error("CKsEnumFilters: interface has no registry"); + + char friendlyName[256]; + DWORD dwSize = sizeof friendlyName; + LONG lval = ::RegQueryValueEx(hRegKey, TEXT("FriendlyName"), NULL, NULL, (LPBYTE)friendlyName, &dwSize); + ::RegCloseKey(hRegKey); + if (lval != ERROR_SUCCESS) + throw std::runtime_error("CKsEnumFilters: interface has no friendly name"); + + // Get details for the device registered in this class + DWORD const cbItfDetails = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + MAX_PATH * sizeof(WCHAR); + std::vector buffer(cbItfDetails); + + SP_DEVICE_INTERFACE_DETAIL_DATA* pDevInterfaceDetails = reinterpret_cast(&buffer[0]); + pDevInterfaceDetails->cbSize = sizeof(*pDevInterfaceDetails); + + SP_DEVINFO_DATA DevInfoData; + DevInfoData.cbSize = sizeof(DevInfoData); + DevInfoData.Reserved = 0; + + fRes = ::SetupDiGetDeviceInterfaceDetail(hDevInfo, &DID, pDevInterfaceDetails, cbItfDetails, NULL, &DevInfoData); + if (!fRes) + throw std::runtime_error("CKsEnumFilters: could not get interface details"); + + // check additional category guids which may (or may not) have been supplied + for (size_t i=1; i < numCategories; ++i) { + SP_DEVICE_INTERFACE_DATA DIDAlias; + DIDAlias.cbSize = sizeof(DIDAlias); + DIDAlias.Reserved = 0; + + fRes = ::SetupDiGetDeviceInterfaceAlias(hDevInfo, &DID, &categories[i], &DIDAlias); + if (!fRes) + throw std::runtime_error("CKsEnumFilters: could not get interface alias"); + + // Check if the this interface alias is enabled. + if (!DIDAlias.Flags || (DIDAlias.Flags & SPINT_REMOVED)) + throw std::runtime_error("CKsEnumFilters: interface alias is not enabled"); + } + + std::auto_ptr pFilter(new TFilterType(pDevInterfaceDetails->DevicePath, friendlyName)); + + pFilter->Instantiate(); + pFilter->FindMidiPins(); + pFilter->Validate(); + + m_Filters.push_back(pFilter.release()); + } + catch (std::runtime_error const& e) { + } + } + + ::SetupDiDestroyDeviceInfoList(hDevInfo); + } + + private: + void DestroyLists() + { + for (size_t i=0;i < m_Filters.size();++i) + delete m_Filters[i]; + m_Filters.clear(); + } + + public: + // TODO: make this private. + std::vector m_Filters; + }; + + class CKsObject + { + public: + CKsObject(HANDLE handle) : m_handle(handle) + { + } + + protected: + HANDLE m_handle; + + void SetProperty(REFGUID guidPropertySet, ULONG nProperty, void* pvValue, ULONG cbValue) + { + KSPROPERTY ksProperty; + memset(&ksProperty, 0, sizeof ksProperty); + ksProperty.Set = guidPropertySet; + ksProperty.Id = nProperty; + ksProperty.Flags = KSPROPERTY_TYPE_SET; + + HRESULT hr = DeviceIoControlKsProperty(ksProperty, pvValue, cbValue); + if (FAILED(hr)) + throw ComException("CKsObject::SetProperty: could not set property", hr); + } + + private: + + HRESULT DeviceIoControlKsProperty(KSPROPERTY& ksProperty, void* pvValue, ULONG cbValue) + { + ULONG ulReturned; + return ::DeviceIoControl( + m_handle, + IOCTL_KS_PROPERTY, + &ksProperty, + sizeof(ksProperty), + pvValue, + cbValue, + &ulReturned, + NULL); + } + }; + + class CKsPin; + + class CKsFilter : public CKsObject + { + friend class CKsPin; + + public: + CKsFilter(tstring const& name, std::string const& sFriendlyName); + virtual ~CKsFilter(); + + virtual void Instantiate(); + + template + T GetPinProperty(ULONG nPinId, ULONG nProperty) + { + ULONG ulReturned = 0; + T value; + + KSP_PIN ksPProp; + ksPProp.Property.Set = KSPROPSETID_Pin; + ksPProp.Property.Id = nProperty; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = nPinId; + ksPProp.Reserved = 0; + + HRESULT hr = ::DeviceIoControl( + m_handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + &value, + sizeof(value), + &ulReturned, + NULL); + if (FAILED(hr)) + throw ComException("CKsFilter::GetPinProperty: failed to retrieve property", hr); + + return value; + } + + void GetPinPropertyMulti(ULONG nPinId, REFGUID guidPropertySet, ULONG nProperty, PKSMULTIPLE_ITEM* ppKsMultipleItem) + { + HRESULT hr; + + KSP_PIN ksPProp; + ksPProp.Property.Set = guidPropertySet; + ksPProp.Property.Id = nProperty; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = nPinId; + ksPProp.Reserved = 0; + + ULONG cbMultipleItem = 0; + hr = ::DeviceIoControl(m_handle, + IOCTL_KS_PROPERTY, + &ksPProp.Property, + sizeof(KSP_PIN), + NULL, + 0, + &cbMultipleItem, + NULL); + if (FAILED(hr)) + throw ComException("CKsFilter::GetPinPropertyMulti: cannot get property", hr); + + *ppKsMultipleItem = (PKSMULTIPLE_ITEM) new BYTE[cbMultipleItem]; + + ULONG ulReturned = 0; + hr = ::DeviceIoControl( + m_handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + (PVOID)*ppKsMultipleItem, + cbMultipleItem, + &ulReturned, + NULL); + if (FAILED(hr)) + throw ComException("CKsFilter::GetPinPropertyMulti: cannot get property", hr); + } + + std::string const& GetFriendlyName() const + { + return m_sFriendlyName; + } + + protected: + + std::vector m_Pins; // this list owns the pins. + + std::vector m_RenderPins; + std::vector m_CapturePins; + + private: + std::string const m_sFriendlyName; // friendly name eg "Virus TI Synth" + tstring const m_sName; // Filter path, eg "\\?\usb#vid_133e&pid_0815...\vtimidi02" + }; + + class CKsPin : public CKsObject + { + public: + CKsPin(CKsFilter* pFilter, ULONG nId); + virtual ~CKsPin(); + + virtual void Instantiate(); + + void ClosePin(); + + void SetState(KSSTATE ksState); + + void WriteData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED); + void ReadData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED); + + KSPIN_DATAFLOW GetDataFlow() const + { + return m_DataFlow; + } + + bool IsSink() const + { + return m_Communication == KSPIN_COMMUNICATION_SINK + || m_Communication == KSPIN_COMMUNICATION_BOTH; + } + + + protected: + PKSPIN_CONNECT m_pKsPinConnect; // creation parameters of pin + CKsFilter* const m_pFilter; + + ULONG m_cInterfaces; + PKSIDENTIFIER m_pInterfaces; + PKSMULTIPLE_ITEM m_pmiInterfaces; + + ULONG m_cMediums; + PKSIDENTIFIER m_pMediums; + PKSMULTIPLE_ITEM m_pmiMediums; + + ULONG m_cDataRanges; + PKSDATARANGE m_pDataRanges; + PKSMULTIPLE_ITEM m_pmiDataRanges; + + KSPIN_DATAFLOW m_DataFlow; + KSPIN_COMMUNICATION m_Communication; + }; + + CKsFilter::CKsFilter(tstring const& sName, std::string const& sFriendlyName) : + CKsObject(INVALID_HANDLE_VALUE), + m_sFriendlyName(sFriendlyName), + m_sName(sName) + { + if (sName.empty()) + throw std::runtime_error("CKsFilter::CKsFilter: name can't be empty"); + } -CKsFilter::~CKsFilter() -{ - for (size_t i=0;i < m_Pins.size();++i) - delete m_Pins[i]; + CKsFilter::~CKsFilter() + { + for (size_t i=0;i < m_Pins.size();++i) + delete m_Pins[i]; - if (IsValid(m_handle)) - ::CloseHandle(m_handle); -} + if (IsValid(m_handle)) + ::CloseHandle(m_handle); + } -void CKsFilter::Instantiate() -{ - m_handle = CreateFile( - m_sName.c_str(), - GENERIC_READ | GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL); - - if (!IsValid(m_handle)) - { - DWORD const dwError = GetLastError(); - throw ComException("CKsFilter::Instantiate: can't open driver", HRESULT_FROM_WIN32(dwError)); - } -} + void CKsFilter::Instantiate() + { + m_handle = CreateFile( + m_sName.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + + if (!IsValid(m_handle)) + { + DWORD const dwError = GetLastError(); + throw ComException("CKsFilter::Instantiate: can't open driver", HRESULT_FROM_WIN32(dwError)); + } + } -CKsPin::CKsPin(CKsFilter* pFilter, ULONG PinId) : - CKsObject(INVALID_HANDLE_VALUE), - m_pKsPinConnect(NULL), - m_pFilter(pFilter) -{ - m_Communication = m_pFilter->GetPinProperty(PinId, KSPROPERTY_PIN_COMMUNICATION); - m_DataFlow = m_pFilter->GetPinProperty(PinId, KSPROPERTY_PIN_DATAFLOW); - - // Interfaces - m_pFilter->GetPinPropertyMulti( - PinId, - KSPROPSETID_Pin, - KSPROPERTY_PIN_INTERFACES, - &m_pmiInterfaces); - - m_cInterfaces = m_pmiInterfaces->Count; - m_pInterfaces = (PKSPIN_INTERFACE)(m_pmiInterfaces + 1); - - // Mediums - m_pFilter->GetPinPropertyMulti( - PinId, - KSPROPSETID_Pin, - KSPROPERTY_PIN_MEDIUMS, - &m_pmiMediums); - - m_cMediums = m_pmiMediums->Count; - m_pMediums = (PKSPIN_MEDIUM)(m_pmiMediums + 1); - - // Data ranges - m_pFilter->GetPinPropertyMulti( - PinId, - KSPROPSETID_Pin, - KSPROPERTY_PIN_DATARANGES, - &m_pmiDataRanges); - - m_cDataRanges = m_pmiDataRanges->Count; - m_pDataRanges = (PKSDATARANGE)(m_pmiDataRanges + 1); -} + CKsPin::CKsPin(CKsFilter* pFilter, ULONG PinId) : + CKsObject(INVALID_HANDLE_VALUE), + m_pKsPinConnect(NULL), + m_pFilter(pFilter) + { + m_Communication = m_pFilter->GetPinProperty(PinId, KSPROPERTY_PIN_COMMUNICATION); + m_DataFlow = m_pFilter->GetPinProperty(PinId, KSPROPERTY_PIN_DATAFLOW); + + // Interfaces + m_pFilter->GetPinPropertyMulti( + PinId, + KSPROPSETID_Pin, + KSPROPERTY_PIN_INTERFACES, + &m_pmiInterfaces); + + m_cInterfaces = m_pmiInterfaces->Count; + m_pInterfaces = (PKSPIN_INTERFACE)(m_pmiInterfaces + 1); + + // Mediums + m_pFilter->GetPinPropertyMulti( + PinId, + KSPROPSETID_Pin, + KSPROPERTY_PIN_MEDIUMS, + &m_pmiMediums); + + m_cMediums = m_pmiMediums->Count; + m_pMediums = (PKSPIN_MEDIUM)(m_pmiMediums + 1); + + // Data ranges + m_pFilter->GetPinPropertyMulti( + PinId, + KSPROPSETID_Pin, + KSPROPERTY_PIN_DATARANGES, + &m_pmiDataRanges); + + m_cDataRanges = m_pmiDataRanges->Count; + m_pDataRanges = (PKSDATARANGE)(m_pmiDataRanges + 1); + } -CKsPin::~CKsPin() -{ - ClosePin(); + CKsPin::~CKsPin() + { + ClosePin(); - delete[] (BYTE*)m_pKsPinConnect; - delete[] (BYTE*)m_pmiDataRanges; - delete[] (BYTE*)m_pmiInterfaces; - delete[] (BYTE*)m_pmiMediums; -} + delete[] (BYTE*)m_pKsPinConnect; + delete[] (BYTE*)m_pmiDataRanges; + delete[] (BYTE*)m_pmiInterfaces; + delete[] (BYTE*)m_pmiMediums; + } -void CKsPin::ClosePin() -{ - if (IsValid(m_handle)) { - SetState(KSSTATE_STOP); - ::CloseHandle(m_handle); - } - m_handle = INVALID_HANDLE_VALUE; -} + void CKsPin::ClosePin() + { + if (IsValid(m_handle)) { + SetState(KSSTATE_STOP); + ::CloseHandle(m_handle); + } + m_handle = INVALID_HANDLE_VALUE; + } -void CKsPin::SetState(KSSTATE ksState) -{ - SetProperty(KSPROPSETID_Connection, KSPROPERTY_CONNECTION_STATE, &ksState, sizeof(ksState)); -} + void CKsPin::SetState(KSSTATE ksState) + { + SetProperty(KSPROPSETID_Connection, KSPROPERTY_CONNECTION_STATE, &ksState, sizeof(ksState)); + } -void CKsPin::Instantiate() -{ - if (!m_pKsPinConnect) - throw std::runtime_error("CKsPin::Instanciate: abstract pin"); + void CKsPin::Instantiate() + { + if (!m_pKsPinConnect) + throw std::runtime_error("CKsPin::Instanciate: abstract pin"); - DWORD const dwResult = KsCreatePin(m_pFilter->m_handle, m_pKsPinConnect, GENERIC_WRITE | GENERIC_READ, &m_handle); - if (dwResult != ERROR_SUCCESS) - throw ComException("CKsMidiCapFilter::CreateRenderPin: Pin instanciation failed", HRESULT_FROM_WIN32(dwResult)); -} + DWORD const dwResult = KsCreatePin(m_pFilter->m_handle, m_pKsPinConnect, GENERIC_WRITE | GENERIC_READ, &m_handle); + if (dwResult != ERROR_SUCCESS) + throw ComException("CKsMidiCapFilter::CreateRenderPin: Pin instanciation failed", HRESULT_FROM_WIN32(dwResult)); + } -void CKsPin::WriteData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED) -{ - DWORD cbWritten; - BOOL fRes = ::DeviceIoControl( - m_handle, - IOCTL_KS_WRITE_STREAM, - NULL, - 0, - pKSSTREAM_HEADER, - pKSSTREAM_HEADER->Size, - &cbWritten, - pOVERLAPPED); - if (!fRes) { - DWORD const dwError = GetLastError(); - if (dwError != ERROR_IO_PENDING) - throw ComException("CKsPin::WriteData: DeviceIoControl failed", HRESULT_FROM_WIN32(dwError)); - } -} + void CKsPin::WriteData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED) + { + DWORD cbWritten; + BOOL fRes = ::DeviceIoControl( + m_handle, + IOCTL_KS_WRITE_STREAM, + NULL, + 0, + pKSSTREAM_HEADER, + pKSSTREAM_HEADER->Size, + &cbWritten, + pOVERLAPPED); + if (!fRes) { + DWORD const dwError = GetLastError(); + if (dwError != ERROR_IO_PENDING) + throw ComException("CKsPin::WriteData: DeviceIoControl failed", HRESULT_FROM_WIN32(dwError)); + } + } -void CKsPin::ReadData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED) -{ - DWORD cbReturned; - BOOL fRes = ::DeviceIoControl( - m_handle, - IOCTL_KS_READ_STREAM, - NULL, - 0, - pKSSTREAM_HEADER, - pKSSTREAM_HEADER->Size, - &cbReturned, - pOVERLAPPED); - if (!fRes) { - DWORD const dwError = GetLastError(); - if (dwError != ERROR_IO_PENDING) - throw ComException("CKsPin::ReadData: DeviceIoControl failed", HRESULT_FROM_WIN32(dwError)); - } -} + void CKsPin::ReadData(KSSTREAM_HEADER* pKSSTREAM_HEADER, OVERLAPPED* pOVERLAPPED) + { + DWORD cbReturned; + BOOL fRes = ::DeviceIoControl( + m_handle, + IOCTL_KS_READ_STREAM, + NULL, + 0, + pKSSTREAM_HEADER, + pKSSTREAM_HEADER->Size, + &cbReturned, + pOVERLAPPED); + if (!fRes) { + DWORD const dwError = GetLastError(); + if (dwError != ERROR_IO_PENDING) + throw ComException("CKsPin::ReadData: DeviceIoControl failed", HRESULT_FROM_WIN32(dwError)); + } + } -class CKsMidiFilter : public CKsFilter -{ -public: - void FindMidiPins(); - -protected: - CKsMidiFilter(tstring const& sPath, std::string const& sFriendlyName); -}; - -class CKsMidiPin : public CKsPin -{ -public: - CKsMidiPin(CKsFilter* pFilter, ULONG nId); -}; - -class CKsMidiRenFilter : public CKsMidiFilter -{ -public: - CKsMidiRenFilter(tstring const& sPath, std::string const& sFriendlyName); - CKsMidiPin* CreateRenderPin(); - - void Validate() - { - if (m_RenderPins.empty()) - throw std::runtime_error("Could not find a MIDI render pin"); - } -}; - -class CKsMidiCapFilter : public CKsMidiFilter -{ -public: - CKsMidiCapFilter(tstring const& sPath, std::string const& sFriendlyName); - CKsMidiPin* CreateCapturePin(); - - void Validate() - { - if (m_CapturePins.empty()) - throw std::runtime_error("Could not find a MIDI capture pin"); - } -}; - -CKsMidiFilter::CKsMidiFilter(tstring const& sPath, std::string const& sFriendlyName) : - CKsFilter(sPath, sFriendlyName) -{ -} + class CKsMidiFilter : public CKsFilter + { + public: + void FindMidiPins(); + + protected: + CKsMidiFilter(tstring const& sPath, std::string const& sFriendlyName); + }; + + class CKsMidiPin : public CKsPin + { + public: + CKsMidiPin(CKsFilter* pFilter, ULONG nId); + }; + + class CKsMidiRenFilter : public CKsMidiFilter + { + public: + CKsMidiRenFilter(tstring const& sPath, std::string const& sFriendlyName); + CKsMidiPin* CreateRenderPin(); + + void Validate() + { + if (m_RenderPins.empty()) + throw std::runtime_error("Could not find a MIDI render pin"); + } + }; + + class CKsMidiCapFilter : public CKsMidiFilter + { + public: + CKsMidiCapFilter(tstring const& sPath, std::string const& sFriendlyName); + CKsMidiPin* CreateCapturePin(); + + void Validate() + { + if (m_CapturePins.empty()) + throw std::runtime_error("Could not find a MIDI capture pin"); + } + }; + + CKsMidiFilter::CKsMidiFilter(tstring const& sPath, std::string const& sFriendlyName) : + CKsFilter(sPath, sFriendlyName) + { + } -void CKsMidiFilter::FindMidiPins() -{ - ULONG numPins = GetPinProperty(0, KSPROPERTY_PIN_CTYPES); - - for (ULONG iPin = 0; iPin < numPins; ++iPin) { - try { - KSPIN_COMMUNICATION com = GetPinProperty(iPin, KSPROPERTY_PIN_COMMUNICATION); - if (com != KSPIN_COMMUNICATION_SINK && com != KSPIN_COMMUNICATION_BOTH) - throw std::runtime_error("Unknown pin communication value"); - - m_Pins.push_back(new CKsMidiPin(this, iPin)); - } - catch (std::runtime_error const&) { - // pin instanciation has failed, continue to the next pin. - } - } - - m_RenderPins.clear(); - m_CapturePins.clear(); - - for (size_t i = 0; i < m_Pins.size(); ++i) { - CKsPin* const pPin = m_Pins[i]; - - if (pPin->IsSink()) { - if (pPin->GetDataFlow() == KSPIN_DATAFLOW_IN) - m_RenderPins.push_back(pPin); - else - m_CapturePins.push_back(pPin); - } - } - - if (m_RenderPins.empty() && m_CapturePins.empty()) - throw std::runtime_error("No valid pins found on the filter."); -} + void CKsMidiFilter::FindMidiPins() + { + ULONG numPins = GetPinProperty(0, KSPROPERTY_PIN_CTYPES); + + for (ULONG iPin = 0; iPin < numPins; ++iPin) { + try { + KSPIN_COMMUNICATION com = GetPinProperty(iPin, KSPROPERTY_PIN_COMMUNICATION); + if (com != KSPIN_COMMUNICATION_SINK && com != KSPIN_COMMUNICATION_BOTH) + throw std::runtime_error("Unknown pin communication value"); + + m_Pins.push_back(new CKsMidiPin(this, iPin)); + } + catch (std::runtime_error const&) { + // pin instanciation has failed, continue to the next pin. + } + } + + m_RenderPins.clear(); + m_CapturePins.clear(); + + for (size_t i = 0; i < m_Pins.size(); ++i) { + CKsPin* const pPin = m_Pins[i]; + + if (pPin->IsSink()) { + if (pPin->GetDataFlow() == KSPIN_DATAFLOW_IN) + m_RenderPins.push_back(pPin); + else + m_CapturePins.push_back(pPin); + } + } + + if (m_RenderPins.empty() && m_CapturePins.empty()) + throw std::runtime_error("No valid pins found on the filter."); + } -CKsMidiRenFilter::CKsMidiRenFilter(tstring const& sPath, std::string const& sFriendlyName) : - CKsMidiFilter(sPath, sFriendlyName) -{ -} + CKsMidiRenFilter::CKsMidiRenFilter(tstring const& sPath, std::string const& sFriendlyName) : + CKsMidiFilter(sPath, sFriendlyName) + { + } -CKsMidiPin* CKsMidiRenFilter::CreateRenderPin() -{ - if (m_RenderPins.empty()) - throw std::runtime_error("Could not find a MIDI render pin"); + CKsMidiPin* CKsMidiRenFilter::CreateRenderPin() + { + if (m_RenderPins.empty()) + throw std::runtime_error("Could not find a MIDI render pin"); - CKsMidiPin* pPin = (CKsMidiPin*)m_RenderPins[0]; - pPin->Instantiate(); - return pPin; -} + CKsMidiPin* pPin = (CKsMidiPin*)m_RenderPins[0]; + pPin->Instantiate(); + return pPin; + } -CKsMidiCapFilter::CKsMidiCapFilter(tstring const& sPath, std::string const& sFriendlyName) : - CKsMidiFilter(sPath, sFriendlyName) -{ -} + CKsMidiCapFilter::CKsMidiCapFilter(tstring const& sPath, std::string const& sFriendlyName) : + CKsMidiFilter(sPath, sFriendlyName) + { + } -CKsMidiPin* CKsMidiCapFilter::CreateCapturePin() -{ - if (m_CapturePins.empty()) - throw std::runtime_error("Could not find a MIDI capture pin"); + CKsMidiPin* CKsMidiCapFilter::CreateCapturePin() + { + if (m_CapturePins.empty()) + throw std::runtime_error("Could not find a MIDI capture pin"); - CKsMidiPin* pPin = (CKsMidiPin*)m_CapturePins[0]; - pPin->Instantiate(); - return pPin; -} + CKsMidiPin* pPin = (CKsMidiPin*)m_CapturePins[0]; + pPin->Instantiate(); + return pPin; + } -CKsMidiPin::CKsMidiPin(CKsFilter* pFilter, ULONG nId) : - CKsPin(pFilter, nId) -{ - DWORD const cbPinCreateSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT); - m_pKsPinConnect = (PKSPIN_CONNECT) new BYTE[cbPinCreateSize]; - - m_pKsPinConnect->Interface.Set = KSINTERFACESETID_Standard; - m_pKsPinConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING; - m_pKsPinConnect->Interface.Flags = 0; - m_pKsPinConnect->Medium.Set = KSMEDIUMSETID_Standard; - m_pKsPinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE; - m_pKsPinConnect->Medium.Flags = 0; - m_pKsPinConnect->PinId = nId; - m_pKsPinConnect->PinToHandle = NULL; - m_pKsPinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; - m_pKsPinConnect->Priority.PrioritySubClass = 1; - - // point m_pDataFormat to just after the pConnect struct - KSDATAFORMAT* m_pDataFormat = (KSDATAFORMAT*)(m_pKsPinConnect + 1); - m_pDataFormat->FormatSize = sizeof(KSDATAFORMAT); - m_pDataFormat->Flags = 0; - m_pDataFormat->SampleSize = 0; - m_pDataFormat->Reserved = 0; - m_pDataFormat->MajorFormat = GUID(KSDATAFORMAT_TYPE_MUSIC); - m_pDataFormat->SubFormat = GUID(KSDATAFORMAT_SUBTYPE_MIDI); - m_pDataFormat->Specifier = GUID(KSDATAFORMAT_SPECIFIER_NONE); - - bool hasStdStreamingInterface = false; - bool hasStdStreamingMedium = false; - - for ( ULONG i = 0; i < m_cInterfaces; i++ ) { - if (m_pInterfaces[i].Set == KSINTERFACESETID_Standard - && m_pInterfaces[i].Id == KSINTERFACE_STANDARD_STREAMING) - hasStdStreamingInterface = true; - } - - for (ULONG i = 0; i < m_cMediums; i++) { - if (m_pMediums[i].Set == KSMEDIUMSETID_Standard - && m_pMediums[i].Id == KSMEDIUM_STANDARD_DEVIO) - hasStdStreamingMedium = true; - } - - if (!hasStdStreamingInterface) // No standard streaming interfaces on the pin - throw std::runtime_error("CKsMidiPin::CKsMidiPin: no standard streaming interface"); - - if (!hasStdStreamingMedium) // No standard streaming mediums on the pin - throw std::runtime_error("CKsMidiPin::CKsMidiPin: no standard streaming medium"); - - bool hasMidiDataRange = false; - - BYTE const* pDataRangePtr = reinterpret_cast(m_pDataRanges); - - for (ULONG i = 0; i < m_cDataRanges; ++i) { - KSDATARANGE const* pDataRange = reinterpret_cast(pDataRangePtr); - - if (pDataRange->SubFormat == KSDATAFORMAT_SUBTYPE_MIDI) { - hasMidiDataRange = true; - break; - } - - pDataRangePtr += pDataRange->FormatSize; - } - - if (!hasMidiDataRange) // No MIDI dataranges on the pin - throw std::runtime_error("CKsMidiPin::CKsMidiPin: no MIDI datarange"); -} + CKsMidiPin::CKsMidiPin(CKsFilter* pFilter, ULONG nId) : + CKsPin(pFilter, nId) + { + DWORD const cbPinCreateSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT); + m_pKsPinConnect = (PKSPIN_CONNECT) new BYTE[cbPinCreateSize]; + + m_pKsPinConnect->Interface.Set = KSINTERFACESETID_Standard; + m_pKsPinConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING; + m_pKsPinConnect->Interface.Flags = 0; + m_pKsPinConnect->Medium.Set = KSMEDIUMSETID_Standard; + m_pKsPinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE; + m_pKsPinConnect->Medium.Flags = 0; + m_pKsPinConnect->PinId = nId; + m_pKsPinConnect->PinToHandle = NULL; + m_pKsPinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; + m_pKsPinConnect->Priority.PrioritySubClass = 1; + + // point m_pDataFormat to just after the pConnect struct + KSDATAFORMAT* m_pDataFormat = (KSDATAFORMAT*)(m_pKsPinConnect + 1); + m_pDataFormat->FormatSize = sizeof(KSDATAFORMAT); + m_pDataFormat->Flags = 0; + m_pDataFormat->SampleSize = 0; + m_pDataFormat->Reserved = 0; + m_pDataFormat->MajorFormat = GUID(KSDATAFORMAT_TYPE_MUSIC); + m_pDataFormat->SubFormat = GUID(KSDATAFORMAT_SUBTYPE_MIDI); + m_pDataFormat->Specifier = GUID(KSDATAFORMAT_SPECIFIER_NONE); + + bool hasStdStreamingInterface = false; + bool hasStdStreamingMedium = false; + + for ( ULONG i = 0; i < m_cInterfaces; i++ ) { + if (m_pInterfaces[i].Set == KSINTERFACESETID_Standard + && m_pInterfaces[i].Id == KSINTERFACE_STANDARD_STREAMING) + hasStdStreamingInterface = true; + } + + for (ULONG i = 0; i < m_cMediums; i++) { + if (m_pMediums[i].Set == KSMEDIUMSETID_Standard + && m_pMediums[i].Id == KSMEDIUM_STANDARD_DEVIO) + hasStdStreamingMedium = true; + } + + if (!hasStdStreamingInterface) // No standard streaming interfaces on the pin + throw std::runtime_error("CKsMidiPin::CKsMidiPin: no standard streaming interface"); + + if (!hasStdStreamingMedium) // No standard streaming mediums on the pin + throw std::runtime_error("CKsMidiPin::CKsMidiPin: no standard streaming medium"); + + bool hasMidiDataRange = false; + + BYTE const* pDataRangePtr = reinterpret_cast(m_pDataRanges); + + for (ULONG i = 0; i < m_cDataRanges; ++i) { + KSDATARANGE const* pDataRange = reinterpret_cast(pDataRangePtr); + + if (pDataRange->SubFormat == KSDATAFORMAT_SUBTYPE_MIDI) { + hasMidiDataRange = true; + break; + } + + pDataRangePtr += pDataRange->FormatSize; + } + + if (!hasMidiDataRange) // No MIDI dataranges on the pin + throw std::runtime_error("CKsMidiPin::CKsMidiPin: no MIDI datarange"); + } -struct WindowsKsData -{ - WindowsKsData() : m_pPin(NULL), m_Buffer(1024), m_hInputThread(NULL) - { - memset(&overlapped, 0, sizeof(OVERLAPPED)); - m_hExitEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); - overlapped.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); - m_hInputThread = NULL; - } - - ~WindowsKsData() - { - ::CloseHandle(overlapped.hEvent); - ::CloseHandle(m_hExitEvent); - } - - OVERLAPPED overlapped; - CKsPin* m_pPin; - std::vector m_Buffer; - std::auto_ptr > m_pCaptureEnum; - std::auto_ptr > m_pRenderEnum; - HANDLE m_hInputThread; - HANDLE m_hExitEvent; -}; - -// *********************************************************************// -// API: WINDOWS Kernel Streaming -// Class Definitions: MidiInWinKS -// *********************************************************************// - -static DWORD WINAPI midiKsInputThread(VOID* pUser) -{ - MidiInApi::RtMidiInData* data = static_cast(pUser); - WindowsKsData* apiData = static_cast(data->apiData); - - HANDLE hEvents[] = { apiData->overlapped.hEvent, apiData->m_hExitEvent }; - - while ( true ) { - KSSTREAM_HEADER packet; - memset(&packet, 0, sizeof packet); - packet.Size = sizeof(KSSTREAM_HEADER); - packet.PresentationTime.Time = 0; - packet.PresentationTime.Numerator = 1; - packet.PresentationTime.Denominator = 1; - packet.Data = &apiData->m_Buffer[0]; - packet.DataUsed = 0; - packet.FrameExtent = apiData->m_Buffer.size(); - apiData->m_pPin->ReadData(&packet, &apiData->overlapped); - - DWORD dwRet = ::WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); - - if ( dwRet == WAIT_OBJECT_0 ) { - // parse packet - unsigned char* pData = (unsigned char*)packet.Data; - unsigned int iOffset = 0; - - while ( iOffset < packet.DataUsed ) { - KSMUSICFORMAT* pMusic = (KSMUSICFORMAT*)&pData[iOffset]; - iOffset += sizeof(KSMUSICFORMAT); - - MidiInApi::MidiMessage message; - message.timeStamp = 0; - for(size_t i=0;i < pMusic->ByteCount;++i) - message.bytes.push_back(pData[iOffset+i]); - - 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 << "\nRtMidiIn: message queue limit reached!!\n\n"; - } - - iOffset += pMusic->ByteCount; - - // re-align on 32 bits - if ( iOffset % 4 != 0 ) - iOffset += (4 - iOffset % 4); - } - } - else - break; - } - return 0; -} + struct WindowsKsData + { + WindowsKsData() : m_pPin(NULL), m_Buffer(1024), m_hInputThread(NULL) + { + memset(&overlapped, 0, sizeof(OVERLAPPED)); + m_hExitEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); + overlapped.hEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL); + m_hInputThread = NULL; + } + + ~WindowsKsData() + { + ::CloseHandle(overlapped.hEvent); + ::CloseHandle(m_hExitEvent); + } + + OVERLAPPED overlapped; + CKsPin* m_pPin; + std::vector m_Buffer; + std::auto_ptr > m_pCaptureEnum; + std::auto_ptr > m_pRenderEnum; + HANDLE m_hInputThread; + HANDLE m_hExitEvent; + }; + + // *********************************************************************// + // API: WINDOWS Kernel Streaming + // Class Definitions: MidiInWinKS + // *********************************************************************// + + static DWORD WINAPI midiKsInputThread(VOID* pUser) + { + MidiInApi::MidiInData* data = static_cast(pUser); + WindowsKsData* apiData = static_cast(data->apiData); + + HANDLE hEvents[] = { apiData->overlapped.hEvent, apiData->m_hExitEvent }; + + while ( true ) { + KSSTREAM_HEADER packet; + memset(&packet, 0, sizeof packet); + packet.Size = sizeof(KSSTREAM_HEADER); + packet.PresentationTime.Time = 0; + packet.PresentationTime.Numerator = 1; + packet.PresentationTime.Denominator = 1; + packet.Data = &apiData->m_Buffer[0]; + packet.DataUsed = 0; + packet.FrameExtent = apiData->m_Buffer.size(); + apiData->m_pPin->ReadData(&packet, &apiData->overlapped); + + DWORD dwRet = ::WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); + + if ( dwRet == WAIT_OBJECT_0 ) { + // parse packet + unsigned char* pData = (unsigned char*)packet.Data; + unsigned int iOffset = 0; + + while ( iOffset < packet.DataUsed ) { + KSMUSICFORMAT* pMusic = (KSMUSICFORMAT*)&pData[iOffset]; + iOffset += sizeof(KSMUSICFORMAT); + + MidiInApi::MidiMessage message; + message.timeStamp = 0; + for(size_t i=0;i < pMusic->ByteCount;++i) + message.bytes.push_back(pData[iOffset+i]); + + if ( data->usingCallback ) { + MidiCallback callback = (MidiCallback)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 << "\nMidiIn: message queue limit reached!!\n\n"; + } + + iOffset += pMusic->ByteCount; + + // re-align on 32 bits + if ( iOffset % 4 != 0 ) + iOffset += (4 - iOffset % 4); + } + } + else + break; + } + return 0; + } -MidiInWinKS :: MidiInWinKS( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) -{ - initialize( clientName ); -} + MidiInWinKS :: MidiInWinKS( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) + { + initialize( clientName ); + } -void MidiInWinKS :: initialize( const std::string& clientName ) -{ - WindowsKsData* data = new WindowsKsData; - apiData_ = (void*)data; - inputData_.apiData = data; - - GUID const aguidEnumCats[] = - { - { STATIC_KSCATEGORY_AUDIO }, { STATIC_KSCATEGORY_CAPTURE } - }; - data->m_pCaptureEnum.reset(new CKsEnumFilters ); - data->m_pCaptureEnum->EnumFilters(aguidEnumCats, 2); -} + void MidiInWinKS :: initialize( const std::string& clientName ) + { + WindowsKsData* data = new WindowsKsData; + apiData_ = (void*)data; + inputData_.apiData = data; + + GUID const aguidEnumCats[] = + { + { STATIC_KSCATEGORY_AUDIO }, { STATIC_KSCATEGORY_CAPTURE } + }; + data->m_pCaptureEnum.reset(new CKsEnumFilters ); + data->m_pCaptureEnum->EnumFilters(aguidEnumCats, 2); + } -MidiInWinKS :: ~MidiInWinKS() -{ - WindowsKsData* data = static_cast(apiData_); - try { - if ( data->m_pPin ) - closePort(); - } - catch(...) { - } - - delete data; -} + MidiInWinKS :: ~MidiInWinKS() + { + WindowsKsData* data = static_cast(apiData_); + try { + if ( data->m_pPin ) + closePort(); + } + catch(...) { + } + + delete data; + } -void MidiInWinKS :: openPort( unsigned int portNumber, const std::string portName ) -{ - WindowsKsData* data = static_cast(apiData_); - - if ( portNumber < 0 || portNumber >= data->m_pCaptureEnum->m_Filters.size() ) { - std::stringstream ost; - ost << "MidiInWinKS::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - CKsMidiCapFilter* pFilter = data->m_pCaptureEnum->m_Filters[portNumber]; - data->m_pPin = pFilter->CreateCapturePin(); - - if ( data->m_pPin == NULL ) { - std::stringstream ost; - ost << "MidiInWinKS::openPort: KS error opening port (could not create pin)"; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - data->m_pPin->SetState(KSSTATE_RUN); - - DWORD threadId; - data->m_hInputThread = ::CreateThread(NULL, 0, &midiKsInputThread, &inputData_, 0, &threadId); - if ( data->m_hInputThread == NULL ) { - std::stringstream ost; - ost << "MidiInWinKS::openPort: Could not create input thread : Windows error " << GetLastError() << std::endl;; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - connected_ = true; -} + void MidiInWinKS :: openPort( unsigned int portNumber, const std::string portName ) + { + WindowsKsData* data = static_cast(apiData_); + + if ( portNumber < 0 || portNumber >= data->m_pCaptureEnum->m_Filters.size() ) { + std::stringstream ost; + ost << "MidiInWinKS::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + CKsMidiCapFilter* pFilter = data->m_pCaptureEnum->m_Filters[portNumber]; + data->m_pPin = pFilter->CreateCapturePin(); + + if ( data->m_pPin == NULL ) { + std::stringstream ost; + ost << "MidiInWinKS::openPort: KS error opening port (could not create pin)"; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + data->m_pPin->SetState(KSSTATE_RUN); + + DWORD threadId; + data->m_hInputThread = ::CreateThread(NULL, 0, &midiKsInputThread, &inputData_, 0, &threadId); + if ( data->m_hInputThread == NULL ) { + std::stringstream ost; + ost << "MidiInWinKS::openPort: Could not create input thread : Windows error " << GetLastError() << std::endl;; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + connected_ = true; + } -void MidiInWinKS :: openVirtualPort( const std::string portName ) -{ - // This function cannot be implemented for the Windows KS MIDI API. - errorString_ = "MidiInWinKS::openVirtualPort: cannot be implemented in Windows KS MIDI API!"; - error( RtMidiError::WARNING, errorString_ ); -} + void MidiInWinKS :: openVirtualPort( const std::string portName ) + { + // This function cannot be implemented for the Windows KS MIDI API. + errorString_ = "MidiInWinKS::openVirtualPort: cannot be implemented in Windows KS MIDI API!"; + error( Error::WARNING, errorString_ ); + } -unsigned int MidiInWinKS :: getPortCount() -{ - WindowsKsData* data = static_cast(apiData_); - return (unsigned int)data->m_pCaptureEnum->m_Filters.size(); -} + unsigned int MidiInWinKS :: getPortCount() + { + WindowsKsData* data = static_cast(apiData_); + return (unsigned int)data->m_pCaptureEnum->m_Filters.size(); + } -std::string MidiInWinKS :: getPortName(unsigned int portNumber) -{ - WindowsKsData* data = static_cast(apiData_); + std::string MidiInWinKS :: getPortName(unsigned int portNumber) + { + WindowsKsData* data = static_cast(apiData_); - if (portNumber < 0 || portNumber >= data->m_pCaptureEnum->m_Filters.size()) { - std::stringstream ost; - ost << "MidiInWinKS::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } + if (portNumber < 0 || portNumber >= data->m_pCaptureEnum->m_Filters.size()) { + std::stringstream ost; + ost << "MidiInWinKS::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } - CKsMidiCapFilter* pFilter = data->m_pCaptureEnum->m_Filters[portNumber]; - return pFilter->GetFriendlyName(); -} + CKsMidiCapFilter* pFilter = data->m_pCaptureEnum->m_Filters[portNumber]; + return pFilter->GetFriendlyName(); + } -void MidiInWinKS :: closePort() -{ - WindowsKsData* data = static_cast(apiData_); - connected_ = false; - - if (data->m_hInputThread) { - ::SignalObjectAndWait(data->m_hExitEvent, data->m_hInputThread, INFINITE, FALSE); - ::CloseHandle(data->m_hInputThread); - } - - if (data->m_pPin) { - data->m_pPin->SetState(KSSTATE_PAUSE); - data->m_pPin->SetState(KSSTATE_STOP); - data->m_pPin->ClosePin(); - data->m_pPin = NULL; - } -} + void MidiInWinKS :: closePort() + { + WindowsKsData* data = static_cast(apiData_); + connected_ = false; + + if (data->m_hInputThread) { + ::SignalObjectAndWait(data->m_hExitEvent, data->m_hInputThread, INFINITE, FALSE); + ::CloseHandle(data->m_hInputThread); + } + + if (data->m_pPin) { + data->m_pPin->SetState(KSSTATE_PAUSE); + data->m_pPin->SetState(KSSTATE_STOP); + data->m_pPin->ClosePin(); + data->m_pPin = NULL; + } + } -// *********************************************************************// -// API: WINDOWS Kernel Streaming -// Class Definitions: MidiOutWinKS -// *********************************************************************// + // *********************************************************************// + // API: WINDOWS Kernel Streaming + // Class Definitions: MidiOutWinKS + // *********************************************************************// -MidiOutWinKS :: MidiOutWinKS( const std::string clientName ) : MidiOutApi() -{ - initialize( clientName ); -} + MidiOutWinKS :: MidiOutWinKS( const std::string clientName ) : MidiOutApi() + { + initialize( clientName ); + } -void MidiOutWinKS :: initialize( const std::string& clientName ) -{ - WindowsKsData* data = new WindowsKsData; + void MidiOutWinKS :: initialize( const std::string& clientName ) + { + WindowsKsData* data = new WindowsKsData; - data->m_pPin = NULL; - data->m_pRenderEnum.reset(new CKsEnumFilters ); - GUID const aguidEnumCats[] = - { - { STATIC_KSCATEGORY_AUDIO }, { STATIC_KSCATEGORY_RENDER } - }; - data->m_pRenderEnum->EnumFilters(aguidEnumCats, 2); + data->m_pPin = NULL; + data->m_pRenderEnum.reset(new CKsEnumFilters ); + GUID const aguidEnumCats[] = + { + { STATIC_KSCATEGORY_AUDIO }, { STATIC_KSCATEGORY_RENDER } + }; + data->m_pRenderEnum->EnumFilters(aguidEnumCats, 2); - apiData_ = (void*)data; -} + apiData_ = (void*)data; + } -MidiOutWinKS :: ~MidiOutWinKS() -{ - // Close a connection if it exists. - closePort(); + MidiOutWinKS :: ~MidiOutWinKS() + { + // Close a connection if it exists. + closePort(); - // Cleanup. - WindowsKsData* data = static_cast(apiData_); - delete data; -} + // Cleanup. + WindowsKsData* data = static_cast(apiData_); + delete data; + } -void MidiOutWinKS :: openPort( unsigned int portNumber, const std::string portName ) -{ - WindowsKsData* data = static_cast(apiData_); - - if (portNumber < 0 || portNumber >= data->m_pRenderEnum->m_Filters.size()) { - std::stringstream ost; - ost << "MidiOutWinKS::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - CKsMidiRenFilter* pFilter = data->m_pRenderEnum->m_Filters[portNumber]; - data->m_pPin = pFilter->CreateRenderPin(); - - if (data->m_pPin == NULL) { - std::stringstream ost; - ost << "MidiOutWinKS::openPort: KS error opening port (could not create pin)"; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - data->m_pPin->SetState(KSSTATE_RUN); - connected_ = true; -} + void MidiOutWinKS :: openPort( unsigned int portNumber, const std::string portName ) + { + WindowsKsData* data = static_cast(apiData_); + + if (portNumber < 0 || portNumber >= data->m_pRenderEnum->m_Filters.size()) { + std::stringstream ost; + ost << "MidiOutWinKS::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + CKsMidiRenFilter* pFilter = data->m_pRenderEnum->m_Filters[portNumber]; + data->m_pPin = pFilter->CreateRenderPin(); + + if (data->m_pPin == NULL) { + std::stringstream ost; + ost << "MidiOutWinKS::openPort: KS error opening port (could not create pin)"; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + data->m_pPin->SetState(KSSTATE_RUN); + connected_ = true; + } -void MidiOutWinKS :: openVirtualPort( const std::string portName ) -{ - // This function cannot be implemented for the Windows KS MIDI API. - errorString_ = "MidiOutWinKS::openVirtualPort: cannot be implemented in Windows KS MIDI API!"; - error( RtMidiError::WARNING, errorString_ ); -} + void MidiOutWinKS :: openVirtualPort( const std::string portName ) + { + // This function cannot be implemented for the Windows KS MIDI API. + errorString_ = "MidiOutWinKS::openVirtualPort: cannot be implemented in Windows KS MIDI API!"; + error( Error::WARNING, errorString_ ); + } -unsigned int MidiOutWinKS :: getPortCount() -{ - WindowsKsData* data = static_cast(apiData_); - return (unsigned int)data->m_pRenderEnum->m_Filters.size(); -} + unsigned int MidiOutWinKS :: getPortCount() + { + WindowsKsData* data = static_cast(apiData_); + return (unsigned int)data->m_pRenderEnum->m_Filters.size(); + } -std::string MidiOutWinKS :: getPortName( unsigned int portNumber ) -{ - WindowsKsData* data = static_cast(apiData_); + std::string MidiOutWinKS :: getPortName( unsigned int portNumber ) + { + WindowsKsData* data = static_cast(apiData_); - if ( portNumber < 0 || portNumber >= data->m_pRenderEnum->m_Filters.size() ) { - std::stringstream ost; - ost << "MidiOutWinKS::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } + if ( portNumber < 0 || portNumber >= data->m_pRenderEnum->m_Filters.size() ) { + std::stringstream ost; + ost << "MidiOutWinKS::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } - CKsMidiRenFilter* pFilter = data->m_pRenderEnum->m_Filters[portNumber]; - return pFilter->GetFriendlyName(); -} + CKsMidiRenFilter* pFilter = data->m_pRenderEnum->m_Filters[portNumber]; + return pFilter->GetFriendlyName(); + } -void MidiOutWinKS :: closePort() -{ - WindowsKsData* data = static_cast(apiData_); - connected_ = false; - - if ( data->m_pPin ) { - data->m_pPin->SetState(KSSTATE_PAUSE); - data->m_pPin->SetState(KSSTATE_STOP); - data->m_pPin->ClosePin(); - data->m_pPin = NULL; - } -} + void MidiOutWinKS :: closePort() + { + WindowsKsData* data = static_cast(apiData_); + connected_ = false; + + if ( data->m_pPin ) { + data->m_pPin->SetState(KSSTATE_PAUSE); + data->m_pPin->SetState(KSSTATE_STOP); + data->m_pPin->ClosePin(); + data->m_pPin = NULL; + } + } -void MidiOutWinKS :: sendMessage(std::vector* pMessage) -{ - std::vector const& msg = *pMessage; - WindowsKsData* data = static_cast(apiData_); - size_t iNumMidiBytes = msg.size(); - size_t pos = 0; - - // write header - KSMUSICFORMAT* pKsMusicFormat = reinterpret_cast(&data->m_Buffer[pos]); - pKsMusicFormat->TimeDeltaMs = 0; - pKsMusicFormat->ByteCount = iNumMidiBytes; - pos += sizeof(KSMUSICFORMAT); - - // write MIDI bytes - if ( pos + iNumMidiBytes > data->m_Buffer.size() ) { - std::stringstream ost; - ost << "KsMidiInput::Write: MIDI buffer too small. Required " << pos + iNumMidiBytes << " bytes, only has " << data->m_Buffer.size(); - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - if ( data->m_pPin == NULL ) { - std::stringstream ost; - ost << "MidiOutWinKS::sendMessage: port is not open"; - errorString_ = ost.str(); - error( RtMidiError::WARNING, errorString_ ); - return; - } - - memcpy(&data->m_Buffer[pos], &msg[0], iNumMidiBytes); - pos += iNumMidiBytes; - - KSSTREAM_HEADER packet; - memset(&packet, 0, sizeof packet); - packet.Size = sizeof(packet); - packet.PresentationTime.Time = 0; - packet.PresentationTime.Numerator = 1; - packet.PresentationTime.Denominator = 1; - packet.Data = const_cast(&data->m_Buffer[0]); - packet.DataUsed = ((pos+3)/4)*4; - packet.FrameExtent = data->m_Buffer.size(); - - data->m_pPin->WriteData(&packet, NULL); + void MidiOutWinKS :: sendMessage(std::vector* pMessage) + { + std::vector const& msg = *pMessage; + WindowsKsData* data = static_cast(apiData_); + size_t iNumMidiBytes = msg.size(); + size_t pos = 0; + + // write header + KSMUSICFORMAT* pKsMusicFormat = reinterpret_cast(&data->m_Buffer[pos]); + pKsMusicFormat->TimeDeltaMs = 0; + pKsMusicFormat->ByteCount = iNumMidiBytes; + pos += sizeof(KSMUSICFORMAT); + + // write MIDI bytes + if ( pos + iNumMidiBytes > data->m_Buffer.size() ) { + std::stringstream ost; + ost << "KsMidiInput::Write: MIDI buffer too small. Required " << pos + iNumMidiBytes << " bytes, only has " << data->m_Buffer.size(); + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + if ( data->m_pPin == NULL ) { + std::stringstream ost; + ost << "MidiOutWinKS::sendMessage: port is not open"; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + return; + } + + memcpy(&data->m_Buffer[pos], &msg[0], iNumMidiBytes); + pos += iNumMidiBytes; + + KSSTREAM_HEADER packet; + memset(&packet, 0, sizeof packet); + packet.Size = sizeof(packet); + packet.PresentationTime.Time = 0; + packet.PresentationTime.Numerator = 1; + packet.PresentationTime.Denominator = 1; + packet.Data = const_cast(&data->m_Buffer[0]); + packet.DataUsed = ((pos+3)/4)*4; + packet.FrameExtent = data->m_Buffer.size(); + + data->m_pPin->WriteData(&packet, NULL); + } } - #endif // __WINDOWS_KS__ -//*********************************************************************// -// API: UNIX JACK -// -// Written primarily by Alexander Svetalkin, with updates for delta -// time by Gary Scavone, April 2011. -// -// *********************************************************************// + //*********************************************************************// + // API: UNIX JACK + // + // Written primarily by Alexander Svetalkin, with updates for delta + // time by Gary Scavone, April 2011. + // + // *********************************************************************// #if defined(__UNIX_JACK__) @@ -3486,399 +5105,970 @@ void MidiOutWinKS :: sendMessage(std::vector* pMessage) #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; - }; +namespace rtmidi { + + struct JackMidiData; + static int jackProcessIn( jack_nframes_t nframes, void *arg ); + static int jackProcessOut( jack_nframes_t nframes, void *arg ); + + template + class JackSequencer { + public: + JackSequencer():client(0),name(),data(0) + { + if (locking) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mutex, &attr); + } + } + + JackSequencer(const std::string & n, bool startqueue, JackMidiData * d):client(0),name(n),data(d) + { + if (locking) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL); + pthread_mutex_init(&mutex, &attr); + } + init(client,startqueue); + } + + ~JackSequencer() + { + { + scoped_lock lock (mutex); + if (client) { + jack_deactivate (client); + // the latter doesn't flush the queue + jack_client_close (client); + client = 0; + } + } + if (locking) { + pthread_mutex_destroy(&mutex); + } + } + + bool setName(const std::string & n) { + /* we don't want to rename the client after opening it. */ + if (client) return false; + name = n; + return true; + } + + const char ** getPortList(unsigned long flags) { + init(); + return jack_get_ports(client, + NULL, + "midi", + flags); + } + + jack_port_t * getPort(const char * name) { + init(); + return jack_port_by_name(client,name); + } + + std::string getPortName(jack_port_t * port, int flags) { + init(); + int naming = flags & PortDescriptor::NAMING_MASK; + + std::ostringstream os; + switch (naming) { + case PortDescriptor::SESSION_PATH: + if (flags & PortDescriptor::INCLUDE_API) + os << "JACK:"; +#if __UNIX_JACK_HAS_UUID__ + os << "UUID:" << std::hex << jack_port_uuid(port); +#else + os << jack_port_name(port); +#endif + break; + case PortDescriptor::STORAGE_PATH: + if (flags & PortDescriptor::INCLUDE_API) + os << "JACK:"; + os << jack_port_name(port); + break; + case PortDescriptor::LONG_NAME: + os << jack_port_name(port); + if (flags & PortDescriptor::INCLUDE_API) + os << " (JACK)"; + break; + case PortDescriptor::SHORT_NAME: + default: + os << jack_port_short_name(port); + if (flags & PortDescriptor::INCLUDE_API) + os << " (JACK)"; + break; + } + return os.str(); + } + + int getPortCapabilities(jack_port_t * port) { + if (!port) return 0; + const char * type = jack_port_type(port); + if (strcmp(type,JACK_DEFAULT_MIDI_TYPE)) return 0; + int flags = jack_port_flags(port); + int retval = 0; + /* a JACK input port is capable of handling output to it and vice versa */ + if (flags & JackPortIsInput) + retval |= PortDescriptor::OUTPUT; + if (flags & JackPortIsOutput) + retval |= PortDescriptor::INPUT; + + return retval; + } + +#if 0 + int getNextClient(snd_seq_client_info_t * cinfo ) { + init(); + scoped_lock lock (mutex); + return snd_seq_query_next_client (client, cinfo); + } + int getNextPort(snd_seq_port_info_t * pinfo ) { + init(); + scoped_lock lock (mutex); + return snd_seq_query_next_port (client, pinfo); + } +#endif -//*********************************************************************// -// API: JACK -// Class Definitions: MidiInJack -//*********************************************************************// + jack_port_t * createPort (const std::string & portName, unsigned long portOptions) { + init(); + scoped_lock lock (mutex); + return jack_port_register(client, + portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, + portOptions, + 0); + } + + void deletePort(jack_port_t * port) { + init(); + scoped_lock lock (mutex); + jack_port_unregister( client, port ); + } + + void connectPorts(jack_port_t * from, + jack_port_t * to) + { + init(); + jack_connect( client, + jack_port_name( from ), + jack_port_name( to ) ); + } + + void closePort(jack_port_t * from, + jack_port_t * to) + { + init(); + jack_disconnect( client, + jack_port_name( from ), + jack_port_name( to ) ); + } + +#if 0 + void startQueue(int queue_id) { + init(); + scoped_lock lock(mutex); + snd_seq_start_queue( client, queue_id, NULL ); + snd_seq_drain_output( client ); + } +#endif -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; -} + /*! Use JackSequencer like a C pointer. + \note This function breaks the design to control thread safety + by the selection of the \ref locking parameter to the class. + It should be removed as soon as possible in order ensure the + thread policy that has been intended by creating this class. + */ + operator jack_client_t * () + { + return client; + } + protected: + struct scoped_lock { + pthread_mutex_t * mutex; + scoped_lock(pthread_mutex_t & m): mutex(&m) + { + if (locking) + pthread_mutex_lock(mutex); + } + ~scoped_lock() + { + if (locking) + pthread_mutex_unlock(mutex); + } + }; + pthread_mutex_t mutex; + jack_client_t * client; + std::string name; + JackMidiData * data; + +#if 0 + snd_seq_client_info_t * GetClient(int id) { + init(); + snd_seq_client_info_t * cinfo; + scoped_lock lock(mutex); + snd_seq_get_any_client_info(client,id,cinfo); + return cinfo; + } +#endif -MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) -{ - initialize( clientName ); -} + void init() + { + init (client,false); + } + + void init(jack_client_t * &c, bool isoutput) + { + if (c) return; + { + scoped_lock lock(mutex); + if (( client = jack_client_open( name.c_str(), + JackNoStartServer, + NULL )) == 0) { + throw Error("JackSequencer::init: Could not connect to JACK server. Is it runnig?", + Error::WARNING); + return; + } + + if (isoutput && data) { + jack_set_process_callback( client, jackProcessOut, data ); + } else if (data) + jack_set_process_callback( client, jackProcessIn, data ); + jack_activate( client ); + } + } + }; + typedef JackSequencer<1> LockingJackSequencer; + typedef JackSequencer<0> NonLockingJackSequencer; + + struct JackPortDescriptor:public PortDescriptor + { + MidiApi * api; + static LockingJackSequencer seq; + JackPortDescriptor(const std::string & name):api(0),clientName(name) + { + port = 0; + } + JackPortDescriptor(const char * portname, const std::string & name):api(0),clientName(name) + { + port = seq.getPort(portname); + seq.setName(name); + } + JackPortDescriptor(jack_port_t * other, + const std::string & name):api(0), + clientName(name) + { + port = other; + seq.setName(name); + } + JackPortDescriptor(JackPortDescriptor & other, + const std::string & name):api(0), + clientName(name) + { + port = other.port; + seq.setName(name); + } + ~JackPortDescriptor() + { + } + MidiInApi * getInputApi(unsigned int queueSizeLimit = 100) { + if (getCapabilities() & INPUT) + return new MidiInJack(clientName,queueSizeLimit); + else + return 0; + } + MidiOutApi * getOutputApi() { + if (getCapabilities() & OUTPUT) + return new MidiOutJack(clientName); + else + return 0; + } + + + std::string getName(int flags = SHORT_NAME | UNIQUE_NAME) { + return seq.getPortName(port,flags); + } + + const std::string & getClientName() { + return clientName; + } + int getCapabilities() { + return seq.getPortCapabilities(port); + } + static PortList getPortList(int capabilities, const std::string & clientName); + + operator jack_port_t * () const { return port; } + + protected: + std::string clientName; + jack_port_t * port; + + friend struct JackMidiData; + }; + + LockingJackSequencer JackPortDescriptor::seq; + + + + PortList JackPortDescriptor :: getPortList(int capabilities, const std::string & clientName) + { + PortList list; + unsigned long flags = 0; + + if (capabilities & INPUT) { + flags |= JackPortIsOutput; + } + if (capabilities & OUTPUT) { + flags |= JackPortIsInput; + } + const char ** ports = seq.getPortList(flags); + if (!ports) return list; + for (const char ** port = ports; *port; port++) { + list.push_back(new JackPortDescriptor(*port, clientName)); + } + jack_free(ports); + return list; + } -void MidiInJack :: initialize( const std::string& clientName ) -{ - JackMidiData *data = new JackMidiData; - apiData_ = (void *) data; + /*! A structure to hold variables related to the JACK API + implementation. + + \note After all sequencer handling is covered by the \ref + JackSequencer class, we should make seq to be a pointer in order + to allow a common client implementation. + */ + + struct JackMidiData:public JackPortDescriptor { + jack_port_t * local; + jack_ringbuffer_t *buffSize; + jack_ringbuffer_t *buffMessage; + jack_time_t lastTime; + MidiInApi :: MidiInData *rtMidiIn; + NonLockingJackSequencer seq; + + /* + JackMidiData():seq() + { + init(); + } + */ + JackMidiData(const std::string &clientName, + MidiInApi :: MidiInData &inputData_):JackPortDescriptor(clientName), + local(0), + buffSize(0), + buffMessage(0), + lastTime(0), + rtMidiIn(&inputData_), + seq(clientName,false,this) + { + } + + /** + * Create output midi data. + * + * \param clientName + * + * \return + */ + JackMidiData(const std::string &clientName):JackPortDescriptor(clientName), + local(0), + buffSize(jack_ringbuffer_create( JACK_RINGBUFFER_SIZE )), + buffMessage(jack_ringbuffer_create( JACK_RINGBUFFER_SIZE )), + lastTime(0), + rtMidiIn(), + seq(clientName,true,this) + {} + + + ~JackMidiData() + { + if (local) + deletePort(); + if (buffSize) + jack_ringbuffer_free( buffSize ); + if (buffMessage) + jack_ringbuffer_free( buffMessage ); + } + + + + + void setRemote(jack_port_t * remote) { + port = remote; + } + + void connectPorts(jack_port_t * from, + jack_port_t * to) { + seq.connectPorts(from, to); + } + + int openPort(unsigned long jackCapabilities, + const std::string & portName) { + local = seq.createPort(portName, jackCapabilities); + if (!local) { + api->error( Error::DRIVER_ERROR, + "MidiInJack::openPort: JACK error opening port subscription." ); + return -99; + } + return 0; + } + + void deletePort() { + seq.deletePort(local); + local = 0; + } + + void closePort(bool output_is_remote) { + if (output_is_remote) { + seq.closePort( local, port ); + } else { + seq.closePort( port, local ); + } + port = 0; + } + + operator jack_port_t * () const { return port; } + }; + + + + //*********************************************************************// + // API: JACK + // Class Definitions: MidiInJack + //*********************************************************************// + + static int jackProcessIn( jack_nframes_t nframes, void *arg ) + { + JackMidiData *jData = (JackMidiData *) arg; + MidiInApi :: MidiInData *rtData = jData->rtMidiIn; + jack_midi_event_t event; + jack_time_t time; + + // Is port created? + if ( jData->local == NULL ) return 0; + void *buff = jack_port_get_buffer( jData->local, 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 ) { + MidiCallback callback = (MidiCallback) 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; + } - data->rtMidiIn = &inputData_; - data->port = NULL; - data->client = NULL; - this->clientName = clientName; + MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) + { + initialize( clientName ); + } - connect(); -} + void MidiInJack :: initialize( const std::string& clientName ) + { + JackMidiData *data = new JackMidiData(clientName,inputData_); + apiData_ = (void *) data; + this->clientName = clientName; + } -void MidiInJack :: connect() -{ - JackMidiData *data = static_cast (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 ); -} +#if 0 + void MidiInJack :: connect() + { + abort(); + // this should be unnecessary + JackMidiData *data = static_cast (apiData_); + if ( data->local ) + return; + + // Initialize JACK client + if (( data->local = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { + errorString_ = "MidiInJack::initialize: JACK server not running?"; + error( Error::WARNING, errorString_ ); + return; + } + + jack_set_process_callback( data->client, jackProcessIn, data ); + jack_activate( data->client ); + } +#endif -MidiInJack :: ~MidiInJack() -{ - JackMidiData *data = static_cast (apiData_); - closePort(); + MidiInJack :: ~MidiInJack() + { + JackMidiData *data = static_cast (apiData_); + closePort(); - if ( data->client ) - jack_client_close( data->client ); - delete data; -} +#if 0 + if ( data->client ) + jack_client_close( data->client ); +#endif + delete data; + } -void MidiInJack :: openPort( unsigned int portNumber, const std::string portName ) -{ - JackMidiData *data = static_cast (apiData_); + void MidiInJack :: openPort( unsigned int portNumber, const std::string & portName ) + { + JackMidiData *data = static_cast (apiData_); - connect(); + // connect(); - // Creating new port - if ( data->port == NULL) - data->port = jack_port_register( data->client, portName.c_str(), - JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); + // Creating new port + if ( data->local == NULL) + data->local = jack_port_register( data->seq, 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; - } + if ( data->local == NULL) { + errorString_ = "MidiInJack::openPort: JACK error creating port"; + error( Error::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 ) ); -} + // Connecting to the output + std::string name = getPortName( portNumber ); + jack_connect( data->seq, name.c_str(), jack_port_name( data->local ) ); + } -void MidiInJack :: openVirtualPort( const std::string portName ) -{ - JackMidiData *data = static_cast (apiData_); + void MidiInJack :: openVirtualPort( const std::string portName ) + { + JackMidiData *data = static_cast (apiData_); - connect(); - if ( data->port == NULL ) - data->port = jack_port_register( data->client, portName.c_str(), - JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); + // connect(); + if ( data->local == NULL ) + data->local = jack_port_register( data->seq, 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_ ); - } -} + if ( data->local == NULL ) { + errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; + error( Error::DRIVER_ERROR, errorString_ ); + } + } -unsigned int MidiInJack :: getPortCount() -{ - int count = 0; - JackMidiData *data = static_cast (apiData_); - connect(); - if ( !data->client ) - return 0; + void MidiInJack :: openPort( const PortDescriptor & p, + const std::string & portName ) + { + JackMidiData *data = static_cast (apiData_); + const JackPortDescriptor * port = dynamic_cast(&p); + + if ( !data ) { + errorString_ = "MidiInAlsa::openPort: Internal error: data has not been allocated!"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } +#if 0 + if ( connected_ ) { + errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } +#endif + if (!port) { + errorString_ = "MidiInAlsa::openPort: an invalid (i.e. non-ALSA) port descriptor has been passed to openPort!"; + error( Error::WARNING, errorString_ ); + return; + } + + if (!data->local) + data->openPort (JackPortIsInput, + portName); + data->setRemote(*port); + data->connectPorts(*port,data->local); + +#if 0 + + 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( Error::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 ) ); +#endif + } - // List of available ports - const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); + Pointer MidiInJack :: getDescriptor(bool local) + { + JackMidiData *data = static_cast (apiData_); + if (local) { + if (data && data->local) { + return new JackPortDescriptor(data->local,data->getClientName()); + } + } else { + if (data && *data) { + return new JackPortDescriptor(*data,data->getClientName()); + } + } + return NULL; + } - if ( ports == NULL ) return 0; - while ( ports[count] != NULL ) - count++; + PortList MidiInJack :: getPortList(int capabilities) + { + JackMidiData *data = static_cast (apiData_); + return JackPortDescriptor::getPortList(capabilities | PortDescriptor::INPUT, + data->getClientName()); + } - free( ports ); + unsigned int MidiInJack :: getPortCount() + { + int count = 0; + // connect(); + JackMidiData *data = static_cast (apiData_); + if ( !(data->seq) ) + return 0; - return count; -} + // List of available ports + const char **ports = jack_get_ports( data->seq, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); -std::string MidiInJack :: getPortName( unsigned int portNumber ) -{ - JackMidiData *data = static_cast (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; -} + if ( ports == NULL ) return 0; + while ( ports[count] != NULL ) + count++; -void MidiInJack :: closePort() -{ - JackMidiData *data = static_cast (apiData_); + free( ports ); - if ( data->port == NULL ) return; - jack_port_unregister( data->client, data->port ); - data->port = NULL; -} + return count; + } -//*********************************************************************// -// API: JACK -// Class Definitions: MidiOutJack -//*********************************************************************// + std::string MidiInJack :: getPortName( unsigned int portNumber ) + { + JackMidiData *data = static_cast (apiData_); + std::string retStr(""); + + // connect(); + + // List of available ports + const char **ports = jack_get_ports( data->seq, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); + + // Check port validity + if ( ports == NULL ) { + errorString_ = "MidiInJack::getPortName: no ports available!"; + error( Error::WARNING, errorString_ ); + return retStr; + } + + if ( ports[portNumber] == NULL ) { + std::ostringstream ost; + ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + } + else retStr.assign( ports[portNumber] ); + + free( ports ); + return retStr; + } + + void MidiInJack :: closePort() + { + JackMidiData *data = static_cast (apiData_); -// Jack process callback -static int jackProcessOut( jack_nframes_t nframes, void *arg ) -{ - JackMidiData *data = (JackMidiData *) arg; - jack_midi_data_t *midiData; - int space; + if ( data->local == NULL ) return; + jack_port_unregister( data->seq, data->local ); + data->local = NULL; + } - // Is port created? - if ( data->port == NULL ) return 0; + //*********************************************************************// + // API: JACK + // Class Definitions: MidiOutJack + //*********************************************************************// - void *buff = jack_port_get_buffer( data->port, nframes ); - jack_midi_clear_buffer( buff ); + // Jack process callback + static int jackProcessOut( jack_nframes_t nframes, void *arg ) + { + JackMidiData *data = (JackMidiData *) arg; + jack_midi_data_t *midiData; + int space; - 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 ); + // Is port created? + if ( data->local == NULL ) return 0; - jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); - } + void *buff = jack_port_get_buffer( data->local, nframes ); + jack_midi_clear_buffer( buff ); - return 0; -} + 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 ); -MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi() -{ - initialize( clientName ); -} + jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); + } -void MidiOutJack :: initialize( const std::string& clientName ) -{ - JackMidiData *data = new JackMidiData; - apiData_ = (void *) data; + return 0; + } - data->port = NULL; - data->client = NULL; - this->clientName = clientName; + MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi() + { + initialize( clientName ); + } - connect(); -} + void MidiOutJack :: initialize( const std::string& clientName ) + { + JackMidiData *data = new JackMidiData(clientName); + apiData_ = (void *) data; + this->clientName = clientName; -void MidiOutJack :: connect() -{ - JackMidiData *data = static_cast (apiData_); - if ( data->client ) - return; - - // 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 ); - data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); - data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); - jack_activate( data->client ); -} + // connect(); + } + + void MidiOutJack :: connect() + { + abort(); +#if 0 + JackMidiData *data = static_cast (apiData_); + if ( data->seq ) + return; + + // Initialize JACK client + if (( data->seq = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { + errorString_ = "MidiOutJack::initialize: JACK server not running?"; + error( Error::WARNING, errorString_ ); + return; + } + + jack_set_process_callback( data->client, jackProcessOut, data ); + data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); + data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); + jack_activate( data->client ); +#endif + } -MidiOutJack :: ~MidiOutJack() -{ - JackMidiData *data = static_cast (apiData_); - closePort(); + MidiOutJack :: ~MidiOutJack() + { + JackMidiData *data = static_cast (apiData_); + closePort(); + +#if 0 + if ( data->seq ) { + // Cleanup + jack_client_close( data->client ); + jack_ringbuffer_free( data->buffSize ); + jack_ringbuffer_free( data->buffMessage ); + } +#endif - if ( data->client ) { - // Cleanup - jack_client_close( data->client ); - jack_ringbuffer_free( data->buffSize ); - jack_ringbuffer_free( data->buffMessage ); - } + delete data; + } - delete data; -} + void MidiOutJack :: openPort( unsigned int portNumber, const std::string & portName ) + { + JackMidiData *data = static_cast (apiData_); -void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName ) -{ - JackMidiData *data = static_cast (apiData_); + // connect(); - connect(); + // Creating new port + if ( data->local == NULL ) + data->local = jack_port_register( data->seq, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); - // 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->local == NULL ) { + errorString_ = "MidiOutJack::openPort: JACK error creating port"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } - 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->seq, jack_port_name( data->local ), name.c_str() ); + } - // 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 (apiData_); -void MidiOutJack :: openVirtualPort( const std::string portName ) -{ - JackMidiData *data = static_cast (apiData_); + // connect(); + if ( data->local == NULL ) + data->local = jack_port_register( data->seq, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); - connect(); - if ( data->port == NULL ) - data->port = jack_port_register( data->client, portName.c_str(), - JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); + if ( data->local == NULL ) { + errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; + error( Error::DRIVER_ERROR, errorString_ ); + } + } - if ( data->port == NULL ) { - errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; - error( RtMidiError::DRIVER_ERROR, errorString_ ); - } -} + void MidiOutJack :: openPort( const PortDescriptor & p, + const std::string & portName ) + { + JackMidiData *data = static_cast (apiData_); + const JackPortDescriptor * port = dynamic_cast(&p); + + if ( !data ) { + errorString_ = "MidiInAlsa::openPort: Internal error: data has not been allocated!"; + error( Error::DRIVER_ERROR, errorString_ ); + return; + } +#if 0 + if ( connected_ ) { + errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; + error( Error::WARNING, errorString_ ); + return; + } +#endif + if (!port) { + errorString_ = "MidiInAlsa::openPort: an invalid (i.e. non-ALSA) port descriptor has been passed to openPort!"; + error( Error::WARNING, errorString_ ); + return; + } + + if (!data->local) + data->openPort (JackPortIsOutput, + portName); + data->setRemote(*port); + data->connectPorts(data->local,*port); + +#if 0 + + 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_ = "MidiOutJack::openPort: JACK error creating port"; + error( Error::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 ) ); +#endif + } -unsigned int MidiOutJack :: getPortCount() -{ - int count = 0; - JackMidiData *data = static_cast (apiData_); - connect(); - if ( !data->client ) - return 0; + Pointer MidiOutJack :: getDescriptor(bool local) + { + JackMidiData *data = static_cast (apiData_); + if (local) { + if (data && data->local) { + return new JackPortDescriptor(data->local,data->getClientName()); + } + } else { + if (data && *data) { + return new JackPortDescriptor(*data,data->getClientName()); + } + } + return NULL; + } - // List of available ports - const char **ports = jack_get_ports( data->client, NULL, - JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); + PortList MidiOutJack :: getPortList(int capabilities) + { + JackMidiData *data = static_cast (apiData_); + return JackPortDescriptor::getPortList(capabilities | PortDescriptor::OUTPUT, + data->getClientName()); + } - if ( ports == NULL ) return 0; - while ( ports[count] != NULL ) - count++; + unsigned int MidiOutJack :: getPortCount() + { + int count = 0; + JackMidiData *data = static_cast (apiData_); + // connect(); + if ( !data->seq ) + return 0; - free( ports ); + // List of available ports + const char **ports = jack_get_ports( data->seq, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); - return count; -} + if ( ports == NULL ) return 0; + while ( ports[count] != NULL ) + count++; -std::string MidiOutJack :: getPortName( unsigned int portNumber ) -{ - JackMidiData *data = static_cast (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; -} + free( ports ); -void MidiOutJack :: closePort() -{ - JackMidiData *data = static_cast (apiData_); + return count; + } - if ( data->port == NULL ) return; - jack_port_unregister( data->client, data->port ); - data->port = NULL; -} + std::string MidiOutJack :: getPortName( unsigned int portNumber ) + { + JackMidiData *data = static_cast (apiData_); + std::string retStr(""); + + // connect(); + + // List of available ports + const char **ports = jack_get_ports( data->seq, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); + + // Check port validity + if ( ports == NULL) { + errorString_ = "MidiOutJack::getPortName: no ports available!"; + error( Error::WARNING, errorString_ ); + return retStr; + } + + if ( ports[portNumber] == NULL) { + std::ostringstream ost; + ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( Error::WARNING, errorString_ ); + } + else retStr.assign( ports[portNumber] ); + + free( ports ); + return retStr; + } -void MidiOutJack :: sendMessage( std::vector *message ) -{ - int nBytes = message->size(); - JackMidiData *data = static_cast (apiData_); + void MidiOutJack :: closePort() + { + JackMidiData *data = static_cast (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 ) ); -} + if ( data->local == NULL ) return; + jack_port_unregister( data->seq, data->local ); + data->local = NULL; + } + + void MidiOutJack :: sendMessage( std::vector &message ) + { + int nBytes = message.size(); + JackMidiData *data = static_cast (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__ + diff --git a/RtMidi.h b/RtMidi.h index 99dc38c1..3bace5ab 100644 --- a/RtMidi.h +++ b/RtMidi.h @@ -1,799 +1,1283 @@ -/**********************************************************************/ +/********************* -*- C++ -*- ****************************************/ /*! \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-2014 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. + \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-2014 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.0" +#define RTMIDI_VERSION "3.0.0alpha" + +#ifdef __GNUC__ +#define RTMIDI_DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) +#define RTMIDI_DEPRECATED(func) __declspec(deprecated) func +#else +#pragma message("WARNING: You need to implement DEPRECATED for this compiler") +#define RTMIDI_DEPRECATED(func) func +#endif #include #include #include #include +#include +#include +#include + +namespace rtmidi { + + //! MIDI API specifier arguments. + enum ApiType { + 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. */ + WINDOWS_KS, /*!< The Microsoft Kernel Streaming MIDI API. */ + RTMIDI_DUMMY /*!< A compilable but non-functional API. */ + }; + + //! User callback function type definition. + /*! + \param timeStamp timestamp indicating when the event has been received + \param message a pointer to the binary MIDI message + \param userData a pointer that can be set using setUserdata + \sa MidiIn + \sa MidiInApi + */ + typedef void (*MidiCallback)( double timeStamp, std::vector *message, void *userData); + + /************************************************************************/ + /*! \class Error + \brief Exception handling class for RtMidi. + + The Error class is quite simple but it does allow errors to be + "caught" by Error::Type. See the RtMidi documentation to know + which methods can throw an Error. + */ + /************************************************************************/ + + class Error : public std::exception + { + public: + //! Defined Error 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. + Error( const std::string& message, Type type = Error::UNSPECIFIED ) throw() : message_(message), type_(type) {} + + //! The destructor. + virtual ~Error( 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 (*ErrorCallback)( Error::Type type, const std::string &errorText ); + + + +#if __cplusplus < 201103L + class PortDescriptor; + + template + class Pointer { + public: + typedef T datatype; + protected: + struct countPointer { + int count; + datatype * descriptor; + }; + public: + Pointer():ptr(0) {} + Pointer(datatype * p):ptr(new countPointer) { + ptr->count = 1; + ptr->descriptor = p; + } + Pointer(const Pointer & other): + ptr(other.ptr) { + if (ptr) + ptr->count++; + } + + ~Pointer() { + if (!ptr) return; + if (!ptr->descriptor) { + delete ptr; + return; + } + if (!(--ptr->count)) { + delete ptr->descriptor; + delete ptr; + } + } + + datatype * operator -> () { + if (!ptr) return 0; + // this should throw an exception + + return ptr->descriptor; + } + + datatype & operator * () { + if (!ptr || !ptr->descriptor) { + throw std::invalid_argument("rtmidi::Pointer: trying to dereference a NULL pointer."); + } + else return (*ptr->descriptor); + } + + bool operator ! () { + return (!ptr || !ptr->descriptor); + } + + Pointer & operator = (const Pointer & other) { + if (ptr) { + if (!(--ptr->count)) { + delete ptr->descriptor; + delete ptr; + } + } + ptr = other.ptr; + ptr->count++; + return *this; + } + protected: + countPointer * ptr; + }; +#else + template + typedef std::shared_ptr Pointer; +#endif -/************************************************************************/ -/*! \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 ); - -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. */ - WINDOWS_KS, /*!< The Microsoft Kernel Streaming 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 &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 ) = 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 *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 JACK, ALSA (Linux) and JACK, - CORE (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 *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 ); - - 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 JACK, ALSA (Linux) and JACK, - CORE (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 *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 ); - - 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 ); - - //! 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_; -}; - -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 *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 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 *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 *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); } -inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback ) { rtapi_->setErrorCallback(errorCallback); } - -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 *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); } -inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback ) { rtapi_->setErrorCallback(errorCallback); } - -// **************************************************************** // -// -// MidiInApi and MidiOutApi subclass prototypes. -// -// **************************************************************** // + class MidiApi; + class MidiInApi; + class MidiOutApi; + + struct PortDescriptor { + //! Flags for formatting a string description of the port. + /*! These flags just mark the requirements that the string + should fulfil. An API may return the same string for + different requirements e.g. the same short and long + name. */ + enum NamingType { + SHORT_NAME =0, /*!< A short human readable name + depending on the API + e.g. “Ensoniq AudioPCI” */ + LONG_NAME, /*!< A complete human readable + name depending on the API + e.g. "Ensoniq AudioPCI: ES1371" */ + SESSION_PATH, /*!< A unique description that can + be used to identify the port + during runtime. It may be a + cryptic string. */ + STORAGE_PATH, /*!< A unique description that is + optimised for storage in + configuration files. This is a + more textual representation that + is more robust to small changes in + the surrounding environment than + \ref SESSION_PATH */ + NAMING_MASK = 0x0F, /*!< part of the flags + that is concerned with + naming. + */ + UNIQUE_NAME = 0x10, /*!< Make all names uniqe. This + is usually done by adding + numbers to the end of the + string */ + INCLUDE_API = 0x20 /*!< Add a string describing the + API at the beginning of the + string. */ + }; + + //! Flags describing the capabilities of a given port. + enum PortCapabilities { + INPUT = 1, /*!< Ports that can be read from. */ + OUTPUT = 2, /*!< Ports that can be written to. */ + INOUTPUT = 3, /*!< Ports that allow reading and writing (INPUT | OUTPUT) */ + UNLIMITED = 0x10 /*!< Some APIs can filter out certain ports which they consider + not to be useful. This flags supresses this behaviour and + selects all ports that are useable. */ + }; + + //! Default constructor. + /*! + * Derived classes should have a constructor. + */ + PortDescriptor() {}; + + //! A virtual destructor + /*! As we might have to destruct the object from the application code + * each port id must have a virtual destructor. + */ + virtual ~PortDescriptor() {}; + + //! Get the MIDI input api for the current port. + /*! This is the only information RtMidi needs to know: Which + * API should handle this object. This can be used to get + * an API which can send data to the given port. + * + * \param queueSizeLimit The limit of the midi queue. This parameter is handled by + * the constructor of the backend API. + * + * \return API that can use this object to connect to an input port or 0 + * if no input API can be created. + */ + virtual MidiInApi * getInputApi(unsigned int queueSizeLimit = 100) = 0; + + //! Get the MIDI output api for the current port. + /*! This is the only information RtMidi needs to know: Which + * API should handle this object. This can be used to get + * an API which can receive data from the given port. + * + * \return API that can use this object to connect to an output port. + */ + virtual MidiOutApi * getOutputApi() = 0; + + //! Return the port name + /*! + * \param flags A description of the requirements of the returned name. + * \return A name that is formatted according to \ref flags. + * \sa NamingTypes + */ + virtual std::string getName(int flags = SHORT_NAME | UNIQUE_NAME) = 0; + + //! Get capabilities + /*! \return a capabilities flag describing the capabilities of the port. + * \sa PortCapabilities + */ + virtual int getCapabilities() = 0; + }; + + //! A list of port descriptors. + /*! Port descriptors are stored as shared pointers. This avoids + unnecessary duplication of the data structure and handles automatic + deletion if all references have been removed. */ + typedef std::list > PortList; + + + + // **************************************************************** // + // + // 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. MidiIn and MidiOut will + // create instances of a MidiInApi or MidiOutApi subclass. + // + // **************************************************************** // + + class MidiApi + { + public: + + MidiApi(); + virtual ~MidiApi(); + + //! Pure virtal function to create a virtual port, with optional name. + /*! + This function creates a virtual MIDI 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 applicaction port that is + used to connect to portId can be specified. + */ + virtual void openVirtualPort( const std::string portName = std::string( "RtMidi virtual port" ) ) = 0; + + //! Pure virtual function to open a MIDI 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 applicaction port + that will be generated to connect to portId can be + specified. + */ + virtual void openPort( unsigned int portNumber = 0, const std::string & portName = std::string( "RtMidi" ) ) = 0; + + //! Pure virtual function to open a MIDI connection given by a port descriptor. + /*! + \param port A port descriptor of the port must be specified. + \param portName An optional name for the applicaction port that is used to connect to portId can be specified. + */ + virtual void openPort( const PortDescriptor & port, const std::string & portName = std::string( "RtMidi" ) ) = 0; + + //! Open a MIDI connection given by a port descriptor pointer. + /*! + \param port A pointer to a port descriptor of the port must be specified. + \param portName An optional name for the applicaction port that is used to connect to portId can be specified. + \sa openPort(const PortDescriptor &,const std::string &); + */ + void openPort( Pointer p, const std::string & portName = std::string( "RtMidi" ) ) { + if (!p) { + errorString_ = "MidiApi::openPort: passed NULL pointer"; + error( Error::INVALID_PARAMETER, errorString_ ); + return; + } + openPort(*p, portName); + } + + //! Pure virtual function to return a port descriptor if the port is open + /*! This function returns a port descriptor that can be used to open another port + either to the connected port or – if the backend supports it – the connecting port. + \param local The parameter local defines whether the function returns a descriptor to + the virtual port (true) or the remote port (false). The function returns 0 if the + port cannot be determined (e.g. if the port is not connected or the backend dosen't support it). + */ + virtual Pointer getDescriptor(bool local=false) = 0; + + //! Pure virtual function to return a list of all available ports of the current API. + /*! + \param capabilities an optional parameter that describes which + capabilities the device typu + + \return This function returns a list of port descriptors. + + \note An input API may but need not necessarily report + output devices which cannot be used as input if + \ref 0 is passed as \ref capabilities parameter. + \sa PortDescriptor::PortCapabilitiers + */ + virtual PortList getPortList(int capabilities = 0) = 0; + + //! Pure virtual to return the number of available MIDI ports of the current API. + /*! + \return This function returns the number of MIDI ports of + the selected API. + + \note Only ports are counted that can be used with the + current API so an input API does ignore all output devices + and vice versa. + + \sa getPortName + */ + virtual unsigned int getPortCount() = 0; + + //! Pure virtual function to return a string identifier for the specified MIDI port number. + /*! + \param portNumber Number of the device to be referred to. + \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. + + \note Only ports are counted that can be used with the + current API so an input API does ignore all output devices + and vice versa. + + \sa getPortCount() + */ + virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; + + //! Pure virtual function to close an open MIDI connection (if one exists). + virtual void closePort( void ) = 0; + + // ! A basic error reporting function for RtMidi classes. + // static void error( Error::Type type, std::string &errorString ); + + //! Pure virtual function to return whether a port is open or not. + /*! \retval true if a port is open and + \retval false if the port is not open (e.g. not opend or closed). + */ + bool isPortOpen() const { return connected_; } + + //! Pure virtual function to 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( ErrorCallback errorCallback = NULL ); + + + //! Returns the MIDI API specifier for the current instance of RtMidiIn. + virtual ApiType getCurrentApi( void ) throw() = 0; + + //! A basic error reporting function for RtMidi classes. + void error( Error::Type type, std::string errorString ); + + protected: + virtual void initialize( const std::string& clientName ) = 0; + + void *apiData_; + bool connected_; + std::string errorString_; + ErrorCallback errorCallback_; + }; + + class MidiInApi : public MidiApi + { + public: + + MidiInApi( unsigned int queueSizeLimit ); + virtual ~MidiInApi( void ); + void setCallback( MidiCallback callback, void *userData = 0 ); + void cancelCallback( void ); + virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); + RTMIDI_DEPRECATED(double getMessage( std::vector *message )) + { + if (!message) { + errorString_ = "MidiInApi::getMessage: passed NULL pointer"; + error( Error::WARNING, errorString_ ); + } + return getMessage(*message); + } + double getMessage( std::vector &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 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 MidiInData { + MidiQueue queue; + MidiMessage message; + unsigned char ignoreFlags; + bool doInput; + bool firstMessage; + void *apiData; + bool usingCallback; + MidiCallback userCallback; + void *userData; + bool continueSysex; + + // Default constructor. + MidiInData() + : ignoreFlags(7), doInput(false), firstMessage(true), + apiData(0), usingCallback(false), userCallback(0), userData(0), + continueSysex(false) {} + }; + + protected: + MidiInData inputData_; + }; + + class MidiOutApi : public MidiApi + { + public: + + MidiOutApi( void ); + virtual ~MidiOutApi( void ); + RTMIDI_DEPRECATED(void sendMessage( std::vector *message )) + { + if (!message) { + errorString_ = "MidiOutApi::sendMessage: no data in message argument!"; + error( Error::WARNING, errorString_ ); + } + sendMessage(*message); + } + virtual void sendMessage( std::vector &message ) = 0; + }; + + + /*! \class Midi + \brief A global class that implements basic backend API handling. + + This class enhances \ref MidiApi by some functionality to handle + backend API objects. It serves as base class for the public RtMidi + API. + + by Gary P. Scavone, 2003-2014. + */ + /**********************************************************************/ + class Midi { + public: + typedef rtmidi::ApiType Api; + //! defined for compatibility + enum Api2 { + UNSPECIFIED = rtmidi::UNSPECIFIED, + MACOSX_CORE = rtmidi::MACOSX_CORE, + LINUX_ALSA = rtmidi::LINUX_ALSA, + UNIX_JACK = rtmidi::UNIX_JACK, + WINDOWS_MM = rtmidi::WINDOWS_MM, + WINDOWS_KS = rtmidi::WINDOWS_KS, + RTMIDI_DUMMY = rtmidi::RTMIDI_DUMMY + }; + + + //! 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 &apis ) throw(); + + //! Returns the MIDI API specifier for the current instance of RtMidiIn. + ApiType getCurrentApi( void ) throw() + { + if (rtapi_) return rtapi_->getCurrentApi(); + else return rtmidi::UNSPECIFIED; + } + + //! Function to create a virtual port, with optional name. + /*! + This function creates a virtual MIDI 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 applicaction port that is + used to connect to portId can be specified. + */ + void openVirtualPort( const std::string portName = std::string( "RtMidi virtual port" ) ) + { + if (rtapi_) rtapi_->openVirtualPort(portName); + } + + //! Pure virtual function to open a MIDI 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 applicaction port + that will be generated to connect to portId can be + specified. + */ + void openPort( unsigned int portNumber = 0, + const std::string portName = std::string( "RtMidi" ) ) + { + if (rtapi_) rtapi_->openPort(portNumber,portName); + } + + //! Pure virtual function to open a MIDI connection given by a port descriptor. + /*! + \param port A port descriptor of the port must be specified. + \param portName An optional name for the applicaction port that is used to connect to portId can be specified. + */ + void openPort( const PortDescriptor & port, + const std::string & portName = std::string( "RtMidi" ) ) + { + if (rtapi_) rtapi_->openPort(port,portName); + } + + //! Open a MIDI connection given by a port descriptor pointer. + /*! + \param port A pointer to a port descriptor of the port must be specified. + \param portName An optional name for the applicaction port that is used to connect to portId can be specified. + \sa openPort(const PortDescriptor &,const std::string &); + */ + void openPort( Pointer p, + const std::string & portName = std::string( "RtMidi" ) ) { + if (!p) { + error( Error::INVALID_PARAMETER, "MidiApi::openPort: passed NULL pointer" ); + return; + } + openPort(*p, portName); + } + + //! Pure virtual function to return a port descirptor if the port is open + Pointer getDescriptor(bool local=false) + { + if (rtapi_) return rtapi_->getDescriptor(local); + return 0; + } + + //! Return a list of all available ports of the current API. + /*! + \param capabilities an optional parameter that + describes which capabilities the returned devices + must support. The returned devices may have + additional capabilities to those which have been + requested, but not less. + + \return This function returns a list of port descriptors. + + \note Each API will request additonal + capabilites. An output API will set always add \ref + PortDescriptor::OUTPUT to the mask while an input + device will always add \ref PortDescriptor::OUTPUT. + + \note An input API may but need not necessarily + report output devices which cannot be used as input + if \ref PortDescriptor::OUTPUT is passed as \ref + capabilities parameter. + */ + PortList getPortList(int capabilities = 0) + { + if (rtapi_) return rtapi_->getPortList(capabilities); + return PortList(); + } + + //! Pure virtual to return the number of available MIDI ports of the current API. + /*! + \return This function returns the number of MIDI ports of + the selected API. + + \note Only ports are counted that can be used with the + current API so an input API does ignore all output devices + and vice versa. + + \sa getPortName + */ + unsigned int getPortCount() + { + if (rtapi_) return rtapi_->getPortCount(); + return 0; + } + + //! Pure virtual function to return a string identifier for the specified MIDI port number. + /*! + \param portNumber Number of the device to be referred to. + \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. + + \note Only ports are counted that can be used with the + current API so an input API does ignore all output devices + and vice versa. + + \sa getPortCount() + */ + std::string getPortName( unsigned int portNumber = 0 ) + { + if (rtapi_) return rtapi_->getPortName(portNumber); + return ""; + } + + //! Close an open MIDI connection (if one exists). + void closePort( void ) + { + if (rtapi_) rtapi_->closePort(); + } + + // ! A basic error reporting function for RtMidi classes. + // static void error( Error::Type type, std::string &errorString ); + + //! Pure virtual function to return whether a port is open or not. + /*! \retval true if a port is open and + \retval false if the port is not open (e.g. not opend or closed). + */ + bool isPortOpen( void ) const + { + if (rtapi_) return rtapi_->isPortOpen(); + return false; + } + + //! Pure virtual function to 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. + */ + void setErrorCallback( ErrorCallback errorCallback = NULL ) + { + if (rtapi_) rtapi_->setErrorCallback(errorCallback); + } + + //! A basic error reporting function for RtMidi classes. + void error( Error::Type type, std::string errorString ); + protected: + MidiApi *rtapi_; + + Midi():rtapi_(0) {} + ~Midi() + { + if (rtapi_) { + delete rtapi_; + rtapi_ = 0; + } + } + }; + + /**********************************************************************/ + /*! \class MidiIn + \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 and Linux ALSA 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. + */ + /**********************************************************************/ + + // **************************************************************** // + // + // MidiIn and MidiOut class declarations. + // + // MidiIn / MidiOut 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. MidiIn and MidiOut + // 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 MidiIn : public Midi + { + public: + + + //! 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 JACK, ALSA (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. + */ + MidiIn( ApiType api=rtmidi::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. + ~MidiIn ( void ) throw(); + + //! 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 Opitionally, a pointer to additional data can be + passed to the callback function whenever it is called. + */ + void setCallback( MidiCallback callback, void *userData = 0 ) + { + if (rtapi_) + static_cast(rtapi_)->setCallback(callback,userData); + } + + //! 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() + { + if (rtapi_) + static_cast(rtapi_)->cancelCallback(); + } + + //! 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 ) + { + if (rtapi_) + static_cast(rtapi_)->ignoreTypes(midiSysex, midiTime, midiSense); + } + + //! 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 &message ) + { + if (rtapi_) + return static_cast(rtapi_)->getMessage(message); + std::string errorString_ = "MidiIn::getMessage: No valid API found."; + error( Error::WARNING, errorString_ ); + return 0.0; + } + + //! 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. + + \deprecated + */ + RTMIDI_DEPRECATED(double getMessage( std::vector *message )) + { + if (!message) { + error( Error::WARNING, + "MidiIn::getMessage: passed NULL pointer"); + } + if (rtapi_) + return static_cast(rtapi_)->getMessage(*message); + error( Error::WARNING, + "MidiIn::getMessage: No valid API found."); + return 0.0; + } + + protected: + void openMidiApi( ApiType api, const std::string clientName, unsigned int queueSizeLimit ); + + }; + + /**********************************************************************/ + /*! \class MidiOut + \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 and Linux ALSA 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 MidiOut : public Midi + { + 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 JACK, ALSA (Linux) and CORE, + JACK (OS-X). + */ + MidiOut( ApiType api=rtmidi::UNSPECIFIED, + const std::string clientName = std::string( "RtMidi Output Client") ); + + //! The destructor closes any open MIDI connections. + ~MidiOut( void ) throw(); + + //! 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. + + \deprecated + */ + RTMIDI_DEPRECATED(void sendMessage( std::vector *message )) + { + if (!message) { + error( Error::WARNING, + "MidiOutApi::sendMessage: no data in message argument!"); + } + if (rtapi_) + static_cast(rtapi_)->sendMessage(*message); + else + error( Error::WARNING, "MidiOut::sendMessage: The API has not been set."); + } + + + //! 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 &message ) { + if (rtapi_) + static_cast(rtapi_)->sendMessage(message); + error( Error::WARNING, "MidiOut::sendMessage: The API has not been set."); + } + protected: + void openMidiApi( ApiType api, const std::string clientName ); + }; + + + // **************************************************************** // + // + // MidiInApi and MidiOutApi subclass prototypes. + // + // **************************************************************** // #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(__WINDOWS_KS__) - #define __RTMIDI_DUMMY__ +#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 *message ); - - protected: - void initialize( const std::string& clientName ); -}; + class MidiInCore: public MidiInApi + { + public: + MidiInCore( const std::string clientName, unsigned int queueSizeLimit ); + ~MidiInCore( void ); + ApiType getCurrentApi( void ) throw() { return MACOSX_CORE; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + 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 ); + ApiType getCurrentApi( void ) throw() { return MACOSX_CORE; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + void closePort( void ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( std::vector &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 *message ); - - protected: - std::string clientName; - - void connect( void ); - void initialize( const std::string& clientName ); -}; + class MidiInJack: public MidiInApi + { + public: + MidiInJack( const std::string clientName, unsigned int queueSizeLimit ); + ~MidiInJack( void ); + ApiType getCurrentApi( void ) throw() { return UNIX_JACK; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + 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 ); + ApiType getCurrentApi( void ) throw() { return UNIX_JACK; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + void closePort( void ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( std::vector &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 *message ); - - protected: - void initialize( const std::string& clientName ); -}; + class MidiInAlsa: public MidiInApi + { + public: + MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ); + ~MidiInAlsa( void ); + ApiType getCurrentApi( void ) throw() { return LINUX_ALSA; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + 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 ); + ApiType getCurrentApi( void ) throw() { return LINUX_ALSA; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + void closePort( void ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( std::vector &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 *message ); - - protected: - void initialize( const std::string& clientName ); -}; + class MidiInWinMM: public MidiInApi + { + public: + MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ); + ~MidiInWinMM( void ); + ApiType getCurrentApi( void ) { return WINDOWS_MM; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + 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 ); + ApiType getCurrentApi( void ) { return WINDOWS_MM; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + void closePort( void ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( std::vector &message ); + + protected: + void initialize( const std::string& clientName ); + }; #endif #if defined(__WINDOWS_KS__) -class MidiInWinKS: public MidiInApi -{ - public: - MidiInWinKS( const std::string clientName, unsigned int queueSizeLimit ); - ~MidiInWinKS( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_KS; }; - 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 MidiOutWinKS: public MidiOutApi -{ - public: - MidiOutWinKS( const std::string clientName ); - ~MidiOutWinKS( void ); - RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_KS; }; - 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 *message ); - - protected: - void initialize( const std::string& clientName ); -}; + class MidiInWinKS: public MidiInApi + { + public: + MidiInWinKS( const std::string clientName, unsigned int queueSizeLimit ); + ~MidiInWinKS( void ); + ApiType getCurrentApi( void ) { return WINDOWS_KS; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + void closePort( void ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + void initialize( const std::string& clientName ); + }; + + class MidiOutWinKS: public MidiOutApi + { + public: + MidiOutWinKS( const std::string clientName ); + ~MidiOutWinKS( void ); + ApiType getCurrentApi( void ) { return WINDOWS_KS; }; + void openPort( unsigned int portNumber, const std::string & portName ); + void openVirtualPort( const std::string portName ); + void openPort( const PortDescriptor & port, const std::string & portName); + Pointer getDescriptor(bool local=false); + PortList getPortList(int capabilities); + void closePort( void ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( std::vector &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 * /*message*/ ) {} - - protected: - void initialize( const std::string& /*clientName*/ ) {} -}; + class MidiInDummy: public MidiInApi + { + public: + MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) { + errorString_ = "MidiInDummy: This class provides no functionality."; + error( Error::WARNING, errorString_ ); + } + ApiType getCurrentApi( void ) { return RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const & std::string /*portName*/ ) {} + void openVirtualPort( const std::string /*portName*/ ) {} + void openPort( const PortDescriptor & port, const & std::string portName) {} + Pointer getDescriptor(bool local=false) { return 0; } + PortList getPortList(int capabilities) { return PortList(); } + 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( Error::WARNING, errorString_ ); + } + ApiType getCurrentApi( void ) { return RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const & std::string /*portName*/ ) {} + void openVirtualPort( const std::string /*portName*/ ) {} + void openPort( const PortDescriptor & port, const & std::string portName) {} + Pointer getDescriptor(bool local=false) { return 0; } + PortList getPortList(int capabilities) { return PortList(); } + void closePort( void ) {} + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } + void sendMessage( std::vector & /*message*/ ) {} + + protected: + void initialize( const std::string& /*clientName*/ ) {} + }; #endif +} + +typedef rtmidi::Midi RtMidi; +typedef rtmidi::MidiIn RtMidiIn; +typedef rtmidi::MidiOut RtMidiOut; +typedef rtmidi::Error RtMidiError; #endif diff --git a/configure.ac b/configure.ac index f4f497aa..e0361195 100644 --- a/configure.ac +++ b/configure.ac @@ -68,6 +68,13 @@ case $host in api="$api -D__UNIX_JACK__" AC_MSG_RESULT(using JACK) AC_CHECK_LIB(jack, jack_client_open, , AC_MSG_ERROR(JACK support requires the jack library!))], ) + AC_LANG_PUSH(C++) + AC_TRY_COMPILE([ +#include + ],[ +return jack_port_uuid(NULL); + ],api="$api -D__UNIX_JACK_HAS_UUID__") + AC_LANG_POP(C++) # Look for ALSA flag AC_ARG_WITH(alsa, [ --with-alsa = choose native ALSA sequencer API support (linux only)], [ @@ -92,6 +99,13 @@ case $host in api="$api -D__UNIX_JACK__" AC_MSG_RESULT(using JACK) AC_CHECK_LIB(jack, jack_client_open, , AC_MSG_ERROR(JACK support requires the jack library!))], ) + AC_LANG_PUSH(C++) + AC_TRY_COMPILE([ +#include + ],[ +return jack_port_uuid(NULL); + ],api="$api -D__UNIX_JACK_HAS_UUID__") + AC_LANG_POP(C++) # Look for Core flag AC_ARG_WITH(core, [ --with-core = choose CoreMidi API support (mac only)], [ diff --git a/tests/Makefile.in b/tests/Makefile.in index 1826737e..8498dfdd 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -1,7 +1,7 @@ ### Do not edit -- Generated by 'configure --with-whatever' from Makefile.in ### RtMidi tests Makefile - for various flavors of unix -PROGRAMS = midiprobe midiout qmidiin cmidiin sysextest +PROGRAMS = midiprobe midiout qmidiin cmidiin sysextest midiprobe2 cmidiin2 qmidiin2 midiout2 loopback RM = /bin/rm SRC_PATH = .. INCLUDE = .. @@ -21,21 +21,37 @@ LIBRARY = @LIBS@ all : $(PROGRAMS) +RtMidi.o: $(SRC_PATH)/RtMidi.cpp $(SRC_PATH)/RtMidi.h midiprobe : midiprobe.cpp $(OBJECTS) $(CC) $(CFLAGS) $(DEFS) -o midiprobe midiprobe.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) +midiprobe2 : midiprobe2.cpp $(OBJECTS) + $(CC) $(CFLAGS) $(DEFS) -o midiprobe2 midiprobe2.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) + midiout : midiout.cpp $(OBJECTS) $(CC) $(CFLAGS) $(DEFS) -o midiout midiout.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) +midiout2 : midiout2.cpp $(OBJECTS) + $(CC) $(CFLAGS) $(DEFS) -o midiout2 midiout2.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) + qmidiin : qmidiin.cpp $(OBJECTS) $(CC) $(CFLAGS) $(DEFS) -o qmidiin qmidiin.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) +qmidiin2 : qmidiin2.cpp $(OBJECTS) + $(CC) $(CFLAGS) $(DEFS) -o qmidiin2 qmidiin2.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) + cmidiin : cmidiin.cpp $(OBJECTS) $(CC) $(CFLAGS) $(DEFS) -o cmidiin cmidiin.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) +cmidiin2 : cmidiin2.cpp $(OBJECTS) + $(CC) $(CFLAGS) $(DEFS) -o cmidiin2 cmidiin2.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) + sysextest : sysextest.cpp $(OBJECTS) $(CC) $(CFLAGS) $(DEFS) -o sysextest sysextest.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) +loopback : loopback.cpp $(OBJECTS) + $(CC) $(CFLAGS) $(DEFS) -o loopback loopback.cpp $(OBJECT_PATH)/RtMidi.o $(LIBRARY) + clean : $(RM) -f $(OBJECT_PATH)/*.o $(RM) -f $(PROGRAMS) *.exe diff --git a/tests/cmidiin.cpp b/tests/cmidiin.cpp index 5aa4053c..49e48edb 100644 --- a/tests/cmidiin.cpp +++ b/tests/cmidiin.cpp @@ -19,7 +19,7 @@ void usage( void ) { exit( 0 ); } -void mycallback( double deltatime, std::vector< unsigned char > *message, void *userData ) +void mycallback( double deltatime, std::vector< unsigned char > *message, void * /* userData */ ) { unsigned int nBytes = message->size(); for ( unsigned int i=0; i *message, void * // It returns false if there are no ports available. bool chooseMidiPort( RtMidiIn *rtmidi ); -int main( int argc, char *argv[] ) +int main( int argc, char ** /*argv[]*/ ) { RtMidiIn *midiin = 0; diff --git a/tests/cmidiin2.cpp b/tests/cmidiin2.cpp new file mode 100644 index 00000000..cfd90942 --- /dev/null +++ b/tests/cmidiin2.cpp @@ -0,0 +1,113 @@ +//*****************************************// +// cmidiin.cpp +// by Gary Scavone, 2003-2004. +// +// Simple program to test MIDI input and +// use of a user callback function. +// +//*****************************************// + +#include +#include +#include "RtMidi.h" + +void usage( void ) { + // Error function in case of incorrect command-line + // argument specifications. + std::cout << "\nuseage: cmidiin2 \n"; + std::cout << " where port = the device to use (default = 0).\n\n"; + exit( 0 ); +} + +void mycallback( double deltatime, std::vector< unsigned char > *message, void * /* userData */ ) +{ + unsigned int nBytes = message->size(); + for ( unsigned int i=0; i 0 ) + std::cout << "stamp = " << deltatime << std::endl; +} + +// This function should be embedded in a try/catch block in case of +// an exception. It offers the user a choice of MIDI ports to open. +// It returns false if there are no ports available. +bool chooseMidiPort( RtMidiIn &rtmidi ); + +int main( int argc, char */*argv*/[] ) +{ + + // Minimal command-line check. + if ( argc > 2 ) usage(); + + try { + + // RtMidiIn constructor + RtMidiIn midiin; + + // Call function to select port. + if ( chooseMidiPort( midiin ) == false ) return 0; + + // Set our callback function. This should be done immediately after + // opening the port to avoid having incoming messages written to the + // queue instead of sent to the callback function. + midiin.setCallback( &mycallback ); + + // Don't ignore sysex, timing, or active sensing messages. + midiin.ignoreTypes( false, false, false ); + + std::cout << "\nReading MIDI input ... press to quit.\n"; + char input; + std::cin.get(input); + + } catch ( RtMidiError &error ) { + error.printMessage(); + } +} + +bool chooseMidiPort( RtMidiIn &midi ) +{ + std::cout << "\nWould you like to open a virtual input port? [y/N] "; + + std::string keyHit; + std::getline( std::cin, keyHit ); + if ( keyHit == "y" ) { + midi.openVirtualPort("RtMidi virtual input"); + return true; + } + + std::string portName; + rtmidi::PortList list = midi.getPortList(rtmidi::PortDescriptor::INPUT); + if ( list.empty() ) { + std::cout << "No input ports available!" << std::endl; + return false; + } + + rtmidi::Pointer selected = list.front(); + if ( list.size() == 1 ) { + std::cout << "\nOpening " << selected->getName() << std::endl; + } else { + int nr; + std::vector > pointers(list.size()); + // copy the data into a structure that is used by the user interface. + std::copy(list.begin(),list.end(),pointers.begin()); + for (nr = 0 ; nr < (int)pointers.size(); nr++) { + portName = pointers[nr]->getName(rtmidi::PortDescriptor::LONG_NAME + | rtmidi::PortDescriptor::UNIQUE_NAME + | rtmidi::PortDescriptor::INCLUDE_API); + std::cout << " Input port #" << nr << ": " << portName << '\n'; + } + + do { + std::cout << "\nChoose a port number: "; + std::cin >> nr; + } while ( nr >= (int)pointers.size() ); + std::getline( std::cin, keyHit ); // used to clear out stdin + selected = pointers[nr]; + } + + /* The midi setup might have changed meanwhile. + Our portlist is under our control. So we enumerate this list. */ + // midi.openPort( i ); + midi.openPort(selected); + return true; +} diff --git a/tests/loopback.cpp b/tests/loopback.cpp new file mode 100644 index 00000000..2ddefd92 --- /dev/null +++ b/tests/loopback.cpp @@ -0,0 +1,208 @@ +//*****************************************// +// loopback +// by Tobis Schlemmer, 2014. +// inspired by virtual-loopback-test-automated.js from the node-midi project. +// donated to RtMidi. +// +// Simple program to test MIDI input and +// output using a user callback function. +// +//*****************************************// + +#include +#include +#include "RtMidi.h" + +// Platform-dependent sleep routines. +#if defined(__WINDOWS_MM__) +#include +#define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants +#include +#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + + +/* Here, we store the expected output. Timing is not tested */ +std::vector virtualinstring; +const char * virtualinstringgoal = + "\xc0\x5\xf1\x3c\xb0\x7\x64\x90\x40\x5a\x80\x40\x28\xb0\x7\x28\xf0\x43\x4\x3\x2\xf7"; +std::vector instring; +const char * instringgoal = + "\xc0\x6\xf1\x3d\xb0\x8\x64\x90\x41\x5a\x80\x41\x28\xb0\x8\x28\xf0\x43\x4\x3\x3\xf7"; + +inline size_t getlength(const char * messages) { + size_t retval = 0; + const unsigned char * c = reinterpret_cast(messages); + while (*(c++) != 0xf7) retval++; + return ++retval; +} + +void mycallback1( double deltatime, std::vector< unsigned char > *message, void * /* userData */ ) +{ + unsigned int nBytes = message->size(); + for ( unsigned int i=0; iat(i)); + // std::cout << "\\x" << std::hex << (int)message->at(i) << std::flush; + } + /* if ( nBytes > 0 ) + std::cout << "stamp = " << deltatime << std::endl; + */ +} + +void mycallback2( double deltatime, std::vector< unsigned char > *message, void * /* userData */ ) +{ + unsigned int nBytes = message->size(); + for ( unsigned int i=0; iat(i)); + // std::cout << "\\x" << std::hex << (int)message->at(i); + } + /* + if ( nBytes > 0 ) + std::cout << "stamp = " << deltatime << std::endl; + */ +} + + + +int main( int argc, char */*argv*/[] ) +{ + + std::vector message; + + try { + + // RtMidiIn constructor + RtMidiIn virtualin; + // RtMidiIn constructor + RtMidiOut virtualout; + + virtualin.openVirtualPort("RtMidi Test Virtual In"); + virtualout.openVirtualPort("RtMidi Test Virtual Out"); + + rtmidi::Pointer indescriptor + = virtualin.getDescriptor(true); + + rtmidi::Pointer outdescriptor + = virtualout.getDescriptor(true); + + { // avoid problems with wrong destruction order + /* use smart pointers to handle deletion */ + rtmidi::Pointer midiin = outdescriptor->getInputApi(); + if (!midiin) abort(); + rtmidi::Pointer midiout = indescriptor->getOutputApi(); + if (!midiout) abort(); + + + + midiin->openPort(outdescriptor); + midiout->openPort(indescriptor); + + + // Set our callback function. This should be done immediately after + // opening the port to avoid having incoming messages written to the + // queue instead of sent to the callback function. + midiin->setCallback( &mycallback1 ); + virtualin.setCallback( &mycallback2 ); + + // Don't ignore sysex, timing, or active sensing messages. + // Don't ignore sysex, timing, or active sensing messages. + midiin->ignoreTypes( false, false, false ); + virtualin.ignoreTypes( false, false, false ); + + SLEEP( 5000 ); + + // Send out a series of MIDI messages. + + // Program change: 192, 5 + message.push_back( 192 ); + message.push_back( 5 ); + midiout->sendMessage( &message ); + message[1] = 6; + virtualout.sendMessage(&message); + + SLEEP( 500 ); + + message[0] = 0xF1; + message[1] = 60; + midiout->sendMessage( &message ); + message[1] = 61; + virtualout.sendMessage(&message); + + // Control Change: 176, 7, 100 (volume) + message[0] = 176; + message[1] = 7; + message.push_back( 100 ); + midiout->sendMessage( &message ); + message[1] = 8; + virtualout.sendMessage ( &message ); + + // Note On: 144, 64, 90 + message[0] = 144; + message[1] = 64; + message[2] = 90; + midiout->sendMessage( &message ); + message[1] = 65; + virtualout.sendMessage( &message ); + + SLEEP( 500 ); + + // Note Off: 128, 64, 40 + message[0] = 128; + message[1] = 64; + message[2] = 40; + midiout->sendMessage( &message ); + message[1] = 65; + virtualout.sendMessage( &message ); + + SLEEP( 500 ); + + // Control Change: 176, 7, 40 + message[0] = 176; + message[1] = 7; + message[2] = 40; + midiout->sendMessage( &message ); + message[1] = 8; + virtualout.sendMessage( &message ); + + SLEEP( 500 ); + + // Sysex: 240, 67, 4, 3, 2, 247 + message[0] = 240; + message[1] = 67; + message[2] = 4; + message.push_back( 3 ); + message.push_back( 2 ); + message.push_back( 247 ); + midiout->sendMessage( &message ); + message[4] = 3; + virtualout.sendMessage( &message ); + + SLEEP( 500 ); + } + const unsigned char * goal = reinterpret_cast(instringgoal); + size_t i; + std::cout << "Virtual output -> input:" << std::endl; + if (instring.size() != getlength(instringgoal)) abort(); + for (i = 0 ; i < instring.size() && goal[i] ; i++){ + std::cout << " " << std::hex << (int) instring[i]; + std::cout << "/" << std::hex << (int) goal[i] << std::flush; + if (instring[i] != goal[i]) abort(); + } + std::cout << std::endl; + if (i != instring.size()) abort(); + goal = reinterpret_cast(virtualinstringgoal); + std::cout << "Output -> virtual input:" << std::endl; + if (instring.size() != getlength(virtualinstringgoal)) abort(); + for (i = 0 ; i < virtualinstring.size() && goal[i] ; i++) { + std::cout << " " << std::hex << (int) virtualinstring[i]; + std::cout << "/" << std::hex << (int) goal[i] << std::flush; + if (virtualinstring[i] != goal[i]) abort(); + } + std::cout << std::endl; + if (i != virtualinstring.size()) abort(); + + } catch ( RtMidiError &error ) { + error.printMessage(); + } +} diff --git a/tests/midiout.cpp b/tests/midiout.cpp index 0a9b9fdf..49c5b081 100644 --- a/tests/midiout.cpp +++ b/tests/midiout.cpp @@ -24,7 +24,7 @@ // It returns false if there are no ports available. bool chooseMidiPort( RtMidiOut *rtmidi ); -int main( int argc, char *argv[] ) +int main( int /* argc*/, char */*argv*/[] ) { RtMidiOut *midiout = 0; std::vector message; diff --git a/tests/midiout2.cpp b/tests/midiout2.cpp new file mode 100644 index 00000000..af8a57a7 --- /dev/null +++ b/tests/midiout2.cpp @@ -0,0 +1,159 @@ +//*****************************************// +// midiout.cpp +// by Gary Scavone, 2003-2004. +// +// Simple program to test MIDI output. +// +//*****************************************// + +#include +#include +#include "RtMidi.h" + +// Platform-dependent sleep routines. +#if defined(__WINDOWS_MM__) +#include +#define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants +#include +#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + +// This function should be embedded in a try/catch block in case of +// an exception. It offers the user a choice of MIDI ports to open. +// It returns false if there are no ports available. +bool chooseMidiPort( RtMidiOut &ortmidi ); + +int main( int /* argc*/, char */*argv*/[] ) +{ + std::vector message; + + // RtMidiOut constructor + try { + RtMidiOut midiout; + + // Call function to select port. + try { + if ( chooseMidiPort( midiout ) == false ) return 1; + } + catch ( RtMidiError &error ) { + error.printMessage(); + return 2; + } + + + SLEEP( 5000 ); + + // Send out a series of MIDI messages. + + // Program change: 192, 5 + message.push_back( 192 ); + message.push_back( 5 ); + midiout.sendMessage( &message ); + + SLEEP( 500 ); + + message[0] = 0xF1; + message[1] = 60; + midiout.sendMessage( &message ); + + // Control Change: 176, 7, 100 (volume) + message[0] = 176; + message[1] = 7; + message.push_back( 100 ); + midiout.sendMessage( &message ); + + // Note On: 144, 64, 90 + message[0] = 144; + message[1] = 64; + message[2] = 90; + midiout.sendMessage( &message ); + + SLEEP( 500 ); + + // Note Off: 128, 64, 40 + message[0] = 128; + message[1] = 64; + message[2] = 40; + midiout.sendMessage( &message ); + + SLEEP( 500 ); + + // Control Change: 176, 7, 40 + message[0] = 176; + message[1] = 7; + message[2] = 40; + midiout.sendMessage( &message ); + + SLEEP( 500 ); + + // Sysex: 240, 67, 4, 3, 2, 247 + message[0] = 240; + message[1] = 67; + message[2] = 4; + message.push_back( 3 ); + message.push_back( 2 ); + message.push_back( 247 ); + midiout.sendMessage( &message ); + + + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + return 0; +} + +bool chooseMidiPort( RtMidiOut &midi ) +{ + std::cout << "\nWould you like to open a virtual output port? [y/N] "; + + std::string keyHit; + std::getline( std::cin, keyHit ); + if ( keyHit == "y" ) { + midi.openVirtualPort("RtMidi virtual output"); + std::cout << "Press return to start the transmission." << std::endl; + std::getline( std::cin, keyHit ); + + return true; + } + + std::string portName; + rtmidi::PortList list = midi.getPortList(rtmidi::PortDescriptor::OUTPUT); + if ( list.empty() ) { + std::cout << "No output ports available!" << std::endl; + return false; + } + + rtmidi::Pointer selected = list.front(); + if ( list.size() == 1 ) { + std::cout << "\nOpening " << selected->getName() << std::endl; + } else { + int nr; + std::vector > pointers(list.size()); + // copy the data into a structure that is used by the user interface. + std::copy(list.begin(),list.end(),pointers.begin()); + for (nr = 0 ; nr < (int)pointers.size(); nr++) { + portName = pointers[nr]->getName(rtmidi::PortDescriptor::LONG_NAME + | rtmidi::PortDescriptor::UNIQUE_NAME + | rtmidi::PortDescriptor::INCLUDE_API); + std::cout << " Output port #" << nr << ": " << portName << '\n'; + } + + do { + std::cout << "\nChoose a port number: "; + std::cin >> nr; + } while ( nr >= (int)pointers.size() ); + std::getline( std::cin, keyHit ); // used to clear out stdin + selected = pointers[nr]; + } + + /* The midi setup might have changed meanwhile. + Our portlist is under our control. So we enumerate this list. */ + // midi.openPort( i ); + midi.openPort(selected); + + return true; +} diff --git a/tests/midiprobe2.cpp b/tests/midiprobe2.cpp new file mode 100644 index 00000000..9c599c9e --- /dev/null +++ b/tests/midiprobe2.cpp @@ -0,0 +1,134 @@ +// midiprobe.cpp +// +// Simple program to check MIDI inputs and outputs. +// +// by Gary Scavone, 2003-2012. + +#include +#include +#include +#include "RtMidi.h" + +int main() +{ + // Create an api map. + std::map apiMap; + apiMap[RtMidi::MACOSX_CORE] = "OS-X CoreMidi"; + apiMap[RtMidi::WINDOWS_MM] = "Windows MultiMedia"; + apiMap[RtMidi::WINDOWS_KS] = "Windows Kernel Straming"; + apiMap[RtMidi::UNIX_JACK] = "Jack Client"; + apiMap[RtMidi::LINUX_ALSA] = "Linux ALSA"; + apiMap[RtMidi::RTMIDI_DUMMY] = "RtMidi Dummy"; + + std::vector< RtMidi::Api > apis; + RtMidi :: getCompiledApi( apis ); + + std::cout << "\nCompiled APIs:\n"; + for ( unsigned int i=0; igetName() << std::endl; + for (int j = 0 ; j < 4 ; j++ ) { + std::cout << j << ":f:f: " << (*i)->getName (j) << std::endl; + std::cout << j << ":t:f: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME) << std::endl; + std::cout << j << ":f:t: " << (*i)->getName (j | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << j << ":t:t: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME + | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << std::endl; + } + std::cout << std::endl; + } + + std::cout << "**********************************************************************" << std::endl; + + // RtMidiOut constructor ... exception possible + RtMidiOut midiout; + + std::cout << "\nCurrent output API: " << apiMap[ midiout.getCurrentApi() ] << std::endl; + + list = midiout.getPortList(); + + // Check inputs. + std::cout << "\nThere are " << list.size() << " MIDI output sinks available.\n"; + + for (rtmidi::PortList::iterator i = list.begin(); + i != list.end(); + i++) { + std::cout << " Output Port: " << (*i)->getName() << std::endl; + for (int j = 0 ; j < 4 ; j++ ) { + std::cout << j << ":f:f: " << (*i)->getName (j) << std::endl; + std::cout << j << ":t:f: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME) << std::endl; + std::cout << j << ":f:t: " << (*i)->getName (j | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << j << ":t:t: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME + | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << std::endl; + } + std::cout << std::endl; + } + + std::cout << "**********************************************************************" << std::endl; + std::cout << "* entering unlimited mode *" << std::endl; + std::cout << "**********************************************************************" << std::endl; + + list = midiin.getPortList(rtmidi::PortDescriptor::UNLIMITED); + // Check inputs. + std::cout << "\nThere are " << list.size() << " MIDI input sources available in unlimited mode.\n"; + for (rtmidi::PortList::iterator i = list.begin(); + i != list.end(); + i++) { + std::cout << " Input Port: " << (*i)->getName() << std::endl; + for (int j = 0 ; j < 4 ; j++ ) { + std::cout << j << ":f:f: " << (*i)->getName (j) << std::endl; + std::cout << j << ":t:f: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME) << std::endl; + std::cout << j << ":f:t: " << (*i)->getName (j | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << j << ":t:t: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME + | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << std::endl; + } + std::cout << std::endl; + } + + std::cout << "**********************************************************************" << std::endl; + list = midiout.getPortList(rtmidi::PortDescriptor::UNLIMITED); + + // Check inputs. + std::cout << "\nThere are " << list.size() << " MIDI output sinks available in unlimited mode.\n"; + + for (rtmidi::PortList::iterator i = list.begin(); + i != list.end(); + i++) { + std::cout << " Output Port: " << (*i)->getName() << std::endl; + for (int j = 0 ; j < 4 ; j++ ) { + std::cout << j << ":f:f: " << (*i)->getName (j) << std::endl; + std::cout << j << ":t:f: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME) << std::endl; + std::cout << j << ":f:t: " << (*i)->getName (j | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << j << ":t:t: " << (*i)->getName (j | rtmidi::PortDescriptor::UNIQUE_NAME + | rtmidi::PortDescriptor::INCLUDE_API) << std::endl; + std::cout << std::endl; + } + std::cout << std::endl; + } + + + + } catch ( RtMidiError &error ) { + error.printMessage(); + } + + return 0; +} diff --git a/tests/qmidiin.cpp b/tests/qmidiin.cpp index 2adc03de..afd112b0 100644 --- a/tests/qmidiin.cpp +++ b/tests/qmidiin.cpp @@ -22,7 +22,7 @@ #endif bool done; -static void finish( int ignore ){ done = true; } +static void finish( int /*ignore*/ ){ done = true; } void usage( void ) { // Error function in case of incorrect command-line diff --git a/tests/qmidiin2.cpp b/tests/qmidiin2.cpp new file mode 100644 index 00000000..e3e1a996 --- /dev/null +++ b/tests/qmidiin2.cpp @@ -0,0 +1,117 @@ +//*****************************************// +// qmidiin.cpp +// by Gary Scavone, 2003-2004. +// +// Simple program to test MIDI input and +// retrieval from the queue. +// +//*****************************************// + +#include +#include +#include +#include "RtMidi.h" + +// Platform-dependent sleep routines. +#if defined(__WINDOWS_MM__) +#include +#define SLEEP( milliseconds ) Sleep( (DWORD) milliseconds ) +#else // Unix variants +#include +#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) +#endif + +bool done; +static void finish( int /*ignore*/ ){ done = true; } + +void usage( rtmidi::PortList list ) { + // Error function in case of incorrect command-line + // argument specifications. + std::cout << "\nusage: qmidiin \n"; + std::cout << " where port = the device to use (default = first available port).\n\n"; + + std::cout << "Available ports:" << std::endl; + for (rtmidi::PortList::iterator i = list.begin(); + i != list.end(); i++) { + std::cout << (*i)->getName(rtmidi::PortDescriptor::SESSION_PATH | + rtmidi::PortDescriptor::UNIQUE_NAME | + rtmidi::PortDescriptor::INCLUDE_API); + std::cout << "\t"; + std::cout << (*i)->getName() << std::endl; + } + exit( 0 ); +} + +int main( int argc, char *argv[] ) +{ + std::vector message; + int nBytes, i; + double stamp; + + + // RtMidiIn constructor + try { + RtMidiIn midiin; + + + rtmidi::PortList list = midiin.getPortList(); + + // Minimal command-line check. + if ( argc > 2 ) usage(list); + + rtmidi::Pointer port = 0; + // Check available ports vs. specified. + if ( argc == 2 ) { + for (rtmidi::PortList::iterator i = list.begin(); + i != list.end(); i++) { + if (argv[1] == (*i)->getName(rtmidi::PortDescriptor::SESSION_PATH | + rtmidi::PortDescriptor::UNIQUE_NAME | + rtmidi::PortDescriptor::INCLUDE_API)) { + port = *i; + break; + } + } + } else { + port = list.front(); + } + if ( !port ) { + std::cout << "Invalid port specifier!\n"; + usage(list); + } + + try { + midiin.openPort( port ); + } + catch ( RtMidiError &error ) { + error.printMessage(); + return 1; + } + + // Don't ignore sysex, timing, or active sensing messages. + midiin.ignoreTypes( false, false, false ); + + // Install an interrupt handler function. + done = false; + (void) signal(SIGINT, finish); + + // Periodically check input queue. + std::cout << "Reading MIDI from port ... quit with Ctrl-C.\n"; + while ( !done ) { + stamp = midiin.getMessage( &message ); + nBytes = message.size(); + for ( i=0; i 0 ) + std::cout << "stamp = " << stamp << std::endl; + + // Sleep for 10 milliseconds. + SLEEP( 10 ); + } + } + catch ( RtMidiError &error ) { + error.printMessage(); + exit( EXIT_FAILURE ); + } + + return 0; +} diff --git a/tests/sysextest.cpp b/tests/sysextest.cpp index 96eaaa82..8402c74c 100644 --- a/tests/sysextest.cpp +++ b/tests/sysextest.cpp @@ -31,7 +31,7 @@ void usage( void ) { // It returns false if there are no ports available. bool chooseMidiPort( RtMidi *rtmidi ); -void mycallback( double deltatime, std::vector< unsigned char > *message, void *userData ) +void mycallback( double deltatime, std::vector< unsigned char > *message, void * /*userData*/ ) { unsigned int nBytes = message->size(); for ( unsigned int i=0; i