Chris@559: /**********************************************************************/ Chris@559: /*! \class RtMidi Chris@559: \brief An abstract base class for realtime MIDI input/output. Chris@559: Chris@559: This class implements some common functionality for the realtime Chris@559: MIDI input/output subclasses RtMidiIn and RtMidiOut. Chris@559: Chris@559: RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ Chris@559: Chris@559: RtMidi: realtime MIDI i/o C++ classes Chris@1397: Copyright (c) 2003-2016 Gary P. Scavone Chris@559: Chris@559: Permission is hereby granted, free of charge, to any person Chris@559: obtaining a copy of this software and associated documentation files Chris@559: (the "Software"), to deal in the Software without restriction, Chris@559: including without limitation the rights to use, copy, modify, merge, Chris@559: publish, distribute, sublicense, and/or sell copies of the Software, Chris@559: and to permit persons to whom the Software is furnished to do so, Chris@559: subject to the following conditions: Chris@559: Chris@559: The above copyright notice and this permission notice shall be Chris@559: included in all copies or substantial portions of the Software. Chris@559: Chris@559: Any person wishing to distribute modifications to the Software is Chris@1397: asked to send the modifications to the original developer so that Chris@1397: they can be incorporated into the canonical version. This is, Chris@1397: however, not a binding provision of this license. Chris@559: Chris@559: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@559: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@559: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. Chris@559: IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR Chris@559: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@559: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@559: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@559: */ Chris@559: /**********************************************************************/ Chris@559: Chris@559: #include "RtMidi.h" Chris@559: #include <sstream> Chris@559: Chris@1402: // CC be gung-ho about this here, assume upstream has it in hand Chris@1402: #pragma GCC diagnostic ignored "-Wconversion" Chris@1402: Chris@1397: #if defined(__MACOSX_CORE__) Chris@1397: #if TARGET_OS_IPHONE Chris@1397: #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime Chris@1397: #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos Chris@1397: #endif Chris@1397: #endif Chris@843: Chris@559: //*********************************************************************// Chris@1397: // RtMidi Definitions Chris@559: //*********************************************************************// Chris@559: Chris@559: RtMidi :: RtMidi() Chris@1397: : rtapi_(0) Chris@559: { Chris@559: } Chris@559: Chris@1397: RtMidi :: ~RtMidi() Chris@559: { Chris@1397: if ( rtapi_ ) Chris@1397: delete rtapi_; Chris@1397: rtapi_ = 0; Chris@1397: } Chris@1397: Chris@1397: std::string RtMidi :: getVersion( void ) throw() Chris@1397: { Chris@1397: return std::string( RTMIDI_VERSION ); Chris@1397: } Chris@1397: Chris@1397: void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw() Chris@1397: { Chris@1397: apis.clear(); Chris@1397: Chris@1397: // The order here will control the order of RtMidi's API search in Chris@1397: // the constructor. Chris@1397: #if defined(__MACOSX_CORE__) Chris@1397: apis.push_back( MACOSX_CORE ); Chris@1397: #endif Chris@1397: #if defined(__LINUX_ALSA__) Chris@1397: apis.push_back( LINUX_ALSA ); Chris@1397: #endif Chris@1397: #if defined(__UNIX_JACK__) Chris@1397: apis.push_back( UNIX_JACK ); Chris@1397: #endif Chris@1397: #if defined(__WINDOWS_MM__) Chris@1397: apis.push_back( WINDOWS_MM ); Chris@1397: #endif Chris@1397: #if defined(__RTMIDI_DUMMY__) Chris@1397: apis.push_back( RTMIDI_DUMMY ); Chris@1397: #endif Chris@1397: } Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // RtMidiIn Definitions Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) Chris@1397: { Chris@1397: if ( rtapi_ ) Chris@1397: delete rtapi_; Chris@1397: rtapi_ = 0; Chris@1397: Chris@1397: #if defined(__UNIX_JACK__) Chris@1397: if ( api == UNIX_JACK ) Chris@1397: rtapi_ = new MidiInJack( clientName, queueSizeLimit ); Chris@1397: #endif Chris@1397: #if defined(__LINUX_ALSA__) Chris@1397: if ( api == LINUX_ALSA ) Chris@1397: rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); Chris@1397: #endif Chris@1397: #if defined(__WINDOWS_MM__) Chris@1397: if ( api == WINDOWS_MM ) Chris@1397: rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); Chris@1397: #endif Chris@1397: #if defined(__MACOSX_CORE__) Chris@1397: if ( api == MACOSX_CORE ) Chris@1397: rtapi_ = new MidiInCore( clientName, queueSizeLimit ); Chris@1397: #endif Chris@1397: #if defined(__RTMIDI_DUMMY__) Chris@1397: if ( api == RTMIDI_DUMMY ) Chris@1397: rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); Chris@1397: #endif Chris@1397: } Chris@1397: Chris@1397: RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) Chris@1397: : RtMidi() Chris@1397: { Chris@1397: if ( api != UNSPECIFIED ) { Chris@1397: // Attempt to open the specified API. Chris@1397: openMidiApi( api, clientName, queueSizeLimit ); Chris@1397: if ( rtapi_ ) return; Chris@1397: Chris@1397: // No compiled support for specified API value. Issue a warning Chris@1397: // and continue as if no API was specified. Chris@1397: std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; Chris@559: } Chris@1397: Chris@1397: // Iterate through the compiled APIs and return as soon as we find Chris@1397: // one with at least one port or we reach the end of the list. Chris@1397: std::vector< RtMidi::Api > apis; Chris@1397: getCompiledApi( apis ); Chris@1397: for ( unsigned int i=0; i<apis.size(); i++ ) { Chris@1397: openMidiApi( apis[i], clientName, queueSizeLimit ); Chris@1397: if ( rtapi_->getPortCount() ) break; Chris@1397: } Chris@1397: Chris@1397: if ( rtapi_ ) return; Chris@1397: Chris@1397: // It should not be possible to get here because the preprocessor Chris@1397: // definition __RTMIDI_DUMMY__ is automatically defined if no Chris@1397: // API-specific definitions are passed to the compiler. But just in Chris@1397: // case something weird happens, we'll throw an error. Chris@1397: std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; Chris@1397: throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); Chris@1397: } Chris@1397: Chris@1397: RtMidiIn :: ~RtMidiIn() throw() Chris@1397: { Chris@1397: } Chris@1397: Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // RtMidiOut Definitions Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName ) Chris@1397: { Chris@1397: if ( rtapi_ ) Chris@1397: delete rtapi_; Chris@1397: rtapi_ = 0; Chris@1397: Chris@1397: #if defined(__UNIX_JACK__) Chris@1397: if ( api == UNIX_JACK ) Chris@1397: rtapi_ = new MidiOutJack( clientName ); Chris@1397: #endif Chris@1397: #if defined(__LINUX_ALSA__) Chris@1397: if ( api == LINUX_ALSA ) Chris@1397: rtapi_ = new MidiOutAlsa( clientName ); Chris@1397: #endif Chris@1397: #if defined(__WINDOWS_MM__) Chris@1397: if ( api == WINDOWS_MM ) Chris@1397: rtapi_ = new MidiOutWinMM( clientName ); Chris@1397: #endif Chris@1397: #if defined(__MACOSX_CORE__) Chris@1397: if ( api == MACOSX_CORE ) Chris@1397: rtapi_ = new MidiOutCore( clientName ); Chris@1397: #endif Chris@1397: #if defined(__RTMIDI_DUMMY__) Chris@1397: if ( api == RTMIDI_DUMMY ) Chris@1397: rtapi_ = new MidiOutDummy( clientName ); Chris@1397: #endif Chris@1397: } Chris@1397: Chris@1397: RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName ) Chris@1397: { Chris@1397: if ( api != UNSPECIFIED ) { Chris@1397: // Attempt to open the specified API. Chris@1397: openMidiApi( api, clientName ); Chris@1397: if ( rtapi_ ) return; Chris@1397: Chris@1397: // No compiled support for specified API value. Issue a warning Chris@1397: // and continue as if no API was specified. Chris@1397: std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; Chris@1397: } Chris@1397: Chris@1397: // Iterate through the compiled APIs and return as soon as we find Chris@1397: // one with at least one port or we reach the end of the list. Chris@1397: std::vector< RtMidi::Api > apis; Chris@1397: getCompiledApi( apis ); Chris@1397: for ( unsigned int i=0; i<apis.size(); i++ ) { Chris@1397: openMidiApi( apis[i], clientName ); Chris@1397: if ( rtapi_->getPortCount() ) break; Chris@1397: } Chris@1397: Chris@1397: if ( rtapi_ ) return; Chris@1397: Chris@1397: // It should not be possible to get here because the preprocessor Chris@1397: // definition __RTMIDI_DUMMY__ is automatically defined if no Chris@1397: // API-specific definitions are passed to the compiler. But just in Chris@1397: // case something weird happens, we'll thrown an error. Chris@1397: std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; Chris@1397: throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); Chris@1397: } Chris@1397: Chris@1397: RtMidiOut :: ~RtMidiOut() throw() Chris@1397: { Chris@1397: } Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // Common MidiApi Definitions Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: MidiApi :: MidiApi( void ) Chris@1397: : apiData_( 0 ), connected_( false ), errorCallback_(0), errorCallbackUserData_(0) Chris@1397: { Chris@1397: } Chris@1397: Chris@1397: MidiApi :: ~MidiApi( void ) Chris@1397: { Chris@1397: } Chris@1397: Chris@1397: void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 ) Chris@1397: { Chris@1397: errorCallback_ = errorCallback; Chris@1397: errorCallbackUserData_ = userData; Chris@1397: } Chris@1397: Chris@1397: void MidiApi :: error( RtMidiError::Type type, std::string errorString ) Chris@1397: { Chris@1397: if ( errorCallback_ ) { Chris@1397: Chris@1397: if ( firstErrorOccurred_ ) Chris@1397: return; Chris@1397: Chris@1397: firstErrorOccurred_ = true; Chris@1397: const std::string errorMessage = errorString; Chris@1397: Chris@1397: errorCallback_( type, errorMessage, errorCallbackUserData_); Chris@1397: firstErrorOccurred_ = false; Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: if ( type == RtMidiError::WARNING ) { Chris@1397: std::cerr << '\n' << errorString << "\n\n"; Chris@1397: } Chris@1397: else if ( type == RtMidiError::DEBUG_WARNING ) { Chris@559: #if defined(__RTMIDI_DEBUG__) Chris@1397: std::cerr << '\n' << errorString << "\n\n"; Chris@559: #endif Chris@559: } Chris@559: else { Chris@1397: std::cerr << '\n' << errorString << "\n\n"; Chris@1397: throw RtMidiError( errorString, type ); Chris@559: } Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@1397: // Common MidiInApi Definitions Chris@559: //*********************************************************************// Chris@559: Chris@1397: MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) Chris@1397: : MidiApi() Chris@559: { Chris@1397: // Allocate the MIDI queue. Chris@1397: inputData_.queue.ringSize = queueSizeLimit; Chris@1397: if ( inputData_.queue.ringSize > 0 ) Chris@1397: inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; Chris@559: } Chris@559: Chris@1397: MidiInApi :: ~MidiInApi( void ) Chris@1397: { Chris@1397: // Delete the MIDI queue. Chris@1397: if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; Chris@1397: } Chris@1397: Chris@1397: void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) Chris@559: { Chris@559: if ( inputData_.usingCallback ) { Chris@1397: errorString_ = "MidiInApi::setCallback: a callback function is already set!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: if ( !callback ) { Chris@559: errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@1397: inputData_.userCallback = callback; Chris@559: inputData_.userData = userData; Chris@559: inputData_.usingCallback = true; Chris@559: } Chris@559: Chris@1397: void MidiInApi :: cancelCallback() Chris@559: { Chris@559: if ( !inputData_.usingCallback ) { Chris@559: errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: inputData_.userCallback = 0; Chris@559: inputData_.userData = 0; Chris@559: inputData_.usingCallback = false; Chris@559: } Chris@559: Chris@1397: void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) Chris@559: { Chris@559: inputData_.ignoreFlags = 0; Chris@559: if ( midiSysex ) inputData_.ignoreFlags = 0x01; Chris@559: if ( midiTime ) inputData_.ignoreFlags |= 0x02; Chris@559: if ( midiSense ) inputData_.ignoreFlags |= 0x04; Chris@559: } Chris@559: Chris@1397: double MidiInApi :: getMessage( std::vector<unsigned char> *message ) Chris@559: { Chris@559: message->clear(); Chris@559: Chris@559: if ( inputData_.usingCallback ) { Chris@559: errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return 0.0; Chris@559: } Chris@559: Chris@1397: if ( inputData_.queue.size == 0 ) return 0.0; Chris@559: Chris@559: // Copy queued message to the vector pointer argument and then "pop" it. Chris@1397: std::vector<unsigned char> *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes); Chris@559: message->assign( bytes->begin(), bytes->end() ); Chris@1397: double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp; Chris@1397: inputData_.queue.size--; Chris@1397: inputData_.queue.front++; Chris@1397: if ( inputData_.queue.front == inputData_.queue.ringSize ) Chris@1397: inputData_.queue.front = 0; Chris@559: Chris@559: return deltaTime; Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@1397: // Common MidiOutApi Definitions Chris@559: //*********************************************************************// Chris@559: Chris@1397: MidiOutApi :: MidiOutApi( void ) Chris@1397: : MidiApi() Chris@559: { Chris@559: } Chris@559: Chris@1397: MidiOutApi :: ~MidiOutApi( void ) Chris@1397: { Chris@1397: } Chris@1397: Chris@1397: // *************************************************** // Chris@1397: // Chris@1397: // OS/API-specific methods. Chris@1397: // Chris@1397: // *************************************************** // Chris@559: Chris@559: #if defined(__MACOSX_CORE__) Chris@559: Chris@559: // The CoreMIDI API is based on the use of a callback function for Chris@559: // MIDI input. We convert the system specific time stamps to delta Chris@559: // time values. Chris@559: Chris@559: // OS-X CoreMIDI header files. Chris@559: #include <CoreMIDI/CoreMIDI.h> Chris@559: #include <CoreAudio/HostTime.h> Chris@1397: #include <CoreServices/CoreServices.h> Chris@559: Chris@559: // A structure to hold variables related to the CoreMIDI API Chris@559: // implementation. Chris@559: struct CoreMidiData { Chris@559: MIDIClientRef client; Chris@559: MIDIPortRef port; Chris@559: MIDIEndpointRef endpoint; Chris@559: MIDIEndpointRef destinationId; Chris@559: unsigned long long lastTime; Chris@1397: MIDISysexSendRequest sysexreq; Chris@559: }; Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: OS-X Chris@1397: // Class Definitions: MidiInCore Chris@559: //*********************************************************************// Chris@559: Chris@1397: static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) Chris@559: { Chris@1397: MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef); Chris@559: CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData); Chris@559: Chris@559: unsigned char status; Chris@559: unsigned short nBytes, iByte, size; Chris@559: unsigned long long time; Chris@565: Chris@565: bool& continueSysex = data->continueSysex; Chris@1397: MidiInApi::MidiMessage& message = data->message; Chris@565: Chris@559: const MIDIPacket *packet = &list->packet[0]; Chris@559: for ( unsigned int i=0; i<list->numPackets; ++i ) { Chris@559: Chris@559: // My interpretation of the CoreMIDI documentation: all message Chris@559: // types, except sysex, are complete within a packet and there may Chris@559: // be several of them in a single packet. Sysex messages can be Chris@565: // broken across multiple packets and PacketLists but are bundled Chris@565: // alone within each packet (these packets do not contain other Chris@565: // message types). If sysex messages are split across multiple Chris@565: // MIDIPacketLists, they must be handled by multiple calls to this Chris@565: // function. Chris@559: Chris@559: nBytes = packet->length; Chris@559: if ( nBytes == 0 ) continue; Chris@559: Chris@559: // Calculate time stamp. Chris@1397: Chris@1397: if ( data->firstMessage ) { Chris@1397: message.timeStamp = 0.0; Chris@559: data->firstMessage = false; Chris@1397: } Chris@559: else { Chris@559: time = packet->timeStamp; Chris@1397: if ( time == 0 ) { // this happens when receiving asynchronous sysex messages Chris@1397: time = AudioGetCurrentHostTime(); Chris@1397: } Chris@559: time -= apiData->lastTime; Chris@559: time = AudioConvertHostTimeToNanos( time ); Chris@1397: if ( !continueSysex ) Chris@1397: message.timeStamp = time * 0.000000001; Chris@559: } Chris@559: apiData->lastTime = packet->timeStamp; Chris@1397: if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages Chris@1397: apiData->lastTime = AudioGetCurrentHostTime(); Chris@1397: } Chris@1397: //std::cout << "TimeStamp = " << packet->timeStamp << std::endl; Chris@559: Chris@559: iByte = 0; Chris@559: if ( continueSysex ) { Chris@559: // We have a continuing, segmented sysex message. Chris@565: if ( !( data->ignoreFlags & 0x01 ) ) { Chris@559: // If we're not ignoring sysex messages, copy the entire packet. Chris@1397: for ( unsigned int j=0; j<nBytes; ++j ) Chris@559: message.bytes.push_back( packet->data[j] ); Chris@559: } Chris@565: continueSysex = packet->data[nBytes-1] != 0xF7; Chris@565: Chris@1397: if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { Chris@559: // If not a continuing sysex message, invoke the user callback function or queue the message. Chris@1397: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@1397: if ( data->queue.size < data->queue.ringSize ) { Chris@1397: data->queue.ring[data->queue.back++] = message; Chris@1397: if ( data->queue.back == data->queue.ringSize ) Chris@1397: data->queue.back = 0; Chris@1397: data->queue.size++; Chris@1397: } Chris@559: else Chris@1397: std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; Chris@559: } Chris@559: message.bytes.clear(); Chris@559: } Chris@559: } Chris@559: else { Chris@559: while ( iByte < nBytes ) { Chris@559: size = 0; Chris@559: // We are expecting that the next byte in the packet is a status byte. Chris@559: status = packet->data[iByte]; Chris@559: if ( !(status & 0x80) ) break; Chris@559: // Determine the number of bytes in the MIDI message. Chris@559: if ( status < 0xC0 ) size = 3; Chris@559: else if ( status < 0xE0 ) size = 2; Chris@559: else if ( status < 0xF0 ) size = 3; Chris@559: else if ( status == 0xF0 ) { Chris@559: // A MIDI sysex Chris@559: if ( data->ignoreFlags & 0x01 ) { Chris@559: size = 0; Chris@559: iByte = nBytes; Chris@559: } Chris@559: else size = nBytes - iByte; Chris@1397: continueSysex = packet->data[nBytes-1] != 0xF7; Chris@559: } Chris@1397: else if ( status == 0xF1 ) { Chris@1397: // A MIDI time code message Chris@1397: if ( data->ignoreFlags & 0x02 ) { Chris@559: size = 0; Chris@1397: iByte += 2; Chris@1397: } Chris@1397: else size = 2; Chris@559: } Chris@1397: else if ( status == 0xF2 ) size = 3; Chris@559: else if ( status == 0xF3 ) size = 2; Chris@1397: else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { Chris@1397: // A MIDI timing tick message and we're ignoring it. Chris@1397: size = 0; Chris@1397: iByte += 1; Chris@559: } Chris@1397: else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { Chris@559: // A MIDI active sensing message and we're ignoring it. Chris@559: size = 0; Chris@559: iByte += 1; Chris@559: } Chris@559: else size = 1; Chris@559: Chris@559: // Copy the MIDI data to our vector. Chris@559: if ( size ) { Chris@559: message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); Chris@559: if ( !continueSysex ) { Chris@559: // If not a continuing sysex message, invoke the user callback function or queue the message. Chris@559: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@1397: if ( data->queue.size < data->queue.ringSize ) { Chris@1397: data->queue.ring[data->queue.back++] = message; Chris@1397: if ( data->queue.back == data->queue.ringSize ) Chris@1397: data->queue.back = 0; Chris@1397: data->queue.size++; Chris@1397: } Chris@559: else Chris@1397: std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; Chris@559: } Chris@559: message.bytes.clear(); Chris@559: } Chris@559: iByte += size; Chris@559: } Chris@559: } Chris@559: } Chris@559: packet = MIDIPacketNext(packet); Chris@559: } Chris@559: } Chris@559: Chris@1397: MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: MidiInCore :: ~MidiInCore( void ) Chris@1397: { Chris@1397: // Close a connection if it exists. Chris@1397: closePort(); Chris@1397: Chris@1397: // Cleanup. Chris@1397: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@1397: MIDIClientDispose( data->client ); Chris@1397: if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); Chris@1397: delete data; Chris@1397: } Chris@1397: Chris@1397: void MidiInCore :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up our client. Chris@559: MIDIClientRef client; Chris@1397: CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); Chris@1397: OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); Chris@559: if ( result != noErr ) { Chris@1397: errorString_ = "MidiInCore::initialize: error creating OS-X MIDI client object."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: CoreMidiData *data = (CoreMidiData *) new CoreMidiData; Chris@559: data->client = client; Chris@559: data->endpoint = 0; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@1397: CFRelease(name); Chris@559: } Chris@559: Chris@1397: void MidiInCore :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@1397: errorString_ = "MidiInCore::openPort: a valid connection already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@1397: CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); Chris@559: unsigned int nSrc = MIDIGetNumberOfSources(); Chris@559: if (nSrc < 1) { Chris@1397: errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; Chris@1397: error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: if ( portNumber >= nSrc ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@1397: error( RtMidiError::INVALID_PARAMETER, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: MIDIPortRef port; Chris@559: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@565: OSStatus result = MIDIInputPortCreate( data->client, Chris@565: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@565: midiInputCallback, (void *)&inputData_, &port ); Chris@559: if ( result != noErr ) { Chris@559: MIDIClientDispose( data->client ); Chris@1397: errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Get the desired input source identifier. Chris@559: MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); Chris@1397: if ( endpoint == 0 ) { Chris@559: MIDIPortDispose( port ); Chris@559: MIDIClientDispose( data->client ); Chris@1397: errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Make the connection. Chris@559: result = MIDIPortConnectSource( port, endpoint, NULL ); Chris@559: if ( result != noErr ) { Chris@559: MIDIPortDispose( port ); Chris@559: MIDIClientDispose( data->client ); Chris@1397: errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Save our api-specific port information. Chris@559: data->port = port; Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@1397: void MidiInCore :: openVirtualPort( const std::string portName ) Chris@559: { Chris@559: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@559: Chris@559: // Create a virtual MIDI input destination. Chris@559: MIDIEndpointRef endpoint; Chris@559: OSStatus result = MIDIDestinationCreate( data->client, Chris@559: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@559: midiInputCallback, (void *)&inputData_, &endpoint ); Chris@559: if ( result != noErr ) { Chris@1397: errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: data->endpoint = endpoint; Chris@559: } Chris@559: Chris@1397: void MidiInCore :: closePort( void ) Chris@559: { Chris@1397: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@1397: Chris@1397: if ( data->endpoint ) { Chris@1397: MIDIEndpointDispose( data->endpoint ); Chris@1397: } Chris@1397: Chris@1397: if ( data->port ) { Chris@559: MIDIPortDispose( data->port ); Chris@559: } Chris@1397: Chris@1397: connected_ = false; Chris@559: } Chris@559: Chris@1397: unsigned int MidiInCore :: getPortCount() Chris@1397: { Chris@1397: CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); Chris@1397: return MIDIGetNumberOfSources(); Chris@1397: } Chris@1397: Chris@1397: // This function was submitted by Douglas Casey Tucker and apparently Chris@1397: // derived largely from PortMidi. Chris@1397: CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) Chris@1397: { Chris@1397: CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); Chris@1397: CFStringRef str; Chris@1397: Chris@1397: // Begin with the endpoint's name. Chris@1397: str = NULL; Chris@1397: MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); Chris@1397: if ( str != NULL ) { Chris@1397: CFStringAppend( result, str ); Chris@1397: CFRelease( str ); Chris@1397: } Chris@1397: Chris@1397: MIDIEntityRef entity = 0; Chris@1397: MIDIEndpointGetEntity( endpoint, &entity ); Chris@1397: if ( entity == 0 ) Chris@1397: // probably virtual Chris@1397: return result; Chris@1397: Chris@1397: if ( CFStringGetLength( result ) == 0 ) { Chris@1397: // endpoint name has zero length -- try the entity Chris@1397: str = NULL; Chris@1397: MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); Chris@1397: if ( str != NULL ) { Chris@1397: CFStringAppend( result, str ); Chris@1397: CFRelease( str ); Chris@1397: } Chris@1397: } Chris@1397: // now consider the device's name Chris@1397: MIDIDeviceRef device = 0; Chris@1397: MIDIEntityGetDevice( entity, &device ); Chris@1397: if ( device == 0 ) Chris@1397: return result; Chris@1397: Chris@1397: str = NULL; Chris@1397: MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); Chris@1397: if ( CFStringGetLength( result ) == 0 ) { Chris@1397: CFRelease( result ); Chris@1397: return str; Chris@1397: } Chris@1397: if ( str != NULL ) { Chris@1397: // if an external device has only one entity, throw away Chris@1397: // the endpoint name and just use the device name Chris@1397: if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { Chris@1397: CFRelease( result ); Chris@1397: return str; Chris@1397: } else { Chris@1397: if ( CFStringGetLength( str ) == 0 ) { Chris@1397: CFRelease( str ); Chris@1397: return result; Chris@1397: } Chris@1397: // does the entity name already start with the device name? Chris@1397: // (some drivers do this though they shouldn't) Chris@1397: // if so, do not prepend Chris@1397: if ( CFStringCompareWithOptions( result, /* endpoint name */ Chris@1397: str /* device name */, Chris@1397: CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { Chris@1397: // prepend the device name to the entity name Chris@1397: if ( CFStringGetLength( result ) > 0 ) Chris@1397: CFStringInsert( result, 0, CFSTR(" ") ); Chris@1397: CFStringInsert( result, 0, str ); Chris@1397: } Chris@1397: CFRelease( str ); Chris@1397: } Chris@1397: } Chris@1397: return result; Chris@1397: } Chris@1397: Chris@1397: // This function was submitted by Douglas Casey Tucker and apparently Chris@1397: // derived largely from PortMidi. Chris@1397: static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) Chris@1397: { Chris@1397: CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); Chris@1397: CFStringRef str; Chris@1397: OSStatus err; Chris@1397: int i; Chris@1397: Chris@1397: // Does the endpoint have connections? Chris@1397: CFDataRef connections = NULL; Chris@1397: int nConnected = 0; Chris@1397: bool anyStrings = false; Chris@1397: err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); Chris@1397: if ( connections != NULL ) { Chris@1397: // It has connections, follow them Chris@1397: // Concatenate the names of all connected devices Chris@1397: nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); Chris@1397: if ( nConnected ) { Chris@1397: const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); Chris@1397: for ( i=0; i<nConnected; ++i, ++pid ) { Chris@1397: MIDIUniqueID id = EndianS32_BtoN( *pid ); Chris@1397: MIDIObjectRef connObject; Chris@1397: MIDIObjectType connObjectType; Chris@1397: err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType ); Chris@1397: if ( err == noErr ) { Chris@1397: if ( connObjectType == kMIDIObjectType_ExternalSource || Chris@1397: connObjectType == kMIDIObjectType_ExternalDestination ) { Chris@1397: // Connected to an external device's endpoint (10.3 and later). Chris@1397: str = EndpointName( (MIDIEndpointRef)(connObject), true ); Chris@1397: } else { Chris@1397: // Connected to an external device (10.2) (or something else, catch- Chris@1397: str = NULL; Chris@1397: MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str ); Chris@1397: } Chris@1397: if ( str != NULL ) { Chris@1397: if ( anyStrings ) Chris@1397: CFStringAppend( result, CFSTR(", ") ); Chris@1397: else anyStrings = true; Chris@1397: CFStringAppend( result, str ); Chris@1397: CFRelease( str ); Chris@1397: } Chris@1397: } Chris@1397: } Chris@1397: } Chris@1397: CFRelease( connections ); Chris@1397: } Chris@1397: if ( anyStrings ) Chris@1397: return result; Chris@1397: Chris@1397: CFRelease( result ); Chris@1397: Chris@1397: // Here, either the endpoint had no connections, or we failed to obtain names Chris@1397: return EndpointName( endpoint, false ); Chris@1397: } Chris@1397: Chris@1397: std::string MidiInCore :: getPortName( unsigned int portNumber ) Chris@1397: { Chris@1397: CFStringRef nameRef; Chris@1397: MIDIEndpointRef portRef; Chris@1397: char name[128]; Chris@1397: Chris@1397: std::string stringName; Chris@1397: CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); Chris@1397: if ( portNumber >= MIDIGetNumberOfSources() ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@1397: errorString_ = ost.str(); Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: portRef = MIDIGetSource( portNumber ); Chris@1397: nameRef = ConnectedEndpointName(portRef); Chris@1397: CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); Chris@1397: CFRelease( nameRef ); Chris@1397: Chris@1397: return stringName = name; Chris@1397: } Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // API: OS-X Chris@1397: // Class Definitions: MidiOutCore Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi() Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: MidiOutCore :: ~MidiOutCore( void ) Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@559: MIDIClientDispose( data->client ); Chris@559: if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); Chris@559: delete data; Chris@559: } Chris@559: Chris@1397: void MidiOutCore :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up our client. Chris@559: MIDIClientRef client; Chris@1397: CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); Chris@1397: OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); Chris@559: if ( result != noErr ) { Chris@1397: errorString_ = "MidiOutCore::initialize: error creating OS-X MIDI client object."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: CoreMidiData *data = (CoreMidiData *) new CoreMidiData; Chris@559: data->client = client; Chris@559: data->endpoint = 0; Chris@559: apiData_ = (void *) data; Chris@1397: CFRelease( name ); Chris@559: } Chris@559: Chris@1397: unsigned int MidiOutCore :: getPortCount() Chris@1397: { Chris@1397: CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); Chris@1397: return MIDIGetNumberOfDestinations(); Chris@1397: } Chris@1397: Chris@1397: std::string MidiOutCore :: getPortName( unsigned int portNumber ) Chris@1397: { Chris@1397: CFStringRef nameRef; Chris@1397: MIDIEndpointRef portRef; Chris@1397: char name[128]; Chris@1397: Chris@1397: std::string stringName; Chris@1397: CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); Chris@1397: if ( portNumber >= MIDIGetNumberOfDestinations() ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@1397: errorString_ = ost.str(); Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: portRef = MIDIGetDestination( portNumber ); Chris@1397: nameRef = ConnectedEndpointName(portRef); Chris@1397: CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); Chris@1397: CFRelease( nameRef ); Chris@1397: Chris@1397: return stringName = name; Chris@1397: } Chris@1397: Chris@1397: void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@1397: errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@1397: CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); Chris@559: unsigned int nDest = MIDIGetNumberOfDestinations(); Chris@559: if (nDest < 1) { Chris@1397: errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; Chris@1397: error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: if ( portNumber >= nDest ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@1397: error( RtMidiError::INVALID_PARAMETER, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: MIDIPortRef port; Chris@559: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@565: OSStatus result = MIDIOutputPortCreate( data->client, Chris@565: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@565: &port ); Chris@559: if ( result != noErr ) { Chris@559: MIDIClientDispose( data->client ); Chris@1397: errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Get the desired output port identifier. Chris@559: MIDIEndpointRef destination = MIDIGetDestination( portNumber ); Chris@1397: if ( destination == 0 ) { Chris@559: MIDIPortDispose( port ); Chris@559: MIDIClientDispose( data->client ); Chris@1397: errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: data->port = port; Chris@559: data->destinationId = destination; Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@1397: void MidiOutCore :: closePort( void ) Chris@559: { Chris@559: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@559: Chris@559: if ( data->endpoint ) { Chris@1397: MIDIEndpointDispose( data->endpoint ); Chris@1397: } Chris@1397: Chris@1397: if ( data->port ) { Chris@1397: MIDIPortDispose( data->port ); Chris@1397: } Chris@1397: Chris@1397: connected_ = false; Chris@1397: } Chris@1397: Chris@1397: void MidiOutCore :: openVirtualPort( std::string portName ) Chris@1397: { Chris@1397: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@1397: Chris@1397: if ( data->endpoint ) { Chris@1397: errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: // Create a virtual MIDI output source. Chris@559: MIDIEndpointRef endpoint; Chris@559: OSStatus result = MIDISourceCreate( data->client, Chris@559: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@559: &endpoint ); Chris@559: if ( result != noErr ) { Chris@1397: errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: data->endpoint = endpoint; Chris@559: } Chris@559: Chris@1397: void MidiOutCore :: sendMessage( std::vector<unsigned char> *message ) Chris@559: { Chris@1397: // We use the MIDISendSysex() function to asynchronously send sysex Chris@1397: // messages. Otherwise, we use a single CoreMidi MIDIPacket. Chris@559: unsigned int nBytes = message->size(); Chris@565: if ( nBytes == 0 ) { Chris@1397: errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@565: return; Chris@565: } Chris@559: Chris@1397: MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); Chris@1397: CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); Chris@1397: OSStatus result; Chris@1397: Chris@1397: if ( message->at(0) != 0xF0 && nBytes > 3 ) { Chris@1397: errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@565: return; Chris@565: } Chris@565: Chris@1397: Byte buffer[nBytes+(sizeof(MIDIPacketList))]; Chris@1397: ByteCount listSize = sizeof(buffer); Chris@1397: MIDIPacketList *packetList = (MIDIPacketList*)buffer; Chris@1397: MIDIPacket *packet = MIDIPacketListInit( packetList ); Chris@1397: Chris@1397: ByteCount remainingBytes = nBytes; Chris@1397: while (remainingBytes && packet) { Chris@1397: ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket Chris@1397: const Byte* dataStartPtr = (const Byte *) &message->at( nBytes - remainingBytes ); Chris@1397: packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr); Chris@1397: remainingBytes -= bytesForPacket; Chris@1397: } Chris@1397: Chris@1397: if ( !packet ) { Chris@1397: errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: // Send to any destinations that may have connected to us. Chris@1397: if ( data->endpoint ) { Chris@1397: result = MIDIReceived( data->endpoint, packetList ); Chris@1397: if ( result != noErr ) { Chris@1397: errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: } Chris@1397: } Chris@1397: Chris@1397: // And send to an explicit destination port if we're connected. Chris@1397: if ( connected_ ) { Chris@1397: result = MIDISend( data->port, data->destinationId, packetList ); Chris@1397: if ( result != noErr ) { Chris@1397: errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@559: #endif // __MACOSX_CORE__ Chris@559: Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: LINUX ALSA SEQUENCER Chris@559: //*********************************************************************// Chris@559: Chris@559: // API information found at: Chris@559: // - http://www.alsa-project.org/documentation.php#Library Chris@559: Chris@1397: #if defined(__LINUX_ALSA__) Chris@559: Chris@559: // The ALSA Sequencer API is based on the use of a callback function for Chris@559: // MIDI input. Chris@559: // Chris@559: // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer Chris@559: // time stamps and other assorted fixes!!! Chris@559: Chris@1397: // If you don't need timestamping for incoming MIDI events, define the Chris@1397: // preprocessor definition AVOID_TIMESTAMPING to save resources Chris@1397: // associated with the ALSA sequencer queues. Chris@1397: Chris@559: #include <pthread.h> Chris@559: #include <sys/time.h> Chris@559: Chris@559: // ALSA header file. Chris@559: #include <alsa/asoundlib.h> Chris@559: Chris@559: // A structure to hold variables related to the ALSA API Chris@559: // implementation. Chris@559: struct AlsaMidiData { Chris@559: snd_seq_t *seq; Chris@1397: unsigned int portNum; Chris@559: int vport; Chris@559: snd_seq_port_subscribe_t *subscription; Chris@559: snd_midi_event_t *coder; Chris@559: unsigned int bufferSize; Chris@559: unsigned char *buffer; Chris@559: pthread_t thread; Chris@1397: pthread_t dummy_thread_id; Chris@559: unsigned long long lastTime; Chris@559: int queue_id; // an input queue is needed to get timestamped events Chris@1397: int trigger_fds[2]; Chris@559: }; Chris@559: Chris@559: #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: LINUX ALSA Chris@1397: // Class Definitions: MidiInAlsa Chris@559: //*********************************************************************// Chris@559: Chris@1397: static void *alsaMidiHandler( void *ptr ) Chris@559: { Chris@1397: MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr); Chris@559: AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData); Chris@559: Chris@559: long nBytes; Chris@559: unsigned long long time, lastTime; Chris@559: bool continueSysex = false; Chris@1397: bool doDecode = false; Chris@1397: MidiInApi::MidiMessage message; Chris@1397: int poll_fd_count; Chris@1397: struct pollfd *poll_fds; Chris@559: Chris@559: snd_seq_event_t *ev; Chris@559: int result; Chris@559: apiData->bufferSize = 32; Chris@559: result = snd_midi_event_new( 0, &apiData->coder ); Chris@559: if ( result < 0 ) { Chris@559: data->doInput = false; Chris@1397: std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; Chris@559: return 0; Chris@559: } Chris@559: unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); Chris@559: if ( buffer == NULL ) { Chris@559: data->doInput = false; Chris@1397: snd_midi_event_free( apiData->coder ); Chris@1397: apiData->coder = 0; Chris@1397: std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; Chris@559: return 0; Chris@559: } Chris@559: snd_midi_event_init( apiData->coder ); Chris@559: snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages Chris@559: Chris@1397: poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; Chris@1397: poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); Chris@1397: snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); Chris@1397: poll_fds[0].fd = apiData->trigger_fds[0]; Chris@1397: poll_fds[0].events = POLLIN; Chris@1397: Chris@559: while ( data->doInput ) { Chris@559: Chris@559: if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { Chris@1397: // No data pending Chris@1397: if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { Chris@1397: if ( poll_fds[0].revents & POLLIN ) { Chris@1397: bool dummy; Chris@1397: int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); Chris@1397: (void) res; Chris@1397: } Chris@1397: } Chris@559: continue; Chris@559: } Chris@559: Chris@559: // If here, there should be data. Chris@559: result = snd_seq_event_input( apiData->seq, &ev ); Chris@559: if ( result == -ENOSPC ) { Chris@1397: std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; Chris@559: continue; Chris@559: } Chris@559: else if ( result <= 0 ) { Chris@1397: std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; Chris@1397: perror("System reports"); Chris@559: continue; Chris@559: } Chris@559: Chris@559: // This is a bit weird, but we now have to decode an ALSA MIDI Chris@559: // event (back) into MIDI bytes. We'll ignore non-MIDI types. Chris@1397: if ( !continueSysex ) message.bytes.clear(); Chris@1397: Chris@1397: doDecode = false; Chris@559: switch ( ev->type ) { Chris@559: Chris@1397: case SND_SEQ_EVENT_PORT_SUBSCRIBED: Chris@559: #if defined(__RTMIDI_DEBUG__) Chris@1397: std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; Chris@559: #endif Chris@559: break; Chris@559: Chris@1397: case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: Chris@565: #if defined(__RTMIDI_DEBUG__) Chris@1397: std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; Chris@1397: std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" Chris@1397: << (int) ev->data.connect.sender.port Chris@1397: << ", dest = " << (int) ev->data.connect.dest.client << ":" Chris@1397: << (int) ev->data.connect.dest.port Chris@1397: << std::endl; Chris@565: #endif Chris@559: break; Chris@559: Chris@559: case SND_SEQ_EVENT_QFRAME: // MIDI time code Chris@1397: if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; Chris@1397: break; Chris@1397: Chris@1397: case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick Chris@1397: if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; Chris@1397: break; Chris@1397: Chris@1397: case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick Chris@1397: if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; Chris@1397: break; Chris@559: Chris@559: case SND_SEQ_EVENT_SENSING: // Active sensing Chris@1397: if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; Chris@1397: break; Chris@559: Chris@1426: case SND_SEQ_EVENT_SYSEX: Chris@559: if ( (data->ignoreFlags & 0x01) ) break; Chris@559: if ( ev->data.ext.len > apiData->bufferSize ) { Chris@559: apiData->bufferSize = ev->data.ext.len; Chris@559: free( buffer ); Chris@559: buffer = (unsigned char *) malloc( apiData->bufferSize ); Chris@559: if ( buffer == NULL ) { Chris@559: data->doInput = false; Chris@1397: std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; Chris@559: break; Chris@559: } Chris@559: } Chris@1426: doDecode = true; Chris@1426: break; Chris@559: Chris@559: default: Chris@1397: doDecode = true; Chris@1397: } Chris@1397: Chris@1397: if ( doDecode ) { Chris@1397: Chris@559: nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); Chris@1397: if ( nBytes > 0 ) { Chris@1397: // The ALSA sequencer has a maximum buffer size for MIDI sysex Chris@1397: // events of 256 bytes. If a device sends sysex messages larger Chris@1397: // than this, they are segmented into 256 byte chunks. So, Chris@1397: // we'll watch for this and concatenate sysex chunks into a Chris@1397: // single sysex message if necessary. Chris@1397: if ( !continueSysex ) Chris@1397: message.bytes.assign( buffer, &buffer[nBytes] ); Chris@1397: else Chris@1397: message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); Chris@1397: Chris@1397: continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); Chris@1397: if ( !continueSysex ) { Chris@1397: Chris@1397: // Calculate the time stamp: Chris@1397: message.timeStamp = 0.0; Chris@1397: Chris@1397: // Method 1: Use the system time. Chris@1397: //(void)gettimeofday(&tv, (struct timezone *)NULL); Chris@1397: //time = (tv.tv_sec * 1000000) + tv.tv_usec; Chris@1397: Chris@1397: // Method 2: Use the ALSA sequencer event time data. Chris@1397: // (thanks to Pedro Lopez-Cabanillas!). Chris@1397: time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 ); Chris@1397: lastTime = time; Chris@1397: time -= apiData->lastTime; Chris@1397: apiData->lastTime = lastTime; Chris@1397: if ( data->firstMessage == true ) Chris@1397: data->firstMessage = false; Chris@1397: else Chris@1397: message.timeStamp = time * 0.000001; Chris@1397: } Chris@1397: else { Chris@559: #if defined(__RTMIDI_DEBUG__) Chris@1397: std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; Chris@559: #endif Chris@1397: } Chris@559: } Chris@559: } Chris@559: Chris@1397: snd_seq_free_event( ev ); Chris@1397: if ( message.bytes.size() == 0 || continueSysex ) continue; Chris@1397: Chris@1397: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@1397: if ( data->queue.size < data->queue.ringSize ) { Chris@1397: data->queue.ring[data->queue.back++] = message; Chris@1397: if ( data->queue.back == data->queue.ringSize ) Chris@1397: data->queue.back = 0; Chris@1397: data->queue.size++; Chris@1397: } Chris@559: else Chris@1397: std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; Chris@559: } Chris@559: } Chris@559: Chris@559: if ( buffer ) free( buffer ); Chris@559: snd_midi_event_free( apiData->coder ); Chris@559: apiData->coder = 0; Chris@1397: apiData->thread = apiData->dummy_thread_id; Chris@559: return 0; Chris@559: } Chris@559: Chris@1397: MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: MidiInAlsa :: ~MidiInAlsa() Chris@1397: { Chris@1397: // Close a connection if it exists. Chris@1397: closePort(); Chris@1397: Chris@1397: // Shutdown the input thread. Chris@1397: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: if ( inputData_.doInput ) { Chris@1397: inputData_.doInput = false; Chris@1397: int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); Chris@1397: (void) res; Chris@1397: if ( !pthread_equal(data->thread, data->dummy_thread_id) ) Chris@1397: pthread_join( data->thread, NULL ); Chris@1397: } Chris@1397: Chris@1397: // Cleanup. Chris@1397: close ( data->trigger_fds[0] ); Chris@1397: close ( data->trigger_fds[1] ); Chris@1397: if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@1397: snd_seq_free_queue( data->seq, data->queue_id ); Chris@1397: #endif Chris@1397: snd_seq_close( data->seq ); Chris@1397: delete data; Chris@1397: } Chris@1397: Chris@1397: void MidiInAlsa :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up the ALSA sequencer client. Chris@565: snd_seq_t *seq; Chris@559: int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); Chris@559: if ( result < 0 ) { Chris@1397: errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@559: Chris@559: // Set client name. Chris@565: snd_seq_set_client_name( seq, clientName.c_str() ); Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; Chris@559: data->seq = seq; Chris@1397: data->portNum = -1; Chris@559: data->vport = -1; Chris@1397: data->subscription = 0; Chris@1397: data->dummy_thread_id = pthread_self(); Chris@1397: data->thread = data->dummy_thread_id; Chris@1397: data->trigger_fds[0] = -1; Chris@1397: data->trigger_fds[1] = -1; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@559: Chris@1397: if ( pipe(data->trigger_fds) == -1 ) { Chris@1397: errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@559: // Create the input queue Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@559: data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); Chris@559: // Set arbitrary tempo (mm=100) and resolution (240) Chris@559: snd_seq_queue_tempo_t *qtempo; Chris@559: snd_seq_queue_tempo_alloca(&qtempo); Chris@559: snd_seq_queue_tempo_set_tempo(qtempo, 600000); Chris@559: snd_seq_queue_tempo_set_ppq(qtempo, 240); Chris@559: snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); Chris@559: snd_seq_drain_output(data->seq); Chris@1397: #endif Chris@559: } Chris@559: Chris@559: // This function is used to count or get the pinfo structure for a given port number. Chris@559: unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) Chris@559: { Chris@1397: snd_seq_client_info_t *cinfo; Chris@559: int client; Chris@559: int count = 0; Chris@1397: snd_seq_client_info_alloca( &cinfo ); Chris@1397: Chris@1397: snd_seq_client_info_set_client( cinfo, -1 ); Chris@1397: while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { Chris@559: client = snd_seq_client_info_get_client( cinfo ); Chris@559: if ( client == 0 ) continue; Chris@1397: // Reset query info Chris@1397: snd_seq_port_info_set_client( pinfo, client ); Chris@1397: snd_seq_port_info_set_port( pinfo, -1 ); Chris@1397: while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { Chris@565: unsigned int atyp = snd_seq_port_info_get_type( pinfo ); Chris@1397: if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && Chris@1397: ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) ) continue; Chris@565: unsigned int caps = snd_seq_port_info_get_capability( pinfo ); Chris@565: if ( ( caps & type ) != type ) continue; Chris@559: if ( count == portNumber ) return 1; Chris@1397: ++count; Chris@1397: } Chris@1397: } Chris@559: Chris@559: // If a negative portNumber was used, return the port count. Chris@559: if ( portNumber < 0 ) return count; Chris@559: return 0; Chris@559: } Chris@559: Chris@1397: unsigned int MidiInAlsa :: getPortCount() Chris@1397: { Chris@1397: snd_seq_port_info_t *pinfo; Chris@1397: snd_seq_port_info_alloca( &pinfo ); Chris@1397: Chris@1397: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); Chris@1397: } Chris@1397: Chris@1397: std::string MidiInAlsa :: getPortName( unsigned int portNumber ) Chris@1397: { Chris@1397: snd_seq_client_info_t *cinfo; Chris@1397: snd_seq_port_info_t *pinfo; Chris@1397: snd_seq_client_info_alloca( &cinfo ); Chris@1397: snd_seq_port_info_alloca( &pinfo ); Chris@1397: Chris@1397: std::string stringName; Chris@1397: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { Chris@1397: int cnum = snd_seq_port_info_get_client( pinfo ); Chris@1397: snd_seq_get_any_client_info( data->seq, cnum, cinfo ); Chris@1397: std::ostringstream os; Chris@1397: os << snd_seq_client_info_get_name( cinfo ); Chris@1397: os << " "; // These lines added to make sure devices are listed Chris@1397: os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names Chris@1397: os << ":"; Chris@1397: os << snd_seq_port_info_get_port( pinfo ); Chris@1397: stringName = os.str(); Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: // If we get here, we didn't find a match. Chris@1397: errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@1397: errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nSrc = this->getPortCount(); Chris@1397: if ( nSrc < 1 ) { Chris@1397: errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; Chris@1397: error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@1397: snd_seq_port_info_t *src_pinfo; Chris@1397: snd_seq_port_info_alloca( &src_pinfo ); Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@1397: error( RtMidiError::INVALID_PARAMETER, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: snd_seq_addr_t sender, receiver; Chris@1397: sender.client = snd_seq_port_info_get_client( src_pinfo ); Chris@1397: sender.port = snd_seq_port_info_get_port( src_pinfo ); Chris@1397: receiver.client = snd_seq_client_id( data->seq ); Chris@1397: Chris@1397: snd_seq_port_info_t *pinfo; Chris@1397: snd_seq_port_info_alloca( &pinfo ); Chris@559: if ( data->vport < 0 ) { Chris@559: snd_seq_port_info_set_client( pinfo, 0 ); Chris@559: snd_seq_port_info_set_port( pinfo, 0 ); Chris@559: snd_seq_port_info_set_capability( pinfo, Chris@559: SND_SEQ_PORT_CAP_WRITE | Chris@559: SND_SEQ_PORT_CAP_SUBS_WRITE ); Chris@559: snd_seq_port_info_set_type( pinfo, Chris@559: SND_SEQ_PORT_TYPE_MIDI_GENERIC | Chris@559: SND_SEQ_PORT_TYPE_APPLICATION ); Chris@559: snd_seq_port_info_set_midi_channels(pinfo, 16); Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@559: snd_seq_port_info_set_timestamping(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_real(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); Chris@1397: #endif Chris@565: snd_seq_port_info_set_name(pinfo, portName.c_str() ); Chris@559: data->vport = snd_seq_create_port(data->seq, pinfo); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@1397: errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: data->vport = snd_seq_port_info_get_port(pinfo); Chris@1397: } Chris@1397: Chris@1397: receiver.port = data->vport; Chris@1397: Chris@1397: if ( !data->subscription ) { Chris@1397: // Make subscription Chris@1397: if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { Chris@1397: errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: snd_seq_port_subscribe_set_sender(data->subscription, &sender); Chris@1397: snd_seq_port_subscribe_set_dest(data->subscription, &receiver); Chris@1397: if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { Chris@1397: snd_seq_port_subscribe_free( data->subscription ); Chris@1397: data->subscription = 0; Chris@1397: errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: } Chris@559: Chris@559: if ( inputData_.doInput == false ) { Chris@559: // Start the input queue Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@559: snd_seq_start_queue( data->seq, data->queue_id, NULL ); Chris@559: snd_seq_drain_output( data->seq ); Chris@1397: #endif Chris@559: // Start our MIDI input thread. Chris@559: pthread_attr_t attr; Chris@559: pthread_attr_init(&attr); Chris@559: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); Chris@559: pthread_attr_setschedpolicy(&attr, SCHED_OTHER); Chris@559: Chris@559: inputData_.doInput = true; Chris@559: int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); Chris@559: pthread_attr_destroy(&attr); Chris@1397: if ( err ) { Chris@559: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@559: snd_seq_port_subscribe_free( data->subscription ); Chris@1397: data->subscription = 0; Chris@559: inputData_.doInput = false; Chris@1397: errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; Chris@1397: error( RtMidiError::THREAD_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@1397: void MidiInAlsa :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@559: if ( data->vport < 0 ) { Chris@559: snd_seq_port_info_t *pinfo; Chris@559: snd_seq_port_info_alloca( &pinfo ); Chris@559: snd_seq_port_info_set_capability( pinfo, Chris@1429: SND_SEQ_PORT_CAP_WRITE | Chris@1429: SND_SEQ_PORT_CAP_SUBS_WRITE ); Chris@559: snd_seq_port_info_set_type( pinfo, Chris@1429: SND_SEQ_PORT_TYPE_MIDI_GENERIC | Chris@1429: SND_SEQ_PORT_TYPE_APPLICATION ); Chris@559: snd_seq_port_info_set_midi_channels(pinfo, 16); Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@559: snd_seq_port_info_set_timestamping(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_real(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); Chris@1397: #endif Chris@559: snd_seq_port_info_set_name(pinfo, portName.c_str()); Chris@559: data->vport = snd_seq_create_port(data->seq, pinfo); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@1397: errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@1397: data->vport = snd_seq_port_info_get_port(pinfo); Chris@559: } Chris@559: Chris@559: if ( inputData_.doInput == false ) { Chris@1397: // Wait for old thread to stop, if still running Chris@1397: if ( !pthread_equal(data->thread, data->dummy_thread_id) ) Chris@1397: pthread_join( data->thread, NULL ); Chris@1397: Chris@559: // Start the input queue Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@559: snd_seq_start_queue( data->seq, data->queue_id, NULL ); Chris@559: snd_seq_drain_output( data->seq ); Chris@1397: #endif Chris@559: // Start our MIDI input thread. Chris@559: pthread_attr_t attr; Chris@559: pthread_attr_init(&attr); Chris@559: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); Chris@559: pthread_attr_setschedpolicy(&attr, SCHED_OTHER); Chris@559: Chris@559: inputData_.doInput = true; Chris@559: int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); Chris@559: pthread_attr_destroy(&attr); Chris@1397: if ( err ) { Chris@1397: if ( data->subscription ) { Chris@1397: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@1397: snd_seq_port_subscribe_free( data->subscription ); Chris@1397: data->subscription = 0; Chris@1397: } Chris@559: inputData_.doInput = false; Chris@1397: errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; Chris@1397: error( RtMidiError::THREAD_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@1397: void MidiInAlsa :: closePort( void ) Chris@559: { Chris@1397: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: Chris@559: if ( connected_ ) { Chris@1397: if ( data->subscription ) { Chris@1397: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@1397: snd_seq_port_subscribe_free( data->subscription ); Chris@1397: data->subscription = 0; Chris@1397: } Chris@559: // Stop the input queue Chris@1397: #ifndef AVOID_TIMESTAMPING Chris@559: snd_seq_stop_queue( data->seq, data->queue_id, NULL ); Chris@559: snd_seq_drain_output( data->seq ); Chris@1397: #endif Chris@559: connected_ = false; Chris@559: } Chris@1397: Chris@1397: // Stop thread to avoid triggering the callback, while the port is intended to be closed Chris@1397: if ( inputData_.doInput ) { Chris@1397: inputData_.doInput = false; Chris@1397: int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); Chris@1397: (void) res; Chris@1397: if ( !pthread_equal(data->thread, data->dummy_thread_id) ) Chris@1397: pthread_join( data->thread, NULL ); Chris@1397: } Chris@559: } Chris@559: Chris@1397: //*********************************************************************// Chris@1397: // API: LINUX ALSA Chris@1397: // Class Definitions: MidiOutAlsa Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi() Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: MidiOutAlsa :: ~MidiOutAlsa() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@1397: // Cleanup. Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@559: if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); Chris@1397: if ( data->coder ) snd_midi_event_free( data->coder ); Chris@1397: if ( data->buffer ) free( data->buffer ); Chris@559: snd_seq_close( data->seq ); Chris@559: delete data; Chris@559: } Chris@559: Chris@1397: void MidiOutAlsa :: initialize( const std::string& clientName ) Chris@1397: { Chris@1397: // Set up the ALSA sequencer client. Chris@1397: snd_seq_t *seq; Chris@1397: int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); Chris@1397: if ( result1 < 0 ) { Chris@1397: errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1429: } Chris@1397: Chris@1397: // Set client name. Chris@1397: snd_seq_set_client_name( seq, clientName.c_str() ); Chris@1397: Chris@1397: // Save our api-specific connection information. Chris@1397: AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; Chris@1397: data->seq = seq; Chris@1397: data->portNum = -1; Chris@1397: data->vport = -1; Chris@1397: data->bufferSize = 32; Chris@1397: data->coder = 0; Chris@1397: data->buffer = 0; Chris@1397: int result = snd_midi_event_new( data->bufferSize, &data->coder ); Chris@1397: if ( result < 0 ) { Chris@1397: delete data; Chris@1397: errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: data->buffer = (unsigned char *) malloc( data->bufferSize ); Chris@1397: if ( data->buffer == NULL ) { Chris@1397: delete data; Chris@1397: errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; Chris@1397: error( RtMidiError::MEMORY_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: snd_midi_event_init( data->coder ); Chris@1397: apiData_ = (void *) data; Chris@1397: } Chris@1397: Chris@1397: unsigned int MidiOutAlsa :: getPortCount() Chris@559: { Chris@1429: snd_seq_port_info_t *pinfo; Chris@1429: snd_seq_port_info_alloca( &pinfo ); Chris@559: Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); Chris@559: } Chris@559: Chris@1397: std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) Chris@559: { Chris@565: snd_seq_client_info_t *cinfo; Chris@565: snd_seq_port_info_t *pinfo; Chris@565: snd_seq_client_info_alloca( &cinfo ); Chris@565: snd_seq_port_info_alloca( &pinfo ); Chris@559: Chris@1397: std::string stringName; Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@559: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { Chris@565: int cnum = snd_seq_port_info_get_client(pinfo); Chris@565: snd_seq_get_any_client_info( data->seq, cnum, cinfo ); Chris@565: std::ostringstream os; Chris@565: os << snd_seq_client_info_get_name(cinfo); Chris@1397: os << " "; // These lines added to make sure devices are listed Chris@1397: os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names Chris@565: os << ":"; Chris@565: os << snd_seq_port_info_get_port(pinfo); Chris@1397: stringName = os.str(); Chris@559: return stringName; Chris@559: } Chris@559: Chris@559: // If we get here, we didn't find a match. Chris@1397: errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return stringName; Chris@559: } Chris@559: Chris@1397: void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@1397: errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nSrc = this->getPortCount(); Chris@559: if (nSrc < 1) { Chris@1397: errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; Chris@1397: error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@1429: snd_seq_port_info_t *pinfo; Chris@1429: snd_seq_port_info_alloca( &pinfo ); Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@559: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@1397: error( RtMidiError::INVALID_PARAMETER, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: snd_seq_addr_t sender, receiver; Chris@1397: receiver.client = snd_seq_port_info_get_client( pinfo ); Chris@1397: receiver.port = snd_seq_port_info_get_port( pinfo ); Chris@1397: sender.client = snd_seq_client_id( data->seq ); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@565: data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), Chris@559: SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, Chris@1397: SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); Chris@559: if ( data->vport < 0 ) { Chris@1397: errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: } Chris@559: Chris@1397: sender.port = data->vport; Chris@559: Chris@559: // Make subscription Chris@1397: if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { Chris@1397: snd_seq_port_subscribe_free( data->subscription ); Chris@1397: errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@559: snd_seq_port_subscribe_set_sender(data->subscription, &sender); Chris@559: snd_seq_port_subscribe_set_dest(data->subscription, &receiver); Chris@559: snd_seq_port_subscribe_set_time_update(data->subscription, 1); Chris@559: snd_seq_port_subscribe_set_time_real(data->subscription, 1); Chris@559: if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { Chris@1397: snd_seq_port_subscribe_free( data->subscription ); Chris@1397: errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@1397: void MidiOutAlsa :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@559: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@559: snd_seq_port_subscribe_free( data->subscription ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@1397: void MidiOutAlsa :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@559: if ( data->vport < 0 ) { Chris@559: data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), Chris@559: SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, Chris@1397: SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@1397: errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@1397: void MidiOutAlsa :: sendMessage( std::vector<unsigned char> *message ) Chris@559: { Chris@559: int result; Chris@559: AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); Chris@1397: unsigned int nBytes = message->size(); Chris@559: if ( nBytes > data->bufferSize ) { Chris@559: data->bufferSize = nBytes; Chris@559: result = snd_midi_event_resize_buffer ( data->coder, nBytes); Chris@559: if ( result != 0 ) { Chris@1397: errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: free (data->buffer); Chris@559: data->buffer = (unsigned char *) malloc( data->bufferSize ); Chris@559: if ( data->buffer == NULL ) { Chris@1397: errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; Chris@1397: error( RtMidiError::MEMORY_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: } Chris@559: Chris@559: snd_seq_event_t ev; Chris@559: snd_seq_ev_clear(&ev); Chris@1397: snd_seq_ev_set_source(&ev, data->vport); Chris@559: snd_seq_ev_set_subs(&ev); Chris@559: snd_seq_ev_set_direct(&ev); Chris@1397: for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message->at(i); Chris@1397: result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); Chris@559: if ( result < (int)nBytes ) { Chris@1397: errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: // Send the event. Chris@559: result = snd_seq_event_output(data->seq, &ev); Chris@559: if ( result < 0 ) { Chris@1397: errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: snd_seq_drain_output(data->seq); Chris@559: } Chris@559: Chris@559: #endif // __LINUX_ALSA__ Chris@559: Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: Windows Multimedia Library (MM) Chris@559: //*********************************************************************// Chris@559: Chris@559: // API information deciphered from: Chris@559: // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp Chris@559: Chris@559: // Thanks to Jean-Baptiste Berruchon for the sysex code. Chris@559: Chris@559: #if defined(__WINDOWS_MM__) Chris@559: Chris@559: // The Windows MM API is based on the use of a callback function for Chris@559: // MIDI input. We convert the system specific time stamps to delta Chris@559: // time values. Chris@559: Chris@559: // Windows MM MIDI header files. Chris@559: #include <windows.h> Chris@559: #include <mmsystem.h> Chris@559: Chris@1397: #define RT_SYSEX_BUFFER_SIZE 1024 Chris@1397: #define RT_SYSEX_BUFFER_COUNT 4 Chris@1397: Chris@559: // A structure to hold variables related to the CoreMIDI API Chris@559: // implementation. Chris@559: struct WinMidiData { Chris@559: HMIDIIN inHandle; // Handle to Midi Input Device Chris@559: HMIDIOUT outHandle; // Handle to Midi Output Device Chris@559: DWORD lastTime; Chris@1397: MidiInApi::MidiMessage message; Chris@1397: LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; Chris@1397: CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo Chris@559: }; Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: Windows MM Chris@1397: // Class Definitions: MidiInWinMM Chris@559: //*********************************************************************// Chris@559: Chris@1397: static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, Chris@559: UINT inputStatus, Chris@1397: DWORD_PTR instancePtr, Chris@1397: DWORD_PTR midiMessage, Chris@559: DWORD timestamp ) Chris@559: { Chris@1397: if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; Chris@1397: Chris@1397: //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr); Chris@1397: MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; Chris@559: WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData); Chris@559: Chris@559: // Calculate time stamp. Chris@1397: if ( data->firstMessage == true ) { Chris@1397: apiData->message.timeStamp = 0.0; Chris@1397: data->firstMessage = false; Chris@1397: } Chris@559: else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; Chris@559: apiData->lastTime = timestamp; Chris@559: Chris@559: if ( inputStatus == MIM_DATA ) { // Channel or system message Chris@559: Chris@559: // Make sure the first byte is a status byte. Chris@559: unsigned char status = (unsigned char) (midiMessage & 0x000000FF); Chris@559: if ( !(status & 0x80) ) return; Chris@559: Chris@559: // Determine the number of bytes in the MIDI message. Chris@559: unsigned short nBytes = 1; Chris@559: if ( status < 0xC0 ) nBytes = 3; Chris@559: else if ( status < 0xE0 ) nBytes = 2; Chris@559: else if ( status < 0xF0 ) nBytes = 3; Chris@1397: else if ( status == 0xF1 ) { Chris@1397: if ( data->ignoreFlags & 0x02 ) return; Chris@1397: else nBytes = 2; Chris@559: } Chris@1397: else if ( status == 0xF2 ) nBytes = 3; Chris@559: else if ( status == 0xF3 ) nBytes = 2; Chris@559: else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) { Chris@559: // A MIDI timing tick message and we're ignoring it. Chris@559: return; Chris@559: } Chris@559: else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { Chris@559: // A MIDI active sensing message and we're ignoring it. Chris@559: return; Chris@559: } Chris@559: Chris@559: // Copy bytes to our MIDI message. Chris@559: unsigned char *ptr = (unsigned char *) &midiMessage; Chris@1397: for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ ); Chris@559: } Chris@1397: else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) Chris@565: MIDIHDR *sysex = ( MIDIHDR *) midiMessage; Chris@1397: if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { Chris@565: // Sysex message and we're not ignoring it Chris@1397: for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) Chris@565: apiData->message.bytes.push_back( sysex->lpData[i] ); Chris@565: } Chris@559: Chris@565: // The WinMM API requires that the sysex buffer be requeued after Chris@565: // input of each sysex message. Even if we are ignoring sysex Chris@565: // messages, we still need to requeue the buffer in case the user Chris@565: // decides to not ignore sysex messages in the future. However, Chris@565: // it seems that WinMM calls this function with an empty sysex Chris@565: // buffer when an application closes and in this case, we should Chris@565: // avoid requeueing it, else the computer suddenly reboots after Chris@565: // one or two minutes. Chris@1397: if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { Chris@565: //if ( sysex->dwBytesRecorded > 0 ) { Chris@1397: EnterCriticalSection( &(apiData->_mutex) ); Chris@1397: MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); Chris@1397: LeaveCriticalSection( &(apiData->_mutex) ); Chris@559: if ( result != MMSYSERR_NOERROR ) Chris@1397: std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; Chris@565: Chris@565: if ( data->ignoreFlags & 0x01 ) return; Chris@559: } Chris@565: else return; Chris@559: } Chris@559: Chris@559: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@1397: if ( data->queue.size < data->queue.ringSize ) { Chris@1397: data->queue.ring[data->queue.back++] = apiData->message; Chris@1397: if ( data->queue.back == data->queue.ringSize ) Chris@1397: data->queue.back = 0; Chris@1397: data->queue.size++; Chris@1397: } Chris@559: else Chris@1397: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: Chris@565: // Clear the vector for the next input message. Chris@559: apiData->message.bytes.clear(); Chris@559: } Chris@559: Chris@1397: MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: MidiInWinMM :: ~MidiInWinMM() Chris@1397: { Chris@1397: // Close a connection if it exists. Chris@1397: closePort(); Chris@1397: Chris@1397: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@1397: DeleteCriticalSection( &(data->_mutex) ); Chris@1397: Chris@1397: // Cleanup. Chris@1397: delete data; Chris@1397: } Chris@1397: Chris@1397: void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) Chris@559: { Chris@559: // We'll issue a warning here if no devices are available but not Chris@559: // throw an error since the user can plugin something later. Chris@559: unsigned int nDevices = midiInGetNumDevs(); Chris@559: if ( nDevices == 0 ) { Chris@1397: errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: WinMidiData *data = (WinMidiData *) new WinMidiData; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@559: data->message.bytes.clear(); // needs to be empty for first input message Chris@1397: Chris@1397: if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) { Chris@1397: errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: } Chris@559: } Chris@559: Chris@1397: void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) Chris@559: { Chris@559: if ( connected_ ) { Chris@1397: errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nDevices = midiInGetNumDevs(); Chris@559: if (nDevices == 0) { Chris@1397: errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; Chris@1397: error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: if ( portNumber >= nDevices ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@1397: error( RtMidiError::INVALID_PARAMETER, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@559: MMRESULT result = midiInOpen( &data->inHandle, Chris@559: portNumber, Chris@1397: (DWORD_PTR)&midiInputCallback, Chris@1397: (DWORD_PTR)&inputData_, Chris@559: CALLBACK_FUNCTION ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@1397: errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@1397: // Allocate and init the sysex buffers. Chris@1397: for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) { Chris@1397: data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; Chris@1397: data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ]; Chris@1397: data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; Chris@1397: data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator Chris@1397: data->sysexBuffer[i]->dwFlags = 0; Chris@1397: Chris@1397: result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); Chris@1397: if ( result != MMSYSERR_NOERROR ) { Chris@1397: midiInClose( data->inHandle ); Chris@1397: errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: // Register the buffer. Chris@1397: result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); Chris@1397: if ( result != MMSYSERR_NOERROR ) { Chris@1397: midiInClose( data->inHandle ); Chris@1397: errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@559: } Chris@559: Chris@559: result = midiInStart( data->inHandle ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: midiInClose( data->inHandle ); Chris@1397: errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@1397: void MidiInWinMM :: openVirtualPort( std::string /*portName*/ ) Chris@559: { Chris@559: // This function cannot be implemented for the Windows MM MIDI API. Chris@1397: errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: } Chris@559: Chris@1397: void MidiInWinMM :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@1397: EnterCriticalSection( &(data->_mutex) ); Chris@559: midiInReset( data->inHandle ); Chris@559: midiInStop( data->inHandle ); Chris@559: Chris@1397: for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) { Chris@1397: int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); Chris@1397: delete [] data->sysexBuffer[i]->lpData; Chris@1397: delete [] data->sysexBuffer[i]; Chris@1397: if ( result != MMSYSERR_NOERROR ) { Chris@1397: midiInClose( data->inHandle ); Chris@1397: errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@559: } Chris@559: Chris@559: midiInClose( data->inHandle ); Chris@559: connected_ = false; Chris@1397: LeaveCriticalSection( &(data->_mutex) ); Chris@559: } Chris@559: } Chris@559: Chris@1397: unsigned int MidiInWinMM :: getPortCount() Chris@1397: { Chris@1397: return midiInGetNumDevs(); Chris@1397: } Chris@1397: Chris@1397: std::string MidiInWinMM :: getPortName( unsigned int portNumber ) Chris@1397: { Chris@1397: std::string stringName; Chris@1397: unsigned int nDevices = midiInGetNumDevs(); Chris@1397: if ( portNumber >= nDevices ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@1397: errorString_ = ost.str(); Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: MIDIINCAPS deviceCaps; Chris@1397: midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); Chris@1397: Chris@1397: #if defined( UNICODE ) || defined( _UNICODE ) Chris@1397: int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; Chris@1397: stringName.assign( length, 0 ); Chris@1397: length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); Chris@1397: #else Chris@1397: stringName = std::string( deviceCaps.szPname ); Chris@1397: #endif Chris@1397: Chris@1397: // Next lines added to add the portNumber to the name so that Chris@1397: // the device's names are sure to be listed with individual names Chris@1397: // even when they have the same brand name Chris@1397: std::ostringstream os; Chris@1397: os << " "; Chris@1397: os << portNumber; Chris@1397: stringName += os.str(); Chris@1397: Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // API: Windows MM Chris@1397: // Class Definitions: MidiOutWinMM Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi() Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: MidiOutWinMM :: ~MidiOutWinMM() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@559: delete data; Chris@559: } Chris@559: Chris@1397: void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) Chris@559: { Chris@559: // We'll issue a warning here if no devices are available but not Chris@559: // throw an error since the user can plug something in later. Chris@559: unsigned int nDevices = midiOutGetNumDevs(); Chris@559: if ( nDevices == 0 ) { Chris@1397: errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: WinMidiData *data = (WinMidiData *) new WinMidiData; Chris@559: apiData_ = (void *) data; Chris@559: } Chris@559: Chris@1397: unsigned int MidiOutWinMM :: getPortCount() Chris@1397: { Chris@1397: return midiOutGetNumDevs(); Chris@1397: } Chris@1397: Chris@1397: std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) Chris@1397: { Chris@1397: std::string stringName; Chris@1397: unsigned int nDevices = midiOutGetNumDevs(); Chris@1397: if ( portNumber >= nDevices ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@1397: errorString_ = ost.str(); Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: MIDIOUTCAPS deviceCaps; Chris@1397: midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); Chris@1397: Chris@1397: #if defined( UNICODE ) || defined( _UNICODE ) Chris@1397: int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; Chris@1397: stringName.assign( length, 0 ); Chris@1397: length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); Chris@1397: #else Chris@1397: stringName = std::string( deviceCaps.szPname ); Chris@1397: #endif Chris@1397: Chris@1397: // Next lines added to add the portNumber to the name so that Chris@1397: // the device's names are sure to be listed with individual names Chris@1397: // even when they have the same brand name Chris@1397: std::ostringstream os; Chris@1397: os << " "; Chris@1397: os << portNumber; Chris@1397: stringName += os.str(); Chris@1397: Chris@1397: return stringName; Chris@1397: } Chris@1397: Chris@1397: void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) Chris@559: { Chris@559: if ( connected_ ) { Chris@1397: errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nDevices = midiOutGetNumDevs(); Chris@559: if (nDevices < 1) { Chris@1397: errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; Chris@1397: error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: if ( portNumber >= nDevices ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@1397: error( RtMidiError::INVALID_PARAMETER, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@559: MMRESULT result = midiOutOpen( &data->outHandle, Chris@559: portNumber, Chris@559: (DWORD)NULL, Chris@559: (DWORD)NULL, Chris@559: CALLBACK_NULL ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@1397: errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@1397: void MidiOutWinMM :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@559: midiOutReset( data->outHandle ); Chris@559: midiOutClose( data->outHandle ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@1397: void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ ) Chris@559: { Chris@559: // This function cannot be implemented for the Windows MM MIDI API. Chris@1397: errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: } Chris@559: Chris@1397: void MidiOutWinMM :: sendMessage( std::vector<unsigned char> *message ) Chris@559: { Chris@1397: if ( !connected_ ) return; Chris@1397: Chris@1397: unsigned int nBytes = static_cast<unsigned int>(message->size()); Chris@559: if ( nBytes == 0 ) { Chris@1397: errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: MMRESULT result; Chris@559: WinMidiData *data = static_cast<WinMidiData *> (apiData_); Chris@559: if ( message->at(0) == 0xF0 ) { // Sysex message Chris@559: Chris@559: // Allocate buffer for sysex data. Chris@559: char *buffer = (char *) malloc( nBytes ); Chris@559: if ( buffer == NULL ) { Chris@1397: errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; Chris@1397: error( RtMidiError::MEMORY_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Copy data to buffer. Chris@1397: for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message->at(i); Chris@559: Chris@559: // Create and prepare MIDIHDR structure. Chris@559: MIDIHDR sysex; Chris@559: sysex.lpData = (LPSTR) buffer; Chris@559: sysex.dwBufferLength = nBytes; Chris@559: sysex.dwFlags = 0; Chris@559: result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: free( buffer ); Chris@1397: errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Send the message. Chris@559: result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: free( buffer ); Chris@1397: errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@559: } Chris@559: Chris@559: // Unprepare the buffer and MIDIHDR. Chris@559: while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); Chris@559: free( buffer ); Chris@559: } Chris@559: else { // Channel or system message. Chris@559: Chris@559: // Make sure the message size isn't too big. Chris@559: if ( nBytes > 3 ) { Chris@1397: errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@559: return; Chris@559: } Chris@559: Chris@559: // Pack MIDI bytes into double word. Chris@559: DWORD packet; Chris@559: unsigned char *ptr = (unsigned char *) &packet; Chris@1397: for ( unsigned int i=0; i<nBytes; ++i ) { Chris@559: *ptr = message->at(i); Chris@1397: ++ptr; Chris@559: } Chris@559: Chris@559: // Send the message immediately. Chris@559: result = midiOutShortMsg( data->outHandle, packet ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@1397: errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@559: #endif // __WINDOWS_MM__ Chris@609: Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // API: UNIX JACK Chris@1397: // Chris@1397: // Written primarily by Alexander Svetalkin, with updates for delta Chris@1397: // time by Gary Scavone, April 2011. Chris@1397: // Chris@1397: // *********************************************************************// Chris@1397: Chris@1397: #if defined(__UNIX_JACK__) Chris@1397: Chris@1397: // JACK header files Chris@1397: #include <jack/jack.h> Chris@1397: #include <jack/midiport.h> Chris@1397: #include <jack/ringbuffer.h> Chris@1397: Chris@1397: #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer Chris@1397: Chris@1397: struct JackMidiData { Chris@1397: jack_client_t *client; Chris@1397: jack_port_t *port; Chris@1397: jack_ringbuffer_t *buffSize; Chris@1397: jack_ringbuffer_t *buffMessage; Chris@1397: jack_time_t lastTime; Chris@1397: MidiInApi :: RtMidiInData *rtMidiIn; Chris@1397: }; Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // API: JACK Chris@1397: // Class Definitions: MidiInJack Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: static int jackProcessIn( jack_nframes_t nframes, void *arg ) Chris@609: { Chris@1397: JackMidiData *jData = (JackMidiData *) arg; Chris@1397: MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; Chris@1397: jack_midi_event_t event; Chris@1397: jack_time_t time; Chris@1397: Chris@1397: // Is port created? Chris@1397: if ( jData->port == NULL ) return 0; Chris@1397: void *buff = jack_port_get_buffer( jData->port, nframes ); Chris@1397: Chris@1397: // We have midi events in buffer Chris@1397: int evCount = jack_midi_get_event_count( buff ); Chris@1397: for (int j = 0; j < evCount; j++) { Chris@1397: MidiInApi::MidiMessage message; Chris@1397: message.bytes.clear(); Chris@1397: Chris@1397: jack_midi_event_get( &event, buff, j ); Chris@1397: Chris@1397: for ( unsigned int i = 0; i < event.size; i++ ) Chris@1397: message.bytes.push_back( event.buffer[i] ); Chris@1397: Chris@1397: // Compute the delta time. Chris@1397: time = jack_get_time(); Chris@1397: if ( rtData->firstMessage == true ) Chris@1397: rtData->firstMessage = false; Chris@1397: else Chris@1397: message.timeStamp = ( time - jData->lastTime ) * 0.000001; Chris@1397: Chris@1397: jData->lastTime = time; Chris@1397: Chris@1397: if ( !rtData->continueSysex ) { Chris@1397: if ( rtData->usingCallback ) { Chris@1397: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; Chris@1397: callback( message.timeStamp, &message.bytes, rtData->userData ); Chris@1397: } Chris@1397: else { Chris@1397: // As long as we haven't reached our queue size limit, push the message. Chris@1397: if ( rtData->queue.size < rtData->queue.ringSize ) { Chris@1397: rtData->queue.ring[rtData->queue.back++] = message; Chris@1397: if ( rtData->queue.back == rtData->queue.ringSize ) Chris@1397: rtData->queue.back = 0; Chris@1397: rtData->queue.size++; Chris@1397: } Chris@1397: else Chris@1397: std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; Chris@1397: } Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: return 0; Chris@609: } Chris@609: Chris@1397: MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) Chris@609: { Chris@1397: initialize( clientName ); Chris@609: } Chris@609: Chris@1397: void MidiInJack :: initialize( const std::string& clientName ) Chris@609: { Chris@1397: JackMidiData *data = new JackMidiData; Chris@1397: apiData_ = (void *) data; Chris@1397: Chris@1397: data->rtMidiIn = &inputData_; Chris@1397: data->port = NULL; Chris@1397: data->client = NULL; Chris@1397: this->clientName = clientName; Chris@1397: Chris@1397: connect(); Chris@609: } Chris@609: Chris@1397: void MidiInJack :: connect() Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: if ( data->client ) Chris@1397: return; Chris@1397: Chris@1397: // Initialize JACK client Chris@1397: if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { Chris@1397: errorString_ = "MidiInJack::initialize: JACK server not running?"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: jack_set_process_callback( data->client, jackProcessIn, data ); Chris@1397: jack_activate( data->client ); Chris@609: } Chris@609: Chris@1397: MidiInJack :: ~MidiInJack() Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: closePort(); Chris@1397: Chris@1397: if ( data->client ) Chris@1397: jack_client_close( data->client ); Chris@1397: delete data; Chris@609: } Chris@609: Chris@1397: void MidiInJack :: openPort( unsigned int portNumber, const std::string portName ) Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: connect(); Chris@1397: Chris@1397: // Creating new port Chris@1397: if ( data->port == NULL) Chris@1397: data->port = jack_port_register( data->client, portName.c_str(), Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); Chris@1397: Chris@1397: if ( data->port == NULL) { Chris@1397: errorString_ = "MidiInJack::openPort: JACK error creating port"; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: // Connecting to the output Chris@1397: std::string name = getPortName( portNumber ); Chris@1397: jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); Chris@1397: } Chris@1397: Chris@1397: void MidiInJack :: openVirtualPort( const std::string portName ) Chris@1397: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: connect(); Chris@1397: if ( data->port == NULL ) Chris@1397: data->port = jack_port_register( data->client, portName.c_str(), Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); Chris@1397: Chris@1397: if ( data->port == NULL ) { Chris@1397: errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: unsigned int MidiInJack :: getPortCount() Chris@1397: { Chris@1397: int count = 0; Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: connect(); Chris@1397: if ( !data->client ) Chris@609: return 0; Chris@1397: Chris@1397: // List of available ports Chris@1397: const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); Chris@1397: Chris@1397: if ( ports == NULL ) return 0; Chris@1397: while ( ports[count] != NULL ) Chris@1397: count++; Chris@1397: Chris@1397: free( ports ); Chris@1397: Chris@1397: return count; Chris@609: } Chris@609: Chris@1397: std::string MidiInJack :: getPortName( unsigned int portNumber ) Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: std::string retStr(""); Chris@1397: Chris@1397: connect(); Chris@1397: Chris@1397: // List of available ports Chris@1397: const char **ports = jack_get_ports( data->client, NULL, Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); Chris@1397: Chris@1397: // Check port validity Chris@1397: if ( ports == NULL ) { Chris@1397: errorString_ = "MidiInJack::getPortName: no ports available!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return retStr; Chris@1397: } Chris@1397: Chris@1397: if ( ports[portNumber] == NULL ) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@1397: errorString_ = ost.str(); Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: } Chris@1397: else retStr.assign( ports[portNumber] ); Chris@1397: Chris@1397: free( ports ); Chris@1397: return retStr; Chris@609: } Chris@609: Chris@1397: void MidiInJack :: closePort() Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: if ( data->port == NULL ) return; Chris@1397: jack_port_unregister( data->client, data->port ); Chris@1397: data->port = NULL; Chris@1397: } Chris@1397: Chris@1397: //*********************************************************************// Chris@1397: // API: JACK Chris@1397: // Class Definitions: MidiOutJack Chris@1397: //*********************************************************************// Chris@1397: Chris@1397: // Jack process callback Chris@1397: static int jackProcessOut( jack_nframes_t nframes, void *arg ) Chris@1397: { Chris@1397: JackMidiData *data = (JackMidiData *) arg; Chris@1397: jack_midi_data_t *midiData; Chris@1397: int space; Chris@1397: Chris@1397: // Is port created? Chris@1397: if ( data->port == NULL ) return 0; Chris@1397: Chris@1397: void *buff = jack_port_get_buffer( data->port, nframes ); Chris@1397: jack_midi_clear_buffer( buff ); Chris@1397: Chris@1397: while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { Chris@1397: jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) ); Chris@1397: midiData = jack_midi_event_reserve( buff, 0, space ); Chris@1397: Chris@1397: jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); Chris@1397: } Chris@1397: Chris@1397: return 0; Chris@1397: } Chris@1397: Chris@1397: MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi() Chris@1397: { Chris@1397: initialize( clientName ); Chris@1397: } Chris@1397: Chris@1397: void MidiOutJack :: initialize( const std::string& clientName ) Chris@1397: { Chris@1397: JackMidiData *data = new JackMidiData; Chris@1397: apiData_ = (void *) data; Chris@1397: Chris@1397: data->port = NULL; Chris@1397: data->client = NULL; Chris@1397: this->clientName = clientName; Chris@1397: Chris@1397: connect(); Chris@1397: } Chris@1397: Chris@1397: void MidiOutJack :: connect() Chris@1397: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: if ( data->client ) Chris@1397: return; Chris@1397: Chris@1397: // Initialize output ringbuffers Chris@1397: data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); Chris@1397: data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); Chris@1397: Chris@1397: // Initialize JACK client Chris@1397: if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { Chris@1397: errorString_ = "MidiOutJack::initialize: JACK server not running?"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: jack_set_process_callback( data->client, jackProcessOut, data ); Chris@1397: jack_activate( data->client ); Chris@1397: } Chris@1397: Chris@1397: MidiOutJack :: ~MidiOutJack() Chris@1397: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: closePort(); Chris@1397: Chris@1397: // Cleanup Chris@1397: jack_ringbuffer_free( data->buffSize ); Chris@1397: jack_ringbuffer_free( data->buffMessage ); Chris@1397: if ( data->client ) { Chris@1397: jack_client_close( data->client ); Chris@1397: } Chris@1397: Chris@1397: delete data; Chris@1397: } Chris@1397: Chris@1397: void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName ) Chris@1397: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: connect(); Chris@1397: Chris@1397: // Creating new port Chris@1397: if ( data->port == NULL ) Chris@1397: data->port = jack_port_register( data->client, portName.c_str(), Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); Chris@1397: Chris@1397: if ( data->port == NULL ) { Chris@1397: errorString_ = "MidiOutJack::openPort: JACK error creating port"; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: return; Chris@1397: } Chris@1397: Chris@1397: // Connecting to the output Chris@1397: std::string name = getPortName( portNumber ); Chris@1397: jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); Chris@1397: } Chris@1397: Chris@1397: void MidiOutJack :: openVirtualPort( const std::string portName ) Chris@1397: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: connect(); Chris@1397: if ( data->port == NULL ) Chris@1397: data->port = jack_port_register( data->client, portName.c_str(), Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); Chris@1397: Chris@1397: if ( data->port == NULL ) { Chris@1397: errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; Chris@1397: error( RtMidiError::DRIVER_ERROR, errorString_ ); Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: unsigned int MidiOutJack :: getPortCount() Chris@1397: { Chris@1397: int count = 0; Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: connect(); Chris@1397: if ( !data->client ) Chris@609: return 0; Chris@1397: Chris@1397: // List of available ports Chris@1397: const char **ports = jack_get_ports( data->client, NULL, Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); Chris@1397: Chris@1397: if ( ports == NULL ) return 0; Chris@1397: while ( ports[count] != NULL ) Chris@1397: count++; Chris@1397: Chris@1397: free( ports ); Chris@1397: Chris@1397: return count; Chris@609: } Chris@609: Chris@1397: std::string MidiOutJack :: getPortName( unsigned int portNumber ) Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: std::string retStr(""); Chris@1397: Chris@1397: connect(); Chris@1397: Chris@1397: // List of available ports Chris@1397: const char **ports = jack_get_ports( data->client, NULL, Chris@1397: JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); Chris@1397: Chris@1397: // Check port validity Chris@1397: if ( ports == NULL) { Chris@1397: errorString_ = "MidiOutJack::getPortName: no ports available!"; Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: return retStr; Chris@1397: } Chris@1397: Chris@1397: if ( ports[portNumber] == NULL) { Chris@1397: std::ostringstream ost; Chris@1397: ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@1397: errorString_ = ost.str(); Chris@1397: error( RtMidiError::WARNING, errorString_ ); Chris@1397: } Chris@1397: else retStr.assign( ports[portNumber] ); Chris@1397: Chris@1397: free( ports ); Chris@1397: return retStr; Chris@609: } Chris@609: Chris@1397: void MidiOutJack :: closePort() Chris@609: { Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: if ( data->port == NULL ) return; Chris@1397: jack_port_unregister( data->client, data->port ); Chris@1397: data->port = NULL; Chris@609: } Chris@609: Chris@1397: void MidiOutJack :: sendMessage( std::vector<unsigned char> *message ) Chris@609: { Chris@1397: int nBytes = message->size(); Chris@1397: JackMidiData *data = static_cast<JackMidiData *> (apiData_); Chris@1397: Chris@1397: // Write full message to buffer Chris@1397: jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0], Chris@1397: message->size() ); Chris@1397: jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); Chris@609: } Chris@609: Chris@1397: #endif // __UNIX_JACK__