Chris@559: /**********************************************************************/ Chris@559: /*! \class RtMidi Chris@559: \brief An abstract base class for realtime MIDI input/output. Chris@559: Chris@559: This class implements some common functionality for the realtime Chris@559: MIDI input/output subclasses RtMidiIn and RtMidiOut. Chris@559: Chris@559: RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ Chris@559: Chris@559: RtMidi: realtime MIDI i/o C++ classes Chris@565: Copyright (c) 2003-2009 Gary P. Scavone Chris@559: Chris@559: Permission is hereby granted, free of charge, to any person Chris@559: obtaining a copy of this software and associated documentation files Chris@559: (the "Software"), to deal in the Software without restriction, Chris@559: including without limitation the rights to use, copy, modify, merge, Chris@559: publish, distribute, sublicense, and/or sell copies of the Software, Chris@559: and to permit persons to whom the Software is furnished to do so, Chris@559: subject to the following conditions: Chris@559: Chris@559: The above copyright notice and this permission notice shall be Chris@559: included in all copies or substantial portions of the Software. Chris@559: Chris@559: Any person wishing to distribute modifications to the Software is Chris@559: requested to send the modifications to the original developer so that Chris@559: they can be incorporated into the canonical version. Chris@559: Chris@559: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@559: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@559: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. Chris@559: IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR Chris@559: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@559: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@559: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@559: */ Chris@559: /**********************************************************************/ Chris@559: Chris@565: // RtMidi: Version 1.0.8 Chris@559: Chris@559: #include "RtMidi.h" Chris@559: #include Chris@559: Chris@559: //*********************************************************************// Chris@559: // Common RtMidi Definitions Chris@559: //*********************************************************************// Chris@559: Chris@559: RtMidi :: RtMidi() Chris@559: : apiData_( 0 ), connected_( false ) Chris@559: { Chris@559: } Chris@559: Chris@559: void RtMidi :: error( RtError::Type type ) Chris@559: { Chris@559: if (type == RtError::WARNING) { Chris@559: std::cerr << '\n' << errorString_ << "\n\n"; Chris@559: } Chris@559: else if (type == RtError::DEBUG_WARNING) { Chris@559: #if defined(__RTMIDI_DEBUG__) Chris@559: std::cerr << '\n' << errorString_ << "\n\n"; Chris@559: #endif Chris@559: } Chris@559: else { Chris@559: std::cerr << '\n' << errorString_ << "\n\n"; Chris@559: throw RtError( errorString_, type ); Chris@559: } Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@559: // Common RtMidiIn Definitions Chris@559: //*********************************************************************// Chris@559: Chris@565: RtMidiIn :: RtMidiIn( const std::string clientName ) : RtMidi() Chris@559: { Chris@565: this->initialize( clientName ); Chris@559: } Chris@559: Chris@559: void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) Chris@559: { Chris@559: if ( inputData_.usingCallback ) { Chris@559: errorString_ = "RtMidiIn::setCallback: a callback function is already set!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: if ( !callback ) { Chris@559: errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: inputData_.userCallback = (void *) callback; Chris@559: inputData_.userData = userData; Chris@559: inputData_.usingCallback = true; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: cancelCallback() Chris@559: { Chris@559: if ( !inputData_.usingCallback ) { Chris@559: errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: inputData_.userCallback = 0; Chris@559: inputData_.userData = 0; Chris@559: inputData_.usingCallback = false; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: setQueueSizeLimit( unsigned int queueSize ) Chris@559: { Chris@559: inputData_.queueLimit = queueSize; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) Chris@559: { Chris@559: inputData_.ignoreFlags = 0; Chris@559: if ( midiSysex ) inputData_.ignoreFlags = 0x01; Chris@559: if ( midiTime ) inputData_.ignoreFlags |= 0x02; Chris@559: if ( midiSense ) inputData_.ignoreFlags |= 0x04; Chris@559: } Chris@559: Chris@559: double RtMidiIn :: getMessage( std::vector *message ) Chris@559: { Chris@559: message->clear(); Chris@559: Chris@559: if ( inputData_.usingCallback ) { Chris@559: errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; Chris@559: error( RtError::WARNING ); Chris@559: return 0.0; Chris@559: } Chris@559: Chris@559: if ( inputData_.queue.size() == 0 ) return 0.0; Chris@559: Chris@559: // Copy queued message to the vector pointer argument and then "pop" it. Chris@559: std::vector *bytes = &(inputData_.queue.front().bytes); Chris@559: message->assign( bytes->begin(), bytes->end() ); Chris@559: double deltaTime = inputData_.queue.front().timeStamp; Chris@559: inputData_.queue.pop(); Chris@559: Chris@559: return deltaTime; Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@559: // Common RtMidiOut Definitions Chris@559: //*********************************************************************// Chris@559: Chris@565: RtMidiOut :: RtMidiOut( const std::string clientName ) : RtMidi() Chris@559: { Chris@565: this->initialize( clientName ); Chris@559: } Chris@559: Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: Macintosh OS-X Chris@559: //*********************************************************************// Chris@559: Chris@559: // API information found at: Chris@559: // - http://developer. apple .com/audio/pdf/coreaudio.pdf Chris@559: Chris@559: #if defined(__MACOSX_CORE__) Chris@559: Chris@559: // The CoreMIDI API is based on the use of a callback function for Chris@559: // MIDI input. We convert the system specific time stamps to delta Chris@559: // time values. Chris@559: Chris@559: // OS-X CoreMIDI header files. Chris@559: #include Chris@559: #include Chris@559: Chris@559: // A structure to hold variables related to the CoreMIDI API Chris@559: // implementation. Chris@559: struct CoreMidiData { Chris@559: MIDIClientRef client; Chris@559: MIDIPortRef port; Chris@559: MIDIEndpointRef endpoint; Chris@559: MIDIEndpointRef destinationId; Chris@559: unsigned long long lastTime; Chris@559: }; Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: OS-X Chris@559: // Class Definitions: RtMidiIn Chris@559: //*********************************************************************// Chris@559: Chris@559: void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef ) Chris@559: { Chris@559: RtMidiIn::RtMidiInData *data = static_cast (procRef); Chris@559: CoreMidiData *apiData = static_cast (data->apiData); Chris@559: Chris@559: unsigned char status; Chris@559: unsigned short nBytes, iByte, size; Chris@559: unsigned long long time; Chris@565: Chris@565: bool& continueSysex = data->continueSysex; Chris@565: RtMidiIn::MidiMessage& message = data->message; Chris@565: Chris@559: const MIDIPacket *packet = &list->packet[0]; Chris@559: for ( unsigned int i=0; inumPackets; ++i ) { Chris@559: Chris@559: // My interpretation of the CoreMIDI documentation: all message Chris@559: // types, except sysex, are complete within a packet and there may Chris@559: // be several of them in a single packet. Sysex messages can be Chris@565: // broken across multiple packets and PacketLists but are bundled Chris@565: // alone within each packet (these packets do not contain other Chris@565: // message types). If sysex messages are split across multiple Chris@565: // MIDIPacketLists, they must be handled by multiple calls to this Chris@565: // function. Chris@559: Chris@559: nBytes = packet->length; Chris@559: if ( nBytes == 0 ) continue; Chris@559: Chris@559: // Calculate time stamp. Chris@559: message.timeStamp = 0.0; Chris@559: if ( data->firstMessage ) Chris@559: data->firstMessage = false; Chris@559: else { Chris@559: time = packet->timeStamp; Chris@559: time -= apiData->lastTime; Chris@559: time = AudioConvertHostTimeToNanos( time ); Chris@559: message.timeStamp = time * 0.000000001; Chris@559: } Chris@559: apiData->lastTime = packet->timeStamp; Chris@559: Chris@559: iByte = 0; Chris@559: if ( continueSysex ) { Chris@559: // We have a continuing, segmented sysex message. Chris@565: if ( !( data->ignoreFlags & 0x01 ) ) { Chris@559: // If we're not ignoring sysex messages, copy the entire packet. Chris@559: for ( unsigned int j=0; jdata[j] ); Chris@559: } Chris@565: continueSysex = packet->data[nBytes-1] != 0xF7; Chris@565: Chris@559: if ( !continueSysex ) { Chris@559: // If not a continuing sysex message, invoke the user callback function or queue the message. Chris@559: if ( data->usingCallback && message.bytes.size() > 0 ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@559: if ( data->queueLimit > data->queue.size() ) Chris@559: data->queue.push( message ); Chris@559: else Chris@559: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: message.bytes.clear(); Chris@559: } Chris@559: } Chris@559: else { Chris@559: while ( iByte < nBytes ) { Chris@559: size = 0; Chris@559: // We are expecting that the next byte in the packet is a status byte. Chris@559: status = packet->data[iByte]; Chris@559: if ( !(status & 0x80) ) break; Chris@559: // Determine the number of bytes in the MIDI message. Chris@559: if ( status < 0xC0 ) size = 3; Chris@559: else if ( status < 0xE0 ) size = 2; Chris@559: else if ( status < 0xF0 ) size = 3; Chris@559: else if ( status == 0xF0 ) { Chris@559: // A MIDI sysex Chris@559: if ( data->ignoreFlags & 0x01 ) { Chris@559: size = 0; Chris@559: iByte = nBytes; Chris@559: } Chris@559: else size = nBytes - iByte; Chris@565: continueSysex = packet->data[nBytes-1] != 0xF7; Chris@559: } Chris@559: else if ( status < 0xF3 ) { Chris@559: if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) { Chris@559: // A MIDI time code message and we're ignoring it. Chris@559: size = 0; Chris@559: iByte += 3; Chris@559: } Chris@559: else size = 3; Chris@559: } Chris@559: else if ( status == 0xF3 ) size = 2; Chris@559: else if ( status == 0xF8 ) { Chris@559: size = 1; Chris@559: if ( data->ignoreFlags & 0x02 ) { Chris@559: // A MIDI timing tick message and we're ignoring it. Chris@559: size = 0; Chris@559: iByte += 3; Chris@559: } Chris@559: } Chris@559: else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { Chris@559: // A MIDI active sensing message and we're ignoring it. Chris@559: size = 0; Chris@559: iByte += 1; Chris@559: } Chris@559: else size = 1; Chris@559: Chris@559: // Copy the MIDI data to our vector. Chris@559: if ( size ) { Chris@559: message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); Chris@559: if ( !continueSysex ) { Chris@559: // If not a continuing sysex message, invoke the user callback function or queue the message. Chris@559: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@559: if ( data->queueLimit > data->queue.size() ) Chris@559: data->queue.push( message ); Chris@559: else Chris@559: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: message.bytes.clear(); Chris@559: } Chris@559: iByte += size; Chris@559: } Chris@559: } Chris@559: } Chris@559: packet = MIDIPacketNext(packet); Chris@559: } Chris@559: } Chris@559: Chris@565: void RtMidiIn :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up our client. Chris@559: MIDIClientRef client; Chris@565: OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client ); Chris@559: if ( result != noErr ) { Chris@559: errorString_ = "RtMidiIn::initialize: error creating OS-X MIDI client object."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: CoreMidiData *data = (CoreMidiData *) new CoreMidiData; Chris@559: data->client = client; Chris@559: data->endpoint = 0; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@559: } Chris@559: Chris@565: void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiIn::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nSrc = MIDIGetNumberOfSources(); Chris@559: if (nSrc < 1) { Chris@559: errorString_ = "RtMidiIn::openPort: no MIDI input sources found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nSrc ) { Chris@559: ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: MIDIPortRef port; Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@565: OSStatus result = MIDIInputPortCreate( data->client, Chris@565: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@565: midiInputCallback, (void *)&inputData_, &port ); Chris@559: if ( result != noErr ) { Chris@559: MIDIClientDispose( data->client ); Chris@559: errorString_ = "RtMidiIn::openPort: error creating OS-X MIDI input port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Get the desired input source identifier. Chris@559: MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); Chris@559: if ( endpoint == NULL ) { Chris@559: MIDIPortDispose( port ); Chris@559: MIDIClientDispose( data->client ); Chris@559: errorString_ = "RtMidiIn::openPort: error getting MIDI input source reference."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Make the connection. Chris@559: result = MIDIPortConnectSource( port, endpoint, NULL ); Chris@559: if ( result != noErr ) { Chris@559: MIDIPortDispose( port ); Chris@559: MIDIClientDispose( data->client ); Chris@559: errorString_ = "RtMidiIn::openPort: error connecting OS-X MIDI input port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Save our api-specific port information. Chris@559: data->port = port; Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: openVirtualPort( const std::string portName ) Chris@559: { Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: Chris@559: // Create a virtual MIDI input destination. Chris@559: MIDIEndpointRef endpoint; Chris@559: OSStatus result = MIDIDestinationCreate( data->client, Chris@559: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@559: midiInputCallback, (void *)&inputData_, &endpoint ); Chris@559: if ( result != noErr ) { Chris@559: errorString_ = "RtMidiIn::openVirtualPort: error creating virtual OS-X MIDI destination."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: data->endpoint = endpoint; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: MIDIPortDispose( data->port ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: RtMidiIn :: ~RtMidiIn() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: MIDIClientDispose( data->client ); Chris@559: if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: unsigned int RtMidiIn :: getPortCount() Chris@559: { Chris@559: return MIDIGetNumberOfSources(); Chris@559: } Chris@559: Chris@559: std::string RtMidiIn :: getPortName( unsigned int portNumber ) Chris@559: { Chris@559: CFStringRef nameRef; Chris@559: MIDIEndpointRef portRef; Chris@559: std::ostringstream ost; Chris@559: char name[128]; Chris@559: Chris@559: if ( portNumber >= MIDIGetNumberOfSources() ) { Chris@559: ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: portRef = MIDIGetSource( portNumber ); Chris@559: Chris@559: MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef ); Chris@559: CFStringGetCString( nameRef, name, sizeof(name), 0); Chris@559: CFRelease( nameRef ); Chris@559: std::string stringName = name; Chris@559: return stringName; Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: OS-X Chris@559: // Class Definitions: RtMidiOut Chris@559: //*********************************************************************// Chris@559: Chris@559: unsigned int RtMidiOut :: getPortCount() Chris@559: { Chris@559: return MIDIGetNumberOfDestinations(); Chris@559: } Chris@559: Chris@559: std::string RtMidiOut :: getPortName( unsigned int portNumber ) Chris@559: { Chris@559: CFStringRef nameRef; Chris@559: MIDIEndpointRef portRef; Chris@559: std::ostringstream ost; Chris@559: char name[128]; Chris@559: Chris@559: if ( portNumber >= MIDIGetNumberOfDestinations() ) { Chris@559: ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: portRef = MIDIGetDestination( portNumber ); Chris@559: Chris@559: MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef ); Chris@559: CFStringGetCString( nameRef, name, sizeof(name), 0); Chris@559: CFRelease( nameRef ); Chris@559: std::string stringName = name; Chris@559: return stringName; Chris@559: } Chris@559: Chris@565: void RtMidiOut :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up our client. Chris@559: MIDIClientRef client; Chris@565: OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client ); Chris@559: if ( result != noErr ) { Chris@559: errorString_ = "RtMidiOut::initialize: error creating OS-X MIDI client object."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: CoreMidiData *data = (CoreMidiData *) new CoreMidiData; Chris@559: data->client = client; Chris@559: data->endpoint = 0; Chris@559: apiData_ = (void *) data; Chris@559: } Chris@559: Chris@565: void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiOut::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nDest = MIDIGetNumberOfDestinations(); Chris@559: if (nDest < 1) { Chris@559: errorString_ = "RtMidiOut::openPort: no MIDI output destinations found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nDest ) { Chris@559: ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: MIDIPortRef port; Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@565: OSStatus result = MIDIOutputPortCreate( data->client, Chris@565: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@565: &port ); Chris@559: if ( result != noErr ) { Chris@559: MIDIClientDispose( data->client ); Chris@559: errorString_ = "RtMidiOut::openPort: error creating OS-X MIDI output port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Get the desired output port identifier. Chris@559: MIDIEndpointRef destination = MIDIGetDestination( portNumber ); Chris@559: if ( destination == NULL ) { Chris@559: MIDIPortDispose( port ); Chris@559: MIDIClientDispose( data->client ); Chris@559: errorString_ = "RtMidiOut::openPort: error getting MIDI output destination reference."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: data->port = port; Chris@559: data->destinationId = destination; Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: MIDIPortDispose( data->port ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: void RtMidiOut :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: Chris@559: if ( data->endpoint ) { Chris@559: errorString_ = "RtMidiOut::openVirtualPort: a virtual output port already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: // Create a virtual MIDI output source. Chris@559: MIDIEndpointRef endpoint; Chris@559: OSStatus result = MIDISourceCreate( data->client, Chris@559: CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), Chris@559: &endpoint ); Chris@559: if ( result != noErr ) { Chris@559: errorString_ = "RtMidiOut::initialize: error creating OS-X virtual MIDI source."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: data->endpoint = endpoint; Chris@559: } Chris@559: Chris@559: RtMidiOut :: ~RtMidiOut() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: MIDIClientDispose( data->client ); Chris@559: if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: sendMessage( std::vector *message ) Chris@559: { Chris@565: // The CoreMidi documentation indicates a maximum PackList size of Chris@565: // 64K, so we may need to break long sysex messages into pieces and Chris@565: // send via separate lists. Chris@559: unsigned int nBytes = message->size(); Chris@565: if ( nBytes == 0 ) { Chris@565: errorString_ = "RtMidiOut::sendMessage: no data in message argument!"; Chris@565: error( RtError::WARNING ); Chris@565: return; Chris@565: } Chris@559: Chris@565: if ( nBytes > 3 && ( message->at(0) != 0xF0 ) ) { Chris@565: errorString_ = "RtMidiOut::sendMessage: message format problem ... not sysex but > 3 bytes?"; Chris@565: error( RtError::WARNING ); Chris@565: return; Chris@565: } Chris@565: Chris@565: unsigned int packetBytes, bytesLeft = nBytes; Chris@565: unsigned int messageIndex = 0; Chris@559: MIDITimeStamp timeStamp = 0; Chris@559: CoreMidiData *data = static_cast (apiData_); Chris@559: Chris@565: while ( bytesLeft > 0 ) { Chris@565: Chris@565: packetBytes = ( bytesLeft > 32736 ) ? 32736 : bytesLeft; Chris@565: Byte buffer[packetBytes + 32]; // extra memory for other structure variables Chris@565: MIDIPacketList *packetList = (MIDIPacketList *) buffer; Chris@565: MIDIPacket *curPacket = MIDIPacketListInit( packetList ); Chris@565: Chris@565: curPacket = MIDIPacketListAdd( packetList, packetBytes+32, curPacket, timeStamp, packetBytes, (const Byte *) &message->at( messageIndex ) ); Chris@565: if ( !curPacket ) { Chris@565: errorString_ = "RtMidiOut::sendMessage: could not allocate packet list"; Chris@565: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@565: messageIndex += packetBytes; Chris@565: bytesLeft -= packetBytes; Chris@559: Chris@565: // Send to any destinations that may have connected to us. Chris@565: OSStatus result; Chris@565: if ( data->endpoint ) { Chris@565: result = MIDIReceived( data->endpoint, packetList ); Chris@565: if ( result != noErr ) { Chris@565: errorString_ = "RtMidiOut::sendMessage: error sending MIDI to virtual destinations."; Chris@565: error( RtError::WARNING ); Chris@565: } Chris@565: } Chris@565: Chris@565: // And send to an explicit destination port if we're connected. Chris@565: if ( connected_ ) { Chris@565: result = MIDISend( data->port, data->destinationId, packetList ); Chris@565: if ( result != noErr ) { Chris@565: errorString_ = "RtMidiOut::sendMessage: error sending MIDI message to port."; Chris@565: error( RtError::WARNING ); Chris@565: } Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@559: #endif // __MACOSX_CORE__ Chris@559: Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: LINUX ALSA SEQUENCER Chris@559: //*********************************************************************// Chris@559: Chris@559: // API information found at: Chris@559: // - http://www.alsa-project.org/documentation.php#Library Chris@559: Chris@559: #if defined(__LINUX_ALSASEQ__) Chris@559: Chris@559: // The ALSA Sequencer API is based on the use of a callback function for Chris@559: // MIDI input. Chris@559: // Chris@559: // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer Chris@559: // time stamps and other assorted fixes!!! Chris@559: Chris@559: #include Chris@559: #include Chris@559: Chris@559: // ALSA header file. Chris@559: #include Chris@559: Chris@559: // A structure to hold variables related to the ALSA API Chris@559: // implementation. Chris@559: struct AlsaMidiData { Chris@559: snd_seq_t *seq; Chris@559: int vport; Chris@559: snd_seq_port_subscribe_t *subscription; Chris@559: snd_midi_event_t *coder; Chris@559: unsigned int bufferSize; Chris@559: unsigned char *buffer; Chris@559: pthread_t thread; Chris@559: unsigned long long lastTime; Chris@559: int queue_id; // an input queue is needed to get timestamped events Chris@559: }; Chris@559: Chris@559: #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: LINUX ALSA Chris@559: // Class Definitions: RtMidiIn Chris@559: //*********************************************************************// Chris@559: Chris@559: extern "C" void *alsaMidiHandler( void *ptr ) Chris@559: { Chris@559: RtMidiIn::RtMidiInData *data = static_cast (ptr); Chris@559: AlsaMidiData *apiData = static_cast (data->apiData); Chris@559: Chris@559: long nBytes; Chris@559: unsigned long long time, lastTime; Chris@559: bool continueSysex = false; Chris@559: RtMidiIn::MidiMessage message; Chris@559: Chris@559: snd_seq_event_t *ev; Chris@559: int result; Chris@559: apiData->bufferSize = 32; Chris@559: result = snd_midi_event_new( 0, &apiData->coder ); Chris@559: if ( result < 0 ) { Chris@559: data->doInput = false; Chris@559: std::cerr << "\nRtMidiIn::alsaMidiHandler: error initializing MIDI event parser!\n\n"; Chris@559: return 0; Chris@559: } Chris@559: unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); Chris@559: if ( buffer == NULL ) { Chris@559: data->doInput = false; Chris@559: std::cerr << "\nRtMidiIn::alsaMidiHandler: error initializing buffer memory!\n\n"; Chris@559: return 0; Chris@559: } Chris@559: snd_midi_event_init( apiData->coder ); Chris@559: snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages Chris@559: Chris@559: while ( data->doInput ) { Chris@559: Chris@559: if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { Chris@559: // No data pending ... sleep a bit. Chris@559: usleep( 1000 ); Chris@559: continue; Chris@559: } Chris@559: Chris@559: // If here, there should be data. Chris@559: result = snd_seq_event_input( apiData->seq, &ev ); Chris@559: if ( result == -ENOSPC ) { Chris@559: std::cerr << "\nRtMidiIn::alsaMidiHandler: MIDI input buffer overrun!\n\n"; Chris@559: continue; Chris@559: } Chris@559: else if ( result <= 0 ) { Chris@559: std::cerr << "RtMidiIn::alsaMidiHandler: unknown MIDI input error!\n"; Chris@559: continue; Chris@559: } Chris@559: Chris@559: // This is a bit weird, but we now have to decode an ALSA MIDI Chris@559: // event (back) into MIDI bytes. We'll ignore non-MIDI types. Chris@565: if ( !continueSysex ) Chris@565: message.bytes.clear(); Chris@565: Chris@559: switch ( ev->type ) { Chris@559: Chris@559: case SND_SEQ_EVENT_PORT_SUBSCRIBED: Chris@559: #if defined(__RTMIDI_DEBUG__) Chris@559: std::cout << "RtMidiIn::alsaMidiHandler: port connection made!\n"; Chris@559: #endif Chris@559: break; Chris@559: Chris@559: case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: Chris@565: #if defined(__RTMIDI_DEBUG__) Chris@559: std::cerr << "RtMidiIn::alsaMidiHandler: port connection has closed!\n"; Chris@565: // FIXME: this is called for all unsubscribe events, even ones Chris@565: //not related to this particular connection. As it stands, I Chris@565: //see no data provided in the "source" and "dest" fields so Chris@565: //there is nothing we can do about this at this time. Chris@565: // std::cout << "sender = " << ev->source.client << ", dest = " << ev->dest.port << std::endl; Chris@565: #endif Chris@565: //data->doInput = false; Chris@559: break; Chris@559: Chris@559: case SND_SEQ_EVENT_QFRAME: // MIDI time code Chris@559: if ( data->ignoreFlags & 0x02 ) break; Chris@559: Chris@559: case SND_SEQ_EVENT_TICK: // MIDI timing tick Chris@559: if ( data->ignoreFlags & 0x02 ) break; Chris@559: Chris@559: case SND_SEQ_EVENT_SENSING: // Active sensing Chris@559: if ( data->ignoreFlags & 0x04 ) break; Chris@559: Chris@559: case SND_SEQ_EVENT_SYSEX: Chris@559: if ( (data->ignoreFlags & 0x01) ) break; Chris@559: if ( ev->data.ext.len > apiData->bufferSize ) { Chris@559: apiData->bufferSize = ev->data.ext.len; Chris@559: free( buffer ); Chris@559: buffer = (unsigned char *) malloc( apiData->bufferSize ); Chris@559: if ( buffer == NULL ) { Chris@559: data->doInput = false; Chris@559: std::cerr << "\nRtMidiIn::alsaMidiHandler: error resizing buffer memory!\n\n"; Chris@559: break; Chris@559: } Chris@559: } Chris@559: Chris@559: default: Chris@559: nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); Chris@559: if ( nBytes <= 0 ) { Chris@559: #if defined(__RTMIDI_DEBUG__) Chris@559: std::cerr << "\nRtMidiIn::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; Chris@559: #endif Chris@559: break; Chris@559: } Chris@559: Chris@559: // The ALSA sequencer has a maximum buffer size for MIDI sysex Chris@559: // events of 256 bytes. If a device sends sysex messages larger Chris@559: // than this, they are segmented into 256 byte chunks. So, Chris@559: // we'll watch for this and concatenate sysex chunks into a Chris@559: // single sysex message if necessary. Chris@559: if ( !continueSysex ) Chris@559: message.bytes.assign( buffer, &buffer[nBytes] ); Chris@559: else Chris@559: message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); Chris@559: Chris@565: continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); Chris@559: if ( continueSysex ) Chris@559: break; Chris@559: Chris@559: // Calculate the time stamp: Chris@559: message.timeStamp = 0.0; Chris@559: Chris@559: // Method 1: Use the system time. Chris@559: //(void)gettimeofday(&tv, (struct timezone *)NULL); Chris@559: //time = (tv.tv_sec * 1000000) + tv.tv_usec; Chris@559: Chris@559: // Method 2: Use the ALSA sequencer event time data. Chris@559: // (thanks to Pedro Lopez-Cabanillas!). Chris@559: time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 ); Chris@559: lastTime = time; Chris@559: time -= apiData->lastTime; Chris@559: apiData->lastTime = lastTime; Chris@559: if ( data->firstMessage == true ) Chris@559: data->firstMessage = false; Chris@559: else Chris@559: message.timeStamp = time * 0.000001; Chris@559: } Chris@559: Chris@559: snd_seq_free_event(ev); Chris@559: if ( message.bytes.size() == 0 ) continue; Chris@559: Chris@565: if ( data->usingCallback && !continueSysex ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@559: if ( data->queueLimit > data->queue.size() ) Chris@559: data->queue.push( message ); Chris@559: else Chris@559: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: } Chris@559: Chris@559: if ( buffer ) free( buffer ); Chris@559: snd_midi_event_free( apiData->coder ); Chris@559: apiData->coder = 0; Chris@559: return 0; Chris@559: } Chris@559: Chris@565: void RtMidiIn :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up the ALSA sequencer client. Chris@565: snd_seq_t *seq; Chris@559: int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); Chris@559: if ( result < 0 ) { Chris@559: errorString_ = "RtMidiIn::initialize: error creating ALSA sequencer input client object."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Set client name. Chris@565: snd_seq_set_client_name( seq, clientName.c_str() ); Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; Chris@559: data->seq = seq; Chris@559: data->vport = -1; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@559: Chris@559: // Create the input queue Chris@559: data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); Chris@559: // Set arbitrary tempo (mm=100) and resolution (240) Chris@559: snd_seq_queue_tempo_t *qtempo; Chris@559: snd_seq_queue_tempo_alloca(&qtempo); Chris@559: snd_seq_queue_tempo_set_tempo(qtempo, 600000); Chris@559: snd_seq_queue_tempo_set_ppq(qtempo, 240); Chris@559: snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); Chris@559: snd_seq_drain_output(data->seq); Chris@559: } Chris@559: Chris@559: // This function is used to count or get the pinfo structure for a given port number. Chris@559: unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) Chris@559: { Chris@559: snd_seq_client_info_t *cinfo; Chris@559: int client; Chris@559: int count = 0; Chris@559: snd_seq_client_info_alloca( &cinfo ); Chris@559: Chris@559: snd_seq_client_info_set_client( cinfo, -1 ); Chris@559: while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { Chris@559: client = snd_seq_client_info_get_client( cinfo ); Chris@559: if ( client == 0 ) continue; Chris@559: // Reset query info Chris@559: snd_seq_port_info_set_client( pinfo, client ); Chris@559: snd_seq_port_info_set_port( pinfo, -1 ); Chris@559: while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { Chris@565: unsigned int atyp = snd_seq_port_info_get_type( pinfo ); Chris@565: if ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) continue; Chris@565: unsigned int caps = snd_seq_port_info_get_capability( pinfo ); Chris@565: if ( ( caps & type ) != type ) continue; Chris@559: if ( count == portNumber ) return 1; Chris@559: count++; Chris@559: } Chris@559: } Chris@559: Chris@559: // If a negative portNumber was used, return the port count. Chris@559: if ( portNumber < 0 ) return count; Chris@559: return 0; Chris@559: } Chris@559: Chris@565: void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiIn::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nSrc = this->getPortCount(); Chris@559: if (nSrc < 1) { Chris@559: errorString_ = "RtMidiIn::openPort: no MIDI input sources found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@565: snd_seq_port_info_t *pinfo; Chris@565: snd_seq_port_info_alloca( &pinfo ); Chris@559: std::ostringstream ost; Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { Chris@559: ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: Chris@559: snd_seq_addr_t sender, receiver; Chris@559: sender.client = snd_seq_port_info_get_client( pinfo ); Chris@559: sender.port = snd_seq_port_info_get_port( pinfo ); Chris@559: receiver.client = snd_seq_client_id( data->seq ); Chris@559: if ( data->vport < 0 ) { Chris@559: snd_seq_port_info_set_client( pinfo, 0 ); Chris@559: snd_seq_port_info_set_port( pinfo, 0 ); Chris@559: snd_seq_port_info_set_capability( pinfo, Chris@559: SND_SEQ_PORT_CAP_WRITE | Chris@559: SND_SEQ_PORT_CAP_SUBS_WRITE ); Chris@559: snd_seq_port_info_set_type( pinfo, Chris@559: SND_SEQ_PORT_TYPE_MIDI_GENERIC | Chris@559: SND_SEQ_PORT_TYPE_APPLICATION ); Chris@559: snd_seq_port_info_set_midi_channels(pinfo, 16); Chris@559: snd_seq_port_info_set_timestamping(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_real(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); Chris@565: snd_seq_port_info_set_name(pinfo, portName.c_str() ); Chris@559: data->vport = snd_seq_create_port(data->seq, pinfo); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@559: errorString_ = "RtMidiIn::openPort: ALSA error creating input port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: } Chris@559: Chris@559: receiver.port = data->vport; Chris@559: Chris@559: // Make subscription Chris@559: snd_seq_port_subscribe_malloc( &data->subscription ); Chris@559: snd_seq_port_subscribe_set_sender(data->subscription, &sender); Chris@559: snd_seq_port_subscribe_set_dest(data->subscription, &receiver); Chris@559: if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { Chris@559: errorString_ = "RtMidiIn::openPort: ALSA error making port connection."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: if ( inputData_.doInput == false ) { Chris@559: // Start the input queue Chris@559: snd_seq_start_queue( data->seq, data->queue_id, NULL ); Chris@559: snd_seq_drain_output( data->seq ); Chris@559: // Start our MIDI input thread. Chris@559: pthread_attr_t attr; Chris@559: pthread_attr_init(&attr); Chris@559: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); Chris@559: pthread_attr_setschedpolicy(&attr, SCHED_OTHER); Chris@559: Chris@559: inputData_.doInput = true; Chris@559: int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); Chris@559: pthread_attr_destroy(&attr); Chris@559: if (err) { Chris@559: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@559: snd_seq_port_subscribe_free( data->subscription ); Chris@559: inputData_.doInput = false; Chris@559: errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!"; Chris@559: error( RtError::THREAD_ERROR ); Chris@559: } Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( data->vport < 0 ) { Chris@559: snd_seq_port_info_t *pinfo; Chris@559: snd_seq_port_info_alloca( &pinfo ); Chris@559: snd_seq_port_info_set_capability( pinfo, Chris@559: SND_SEQ_PORT_CAP_WRITE | Chris@559: SND_SEQ_PORT_CAP_SUBS_WRITE ); Chris@559: snd_seq_port_info_set_type( pinfo, Chris@559: SND_SEQ_PORT_TYPE_MIDI_GENERIC | Chris@559: SND_SEQ_PORT_TYPE_APPLICATION ); Chris@559: snd_seq_port_info_set_midi_channels(pinfo, 16); Chris@559: snd_seq_port_info_set_timestamping(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_real(pinfo, 1); Chris@559: snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); Chris@559: snd_seq_port_info_set_name(pinfo, portName.c_str()); Chris@559: data->vport = snd_seq_create_port(data->seq, pinfo); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@559: errorString_ = "RtMidiIn::openVirtualPort: ALSA error creating virtual port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: } Chris@559: Chris@559: if ( inputData_.doInput == false ) { Chris@559: // Start the input queue Chris@559: snd_seq_start_queue( data->seq, data->queue_id, NULL ); Chris@559: snd_seq_drain_output( data->seq ); Chris@559: // Start our MIDI input thread. Chris@559: pthread_attr_t attr; Chris@559: pthread_attr_init(&attr); Chris@559: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); Chris@559: pthread_attr_setschedpolicy(&attr, SCHED_OTHER); Chris@559: Chris@559: inputData_.doInput = true; Chris@559: int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); Chris@559: pthread_attr_destroy(&attr); Chris@559: if (err) { Chris@559: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@559: snd_seq_port_subscribe_free( data->subscription ); Chris@559: inputData_.doInput = false; Chris@559: errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!"; Chris@559: error( RtError::THREAD_ERROR ); Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@559: void RtMidiIn :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@559: snd_seq_port_subscribe_free( data->subscription ); Chris@559: // Stop the input queue Chris@559: snd_seq_stop_queue( data->seq, data->queue_id, NULL ); Chris@559: snd_seq_drain_output( data->seq ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: RtMidiIn :: ~RtMidiIn() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Shutdown the input thread. Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( inputData_.doInput ) { Chris@559: inputData_.doInput = false; Chris@559: pthread_join( data->thread, NULL ); Chris@559: } Chris@559: Chris@559: // Cleanup. Chris@559: if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); Chris@559: snd_seq_free_queue( data->seq, data->queue_id ); Chris@559: snd_seq_close( data->seq ); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: unsigned int RtMidiIn :: getPortCount() Chris@559: { Chris@559: snd_seq_port_info_t *pinfo; Chris@559: snd_seq_port_info_alloca( &pinfo ); Chris@559: Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); Chris@559: } Chris@559: Chris@559: std::string RtMidiIn :: getPortName( unsigned int portNumber ) Chris@559: { Chris@565: snd_seq_client_info_t *cinfo; Chris@565: snd_seq_port_info_t *pinfo; Chris@565: snd_seq_client_info_alloca( &cinfo ); Chris@565: snd_seq_port_info_alloca( &pinfo ); Chris@559: Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { Chris@565: int cnum = snd_seq_port_info_get_client( pinfo ); Chris@565: snd_seq_get_any_client_info( data->seq, cnum, cinfo ); Chris@565: std::ostringstream os; Chris@565: os << snd_seq_client_info_get_name( cinfo ); Chris@565: os << ":"; Chris@565: os << snd_seq_port_info_get_port( pinfo ); Chris@565: std::string stringName = os.str(); Chris@559: return stringName; Chris@559: } Chris@559: Chris@559: // If we get here, we didn't find a match. Chris@559: errorString_ = "RtMidiIn::getPortName: error looking for port name!"; Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: return 0; Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: LINUX ALSA Chris@559: // Class Definitions: RtMidiOut Chris@559: //*********************************************************************// Chris@559: Chris@559: unsigned int RtMidiOut :: getPortCount() Chris@559: { Chris@559: snd_seq_port_info_t *pinfo; Chris@559: snd_seq_port_info_alloca( &pinfo ); Chris@559: Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); Chris@559: } Chris@559: Chris@559: std::string RtMidiOut :: getPortName( unsigned int portNumber ) Chris@559: { Chris@565: snd_seq_client_info_t *cinfo; Chris@565: snd_seq_port_info_t *pinfo; Chris@565: snd_seq_client_info_alloca( &cinfo ); Chris@565: snd_seq_port_info_alloca( &pinfo ); Chris@559: Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { Chris@565: int cnum = snd_seq_port_info_get_client(pinfo); Chris@565: snd_seq_get_any_client_info( data->seq, cnum, cinfo ); Chris@565: std::ostringstream os; Chris@565: os << snd_seq_client_info_get_name(cinfo); Chris@565: os << ":"; Chris@565: os << snd_seq_port_info_get_port(pinfo); Chris@565: std::string stringName = os.str(); Chris@559: return stringName; Chris@559: } Chris@559: Chris@559: // If we get here, we didn't find a match. Chris@559: errorString_ = "RtMidiOut::getPortName: error looking for port name!"; Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: return 0; Chris@559: } Chris@559: Chris@565: void RtMidiOut :: initialize( const std::string& clientName ) Chris@559: { Chris@559: // Set up the ALSA sequencer client. Chris@565: snd_seq_t *seq; Chris@565: int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); Chris@559: if ( result < 0 ) { Chris@559: errorString_ = "RtMidiOut::initialize: error creating ALSA sequencer client object."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Set client name. Chris@565: snd_seq_set_client_name( seq, clientName.c_str() ); Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; Chris@559: data->seq = seq; Chris@559: data->vport = -1; Chris@559: data->bufferSize = 32; Chris@559: data->coder = 0; Chris@559: data->buffer = 0; Chris@559: result = snd_midi_event_new( data->bufferSize, &data->coder ); Chris@559: if ( result < 0 ) { Chris@559: delete data; Chris@559: errorString_ = "RtMidiOut::initialize: error initializing MIDI event parser!\n\n"; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: data->buffer = (unsigned char *) malloc( data->bufferSize ); Chris@559: if ( data->buffer == NULL ) { Chris@559: delete data; Chris@559: errorString_ = "RtMidiOut::initialize: error allocating buffer memory!\n\n"; Chris@559: error( RtError::MEMORY_ERROR ); Chris@559: } Chris@559: snd_midi_event_init( data->coder ); Chris@559: apiData_ = (void *) data; Chris@559: } Chris@559: Chris@565: void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiOut::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nSrc = this->getPortCount(); Chris@559: if (nSrc < 1) { Chris@559: errorString_ = "RtMidiOut::openPort: no MIDI output sources found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@565: snd_seq_port_info_t *pinfo; Chris@565: snd_seq_port_info_alloca( &pinfo ); Chris@559: std::ostringstream ost; Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { Chris@559: ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: snd_seq_addr_t sender, receiver; Chris@559: receiver.client = snd_seq_port_info_get_client( pinfo ); Chris@559: receiver.port = snd_seq_port_info_get_port( pinfo ); Chris@559: sender.client = snd_seq_client_id( data->seq ); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@565: data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), Chris@559: SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, Chris@559: SND_SEQ_PORT_TYPE_MIDI_GENERIC ); Chris@559: if ( data->vport < 0 ) { Chris@559: errorString_ = "RtMidiOut::openPort: ALSA error creating output port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: } Chris@559: Chris@559: sender.port = data->vport; Chris@559: Chris@559: // Make subscription Chris@559: snd_seq_port_subscribe_malloc( &data->subscription ); Chris@559: snd_seq_port_subscribe_set_sender(data->subscription, &sender); Chris@559: snd_seq_port_subscribe_set_dest(data->subscription, &receiver); Chris@559: snd_seq_port_subscribe_set_time_update(data->subscription, 1); Chris@559: snd_seq_port_subscribe_set_time_real(data->subscription, 1); Chris@559: if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { Chris@559: errorString_ = "RtMidiOut::openPort: ALSA error making port connection."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: snd_seq_unsubscribe_port( data->seq, data->subscription ); Chris@559: snd_seq_port_subscribe_free( data->subscription ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: void RtMidiOut :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( data->vport < 0 ) { Chris@559: data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), Chris@559: SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, Chris@559: SND_SEQ_PORT_TYPE_MIDI_GENERIC ); Chris@559: Chris@559: if ( data->vport < 0 ) { Chris@559: errorString_ = "RtMidiOut::openVirtualPort: ALSA error creating virtual port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@559: RtMidiOut :: ~RtMidiOut() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); Chris@559: if ( data->coder ) snd_midi_event_free( data->coder ); Chris@559: if ( data->buffer ) free( data->buffer ); Chris@559: snd_seq_close( data->seq ); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: sendMessage( std::vector *message ) Chris@559: { Chris@559: int result; Chris@559: AlsaMidiData *data = static_cast (apiData_); Chris@559: unsigned int nBytes = message->size(); Chris@559: if ( nBytes > data->bufferSize ) { Chris@559: data->bufferSize = nBytes; Chris@559: result = snd_midi_event_resize_buffer ( data->coder, nBytes); Chris@559: if ( result != 0 ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: ALSA error resizing MIDI event buffer."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: free (data->buffer); Chris@559: data->buffer = (unsigned char *) malloc( data->bufferSize ); Chris@559: if ( data->buffer == NULL ) { Chris@559: errorString_ = "RtMidiOut::initialize: error allocating buffer memory!\n\n"; Chris@559: error( RtError::MEMORY_ERROR ); Chris@559: } Chris@559: } Chris@559: Chris@559: snd_seq_event_t ev; Chris@559: snd_seq_ev_clear(&ev); Chris@559: snd_seq_ev_set_source(&ev, data->vport); Chris@559: snd_seq_ev_set_subs(&ev); Chris@559: snd_seq_ev_set_direct(&ev); Chris@559: for ( unsigned int i=0; ibuffer[i] = message->at(i); Chris@559: result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); Chris@559: if ( result < (int)nBytes ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: event parsing error!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: // Send the event. Chris@559: result = snd_seq_event_output(data->seq, &ev); Chris@559: if ( result < 0 ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: error sending MIDI message to port."; Chris@559: error( RtError::WARNING ); Chris@559: } Chris@559: snd_seq_drain_output(data->seq); Chris@559: } Chris@559: Chris@559: #endif // __LINUX_ALSA__ Chris@559: Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: IRIX MD Chris@559: //*********************************************************************// Chris@559: Chris@559: // API information gleamed from: Chris@559: // http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?cmd=getdoc&coll=0650&db=man&fname=3%20mdIntro Chris@559: Chris@559: // If the Makefile doesn't work, try the following: Chris@559: // CC -o midiinfo -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp midiinfo.cpp -lpthread -lmd Chris@559: // CC -o midiout -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp midiout.cpp -lpthread -lmd Chris@559: // CC -o qmidiin -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp qmidiin.cpp -lpthread -lmd Chris@559: // CC -o cmidiin -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp cmidiin.cpp -lpthread -lmd Chris@559: Chris@559: #if defined(__IRIX_MD__) Chris@559: Chris@559: #include Chris@559: #include Chris@559: #include Chris@559: Chris@559: // Irix MIDI header file. Chris@559: #include Chris@559: Chris@559: // A structure to hold variables related to the IRIX API Chris@559: // implementation. Chris@559: struct IrixMidiData { Chris@559: MDport port; Chris@559: pthread_t thread; Chris@559: }; Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: IRIX Chris@559: // Class Definitions: RtMidiIn Chris@559: //*********************************************************************// Chris@559: Chris@559: extern "C" void *irixMidiHandler( void *ptr ) Chris@559: { Chris@559: RtMidiIn::RtMidiInData *data = static_cast (ptr); Chris@559: IrixMidiData *apiData = static_cast (data->apiData); Chris@559: Chris@559: bool continueSysex = false; Chris@559: unsigned char status; Chris@559: unsigned short size; Chris@559: MDevent event; Chris@559: int fd = mdGetFd( apiData->port ); Chris@559: if ( fd < 0 ) { Chris@559: data->doInput = false; Chris@559: std::cerr << "\nRtMidiIn::irixMidiHandler: error getting port descriptor!\n\n"; Chris@559: return 0; Chris@559: } Chris@559: Chris@559: fd_set mask, rmask; Chris@559: FD_ZERO( &mask ); Chris@559: FD_SET( fd, &mask ); Chris@559: struct timeval timeout = {0, 0}; Chris@559: RtMidiIn::MidiMessage message; Chris@559: int result; Chris@559: Chris@559: while ( data->doInput ) { Chris@559: Chris@559: rmask = mask; Chris@559: timeout.tv_sec = 0; Chris@559: timeout.tv_usec = 0; Chris@559: if ( select( fd+1, &rmask, NULL, NULL, &timeout ) <= 0 ) { Chris@559: // No data pending ... sleep a bit. Chris@559: usleep( 1000 ); Chris@559: continue; Chris@559: } Chris@559: Chris@559: // If here, there should be data. Chris@559: result = mdReceive( apiData->port, &event, 1); Chris@559: if ( result <= 0 ) { Chris@559: std::cerr << "\nRtMidiIn::irixMidiHandler: MIDI input read error!\n\n"; Chris@559: continue; Chris@559: } Chris@559: Chris@559: message.timeStamp = event.stamp * 0.000000001; Chris@559: Chris@559: size = 0; Chris@559: status = event.msg[0]; Chris@559: if ( !(status & 0x80) ) continue; Chris@559: if ( status == 0xF0 ) { Chris@559: // Sysex message ... can be segmented across multiple messages. Chris@559: if ( !(data->ignoreFlags & 0x01) ) { Chris@559: if ( continueSysex ) { Chris@559: // We have a continuing, segmented sysex message. Append Chris@559: // the new bytes to our existing message. Chris@559: for ( int i=0; iusingCallback && message.bytes.size() > 0 ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@559: if ( data->queueLimit > data->queue.size() ) Chris@559: data->queue.push( message ); Chris@559: else Chris@559: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: message.bytes.clear(); Chris@559: } Chris@559: } Chris@559: } Chris@559: mdFree( NULL ); Chris@559: continue; Chris@559: } Chris@559: else if ( status < 0xC0 ) size = 3; Chris@559: else if ( status < 0xE0 ) size = 2; Chris@559: else if ( status < 0xF0 ) size = 3; Chris@559: else if ( status < 0xF3 ) { Chris@559: if ( status == 0xF1 && !(data->ignoreFlags & 0x02) ) { Chris@559: // A MIDI time code message and we're not ignoring it. Chris@559: size = 3; Chris@559: } Chris@559: } Chris@559: else if ( status == 0xF3 ) size = 2; Chris@559: else if ( status == 0xF8 ) { Chris@559: if ( !(data->ignoreFlags & 0x02) ) { Chris@559: // A MIDI timing tick message and we're not ignoring it. Chris@559: size = 1; Chris@559: } Chris@559: } Chris@559: else if ( status == 0xFE ) { // MIDI active sensing Chris@559: if ( !(data->ignoreFlags & 0x04) ) Chris@559: size = 1; Chris@559: } Chris@559: else size = 1; Chris@559: Chris@559: // Copy the MIDI data to our vector. Chris@559: if ( size ) { Chris@559: message.bytes.assign( &event.msg[0], &event.msg[size] ); Chris@559: // Invoke the user callback function or queue the message. Chris@559: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( message.timeStamp, &message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@559: if ( data->queueLimit > data->queue.size() ) Chris@559: data->queue.push( message ); Chris@559: else Chris@559: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: message.bytes.clear(); Chris@559: } Chris@559: } Chris@559: Chris@559: return 0; Chris@559: } Chris@559: Chris@565: void RtMidiIn :: initialize( const std::string& /*clientName*/ ) Chris@559: { Chris@559: // Initialize the Irix MIDI system. At the moment, we will not Chris@559: // worry about a return value of zero (ports) because there is a Chris@559: // chance the user could plug something in after instantiation. Chris@559: int nPorts = mdInit(); Chris@559: Chris@559: // Create our api-specific connection information. Chris@559: IrixMidiData *data = (IrixMidiData *) new IrixMidiData; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@559: } Chris@559: Chris@565: void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiIn::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: int nPorts = mdInit(); Chris@559: if (nPorts < 1) { Chris@559: errorString_ = "RtMidiIn::openPort: no Irix MIDI input sources found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nPorts ) { Chris@559: ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: data->port = mdOpenInPort( mdGetName(portNumber) ); Chris@559: if ( data->port == NULL ) { Chris@559: ost << "RtMidiIn::openPort: Irix error opening the port (" << portNumber << ")."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: mdSetStampMode(data->port, MD_DELTASTAMP); Chris@559: Chris@559: // Start our MIDI input thread. Chris@559: pthread_attr_t attr; Chris@559: pthread_attr_init(&attr); Chris@559: pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); Chris@559: pthread_attr_setschedpolicy(&attr, SCHED_RR); Chris@559: Chris@559: inputData_.doInput = true; Chris@559: int err = pthread_create(&data->thread, &attr, irixMidiHandler, &inputData_); Chris@559: pthread_attr_destroy(&attr); Chris@559: if (err) { Chris@559: mdClosePort( data->port ); Chris@559: inputData_.doInput = false; Chris@559: errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!"; Chris@559: error( RtError::THREAD_ERROR ); Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: // This function cannot be implemented for the Irix MIDI API. Chris@559: errorString_ = "RtMidiIn::openVirtualPort: cannot be implemented in Irix MIDI API!"; Chris@559: error( RtError::WARNING ); Chris@559: } Chris@559: Chris@559: void RtMidiIn :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: mdClosePort( data->port ); Chris@559: connected_ = false; Chris@559: Chris@559: // Shutdown the input thread. Chris@559: inputData_.doInput = false; Chris@559: pthread_join( data->thread, NULL ); Chris@559: } Chris@559: } Chris@559: Chris@559: RtMidiIn :: ~RtMidiIn() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: unsigned int RtMidiIn :: getPortCount() Chris@559: { Chris@559: int nPorts = mdInit(); Chris@559: if ( nPorts >= 0 ) return nPorts; Chris@559: else return 0; Chris@559: } Chris@559: Chris@559: std::string RtMidiIn :: getPortName( unsigned int portNumber ) Chris@559: { Chris@559: int nPorts = mdInit(); Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nPorts ) { Chris@559: ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: std::string stringName = std::string( mdGetName( portNumber ) ); Chris@559: return stringName; Chris@559: } Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: IRIX MD Chris@559: // Class Definitions: RtMidiOut Chris@559: //*********************************************************************// Chris@559: Chris@559: unsigned int RtMidiOut :: getPortCount() Chris@559: { Chris@559: int nPorts = mdInit(); Chris@559: if ( nPorts >= 0 ) return nPorts; Chris@559: else return 0; Chris@559: } Chris@559: Chris@559: std::string RtMidiOut :: getPortName( unsigned int portNumber ) Chris@559: { Chris@559: int nPorts = mdInit(); Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nPorts ) { Chris@559: ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: std::string stringName = std::string( mdGetName( portNumber ) ); Chris@559: return stringName; Chris@559: } Chris@559: Chris@565: void RtMidiOut :: initialize( const std::string& /*clientName*/ ) Chris@559: { Chris@559: // Initialize the Irix MIDI system. At the moment, we will not Chris@559: // worry about a return value of zero (ports) because there is a Chris@559: // chance the user could plug something in after instantiation. Chris@559: int nPorts = mdInit(); Chris@559: Chris@559: // Create our api-specific connection information. Chris@559: IrixMidiData *data = (IrixMidiData *) new IrixMidiData; Chris@559: apiData_ = (void *) data; Chris@559: } Chris@559: Chris@565: void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiOut::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: int nPorts = mdInit(); Chris@559: if (nPorts < 1) { Chris@559: errorString_ = "RtMidiOut::openPort: no Irix MIDI output sources found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nPorts ) { Chris@559: ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: data->port = mdOpenOutPort( mdGetName(portNumber) ); Chris@559: if ( data->port == NULL ) { Chris@559: ost << "RtMidiOut::openPort: Irix error opening the port (" << portNumber << ")."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: mdSetStampMode(data->port, MD_NOSTAMP); Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: mdClosePort( data->port ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: void RtMidiOut :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: // This function cannot be implemented for the Irix MIDI API. Chris@559: errorString_ = "RtMidiOut::openVirtualPort: cannot be implemented in Irix MIDI API!"; Chris@559: error( RtError::WARNING ); Chris@559: } Chris@559: Chris@559: RtMidiOut :: ~RtMidiOut() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: sendMessage( std::vector *message ) Chris@559: { Chris@559: int result; Chris@559: MDevent event; Chris@559: IrixMidiData *data = static_cast (apiData_); Chris@559: char *buffer = 0; Chris@559: Chris@559: unsigned int nBytes = message->size(); Chris@559: if ( nBytes == 0 ) return; Chris@559: event.stamp = 0; Chris@559: if ( message->at(0) == 0xF0 ) { Chris@559: if ( nBytes < 3 ) return; // check for bogus sysex Chris@559: event.msg[0] = 0xF0; Chris@559: event.msglen = nBytes; Chris@559: buffer = (char *) malloc( nBytes ); Chris@559: for ( int i=0; iat(i); Chris@559: event.sysexmsg = buffer; Chris@559: } Chris@559: else { Chris@559: for ( int i=0; iat(i); Chris@559: } Chris@559: Chris@559: // Send the event. Chris@559: result = mdSend( data->port, &event, 1 ); Chris@559: if ( buffer ) free( buffer ); Chris@559: if ( result < 1 ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: IRIX error sending MIDI message!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: } Chris@559: Chris@559: #endif // __IRIX_MD__ Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: Windows Multimedia Library (MM) Chris@559: //*********************************************************************// Chris@559: Chris@559: // API information deciphered from: Chris@559: // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp Chris@559: Chris@559: // Thanks to Jean-Baptiste Berruchon for the sysex code. Chris@559: Chris@559: #if defined(__WINDOWS_MM__) Chris@559: Chris@559: // The Windows MM API is based on the use of a callback function for Chris@559: // MIDI input. We convert the system specific time stamps to delta Chris@559: // time values. Chris@559: Chris@559: // Windows MM MIDI header files. Chris@559: #include Chris@559: #include Chris@559: Chris@559: // A structure to hold variables related to the CoreMIDI API Chris@559: // implementation. Chris@559: struct WinMidiData { Chris@559: HMIDIIN inHandle; // Handle to Midi Input Device Chris@559: HMIDIOUT outHandle; // Handle to Midi Output Device Chris@559: DWORD lastTime; Chris@559: RtMidiIn::MidiMessage message; Chris@559: LPMIDIHDR sysexBuffer; Chris@559: }; Chris@559: Chris@559: #define RT_SYSEX_BUFFER_SIZE 1024 Chris@559: Chris@559: //*********************************************************************// Chris@559: // API: Windows MM Chris@559: // Class Definitions: RtMidiIn Chris@559: //*********************************************************************// Chris@559: Chris@559: static void CALLBACK midiInputCallback( HMIDIOUT hmin, Chris@559: UINT inputStatus, Chris@559: DWORD instancePtr, Chris@559: DWORD midiMessage, Chris@559: DWORD timestamp ) Chris@559: { Chris@559: if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA ) return; Chris@559: Chris@559: //RtMidiIn::RtMidiInData *data = static_cast (instancePtr); Chris@559: RtMidiIn::RtMidiInData *data = (RtMidiIn::RtMidiInData *)instancePtr; Chris@559: WinMidiData *apiData = static_cast (data->apiData); Chris@559: Chris@559: // Calculate time stamp. Chris@559: apiData->message.timeStamp = 0.0; Chris@559: if ( data->firstMessage == true ) data->firstMessage = false; Chris@559: else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; Chris@559: apiData->lastTime = timestamp; Chris@559: Chris@559: if ( inputStatus == MIM_DATA ) { // Channel or system message Chris@559: Chris@559: // Make sure the first byte is a status byte. Chris@559: unsigned char status = (unsigned char) (midiMessage & 0x000000FF); Chris@559: if ( !(status & 0x80) ) return; Chris@559: Chris@559: // Determine the number of bytes in the MIDI message. Chris@559: unsigned short nBytes = 1; Chris@559: if ( status < 0xC0 ) nBytes = 3; Chris@559: else if ( status < 0xE0 ) nBytes = 2; Chris@559: else if ( status < 0xF0 ) nBytes = 3; Chris@559: else if ( status < 0xF3 ) { Chris@559: // A MIDI time code message and we're ignoring it. Chris@559: if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) return; Chris@559: nBytes = 3; Chris@559: } Chris@559: else if ( status == 0xF3 ) nBytes = 2; Chris@559: else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) { Chris@559: // A MIDI timing tick message and we're ignoring it. Chris@559: return; Chris@559: } Chris@559: else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { Chris@559: // A MIDI active sensing message and we're ignoring it. Chris@559: return; Chris@559: } Chris@559: Chris@559: // Copy bytes to our MIDI message. Chris@559: unsigned char *ptr = (unsigned char *) &midiMessage; Chris@559: for ( int i=0; imessage.bytes.push_back( *ptr++ ); Chris@559: } Chris@565: else { // Sysex message ( MIM_LONGDATA ) Chris@565: MIDIHDR *sysex = ( MIDIHDR *) midiMessage; Chris@565: if ( !( data->ignoreFlags & 0x01 ) ) { Chris@565: // Sysex message and we're not ignoring it Chris@565: for ( int i=0; i<(int)sysex->dwBytesRecorded; i++ ) Chris@565: apiData->message.bytes.push_back( sysex->lpData[i] ); Chris@565: } Chris@559: Chris@565: // The WinMM API requires that the sysex buffer be requeued after Chris@565: // input of each sysex message. Even if we are ignoring sysex Chris@565: // messages, we still need to requeue the buffer in case the user Chris@565: // decides to not ignore sysex messages in the future. However, Chris@565: // it seems that WinMM calls this function with an empty sysex Chris@565: // buffer when an application closes and in this case, we should Chris@565: // avoid requeueing it, else the computer suddenly reboots after Chris@565: // one or two minutes. Chris@559: if ( apiData->sysexBuffer->dwBytesRecorded > 0 ) { Chris@565: //if ( sysex->dwBytesRecorded > 0 ) { Chris@559: MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) Chris@559: std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; Chris@565: Chris@565: if ( data->ignoreFlags & 0x01 ) return; Chris@559: } Chris@565: else return; Chris@559: } Chris@559: Chris@559: if ( data->usingCallback ) { Chris@559: RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; Chris@559: callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); Chris@559: } Chris@559: else { Chris@559: // As long as we haven't reached our queue size limit, push the message. Chris@559: if ( data->queueLimit > data->queue.size() ) Chris@559: data->queue.push( apiData->message ); Chris@559: else Chris@559: std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; Chris@559: } Chris@559: Chris@565: // Clear the vector for the next input message. Chris@559: apiData->message.bytes.clear(); Chris@559: } Chris@559: Chris@565: void RtMidiIn :: initialize( const std::string& /*clientName*/ ) Chris@559: { Chris@559: // We'll issue a warning here if no devices are available but not Chris@559: // throw an error since the user can plugin something later. Chris@559: unsigned int nDevices = midiInGetNumDevs(); Chris@559: if ( nDevices == 0 ) { Chris@559: errorString_ = "RtMidiIn::initialize: no MIDI input devices currently available."; Chris@559: error( RtError::WARNING ); Chris@559: } Chris@559: Chris@559: // Save our api-specific connection information. Chris@559: WinMidiData *data = (WinMidiData *) new WinMidiData; Chris@559: apiData_ = (void *) data; Chris@559: inputData_.apiData = (void *) data; Chris@559: data->message.bytes.clear(); // needs to be empty for first input message Chris@559: } Chris@559: Chris@565: void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: errorString_ = "RtMidiIn::openPort: a valid connection already exists!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: unsigned int nDevices = midiInGetNumDevs(); Chris@559: if (nDevices == 0) { Chris@559: errorString_ = "RtMidiIn::openPort: no MIDI input sources found!"; Chris@559: error( RtError::NO_DEVICES_FOUND ); Chris@559: } Chris@559: Chris@559: std::ostringstream ost; Chris@559: if ( portNumber >= nDevices ) { Chris@559: ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: MMRESULT result = midiInOpen( &data->inHandle, Chris@559: portNumber, Chris@559: (DWORD)&midiInputCallback, Chris@559: (DWORD)&inputData_, Chris@559: CALLBACK_FUNCTION ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: errorString_ = "RtMidiIn::openPort: error creating Windows MM MIDI input port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Allocate and init the sysex buffer. Chris@559: data->sysexBuffer = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; Chris@565: data->sysexBuffer->lpData = new char[ RT_SYSEX_BUFFER_SIZE ]; Chris@565: data->sysexBuffer->dwBufferLength = RT_SYSEX_BUFFER_SIZE; Chris@559: data->sysexBuffer->dwFlags = 0; Chris@559: Chris@559: result = midiInPrepareHeader( data->inHandle, data->sysexBuffer, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: midiInClose( data->inHandle ); Chris@559: errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Register the buffer. Chris@559: result = midiInAddBuffer( data->inHandle, data->sysexBuffer, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: midiInClose( data->inHandle ); Chris@559: errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port (AddBuffer)."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: result = midiInStart( data->inHandle ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: midiInClose( data->inHandle ); Chris@559: errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiIn :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: // This function cannot be implemented for the Windows MM MIDI API. Chris@559: errorString_ = "RtMidiIn::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; Chris@559: error( RtError::WARNING ); Chris@559: } Chris@559: Chris@559: void RtMidiIn :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: midiInReset( data->inHandle ); Chris@559: midiInStop( data->inHandle ); Chris@559: Chris@559: int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer, sizeof(MIDIHDR)); Chris@559: delete [] data->sysexBuffer->lpData; Chris@559: delete [] data->sysexBuffer; Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: midiInClose( data->inHandle ); Chris@559: errorString_ = "RtMidiIn::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: midiInClose( data->inHandle ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: RtMidiIn :: ~RtMidiIn() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: unsigned int RtMidiIn :: getPortCount() Chris@559: { Chris@559: return midiInGetNumDevs(); Chris@559: } Chris@559: Chris@559: std::string RtMidiIn :: getPortName( unsigned int portNumber ) Chris@559: { Chris@559: unsigned int nDevices = midiInGetNumDevs(); Chris@559: if ( portNumber >= nDevices ) { Chris@559: std::ostringstream ost; Chris@559: ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: MIDIINCAPS deviceCaps; Chris@559: midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); Chris@559: Chris@559: // For some reason, we need to copy character by character with Chris@559: // UNICODE (thanks to Eduardo Coutinho!). Chris@559: //std::string stringName = std::string( deviceCaps.szPname ); Chris@559: char nameString[MAXPNAMELEN]; Chris@559: for( int i=0; i= nDevices ) { Chris@559: std::ostringstream ost; Chris@559: ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: MIDIOUTCAPS deviceCaps; Chris@559: midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); Chris@559: Chris@559: // For some reason, we need to copy character by character with Chris@559: // UNICODE (thanks to Eduardo Coutinho!). Chris@559: //std::string stringName = std::string( deviceCaps.szPname ); Chris@559: char nameString[MAXPNAMELEN]; Chris@559: for( int i=0; i= nDevices ) { Chris@559: ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; Chris@559: errorString_ = ost.str(); Chris@559: error( RtError::INVALID_PARAMETER ); Chris@559: } Chris@559: Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: MMRESULT result = midiOutOpen( &data->outHandle, Chris@559: portNumber, Chris@559: (DWORD)NULL, Chris@559: (DWORD)NULL, Chris@559: CALLBACK_NULL ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: errorString_ = "RtMidiOut::openPort: error creating Windows MM MIDI output port."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: connected_ = true; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: closePort( void ) Chris@559: { Chris@559: if ( connected_ ) { Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: midiOutReset( data->outHandle ); Chris@559: midiOutClose( data->outHandle ); Chris@559: connected_ = false; Chris@559: } Chris@559: } Chris@559: Chris@559: void RtMidiOut :: openVirtualPort( std::string portName ) Chris@559: { Chris@559: // This function cannot be implemented for the Windows MM MIDI API. Chris@559: errorString_ = "RtMidiOut::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; Chris@559: error( RtError::WARNING ); Chris@559: } Chris@559: Chris@559: RtMidiOut :: ~RtMidiOut() Chris@559: { Chris@559: // Close a connection if it exists. Chris@559: closePort(); Chris@559: Chris@559: // Cleanup. Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: delete data; Chris@559: } Chris@559: Chris@559: void RtMidiOut :: sendMessage( std::vector *message ) Chris@559: { Chris@559: unsigned int nBytes = message->size(); Chris@559: if ( nBytes == 0 ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: message argument is empty!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: MMRESULT result; Chris@559: WinMidiData *data = static_cast (apiData_); Chris@559: if ( message->at(0) == 0xF0 ) { // Sysex message Chris@559: Chris@559: // Allocate buffer for sysex data. Chris@559: char *buffer = (char *) malloc( nBytes ); Chris@559: if ( buffer == NULL ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: error allocating sysex message memory!"; Chris@559: error( RtError::MEMORY_ERROR ); Chris@559: } Chris@559: Chris@559: // Copy data to buffer. Chris@559: for ( unsigned int i=0; iat(i); Chris@559: Chris@559: // Create and prepare MIDIHDR structure. Chris@559: MIDIHDR sysex; Chris@559: sysex.lpData = (LPSTR) buffer; Chris@559: sysex.dwBufferLength = nBytes; Chris@559: sysex.dwFlags = 0; Chris@559: result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: free( buffer ); Chris@559: errorString_ = "RtMidiOut::sendMessage: error preparing sysex header."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Send the message. Chris@559: result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: free( buffer ); Chris@559: errorString_ = "RtMidiOut::sendMessage: error sending sysex message."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: Chris@559: // Unprepare the buffer and MIDIHDR. Chris@559: while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); Chris@559: free( buffer ); Chris@559: Chris@559: } Chris@559: else { // Channel or system message. Chris@559: Chris@559: // Make sure the message size isn't too big. Chris@559: if ( nBytes > 3 ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: message size is greater than 3 bytes (and not sysex)!"; Chris@559: error( RtError::WARNING ); Chris@559: return; Chris@559: } Chris@559: Chris@559: // Pack MIDI bytes into double word. Chris@559: DWORD packet; Chris@559: unsigned char *ptr = (unsigned char *) &packet; Chris@559: for ( unsigned int i=0; iat(i); Chris@559: ptr++; Chris@559: } Chris@559: Chris@559: // Send the message immediately. Chris@559: result = midiOutShortMsg( data->outHandle, packet ); Chris@559: if ( result != MMSYSERR_NOERROR ) { Chris@559: errorString_ = "RtMidiOut::sendMessage: error sending MIDI message."; Chris@559: error( RtError::DRIVER_ERROR ); Chris@559: } Chris@559: } Chris@559: } Chris@559: Chris@559: #endif // __WINDOWS_MM__