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