Home   Information   Classes   Download   Usage   Mail List   Requirements   Links   Tutorial


RtMidi.cpp

00001 /**********************************************************************/
00010 /**********************************************************************/
00011 
00012 #include "RtMidi.h"
00013 #include <sstream>
00014 
00015 RtMidi :: RtMidi()
00016   : apiData_( 0 ), connected_( false )
00017 {
00018 }
00019 
00020 void RtMidi :: error( RtError::Type type )
00021 {
00022   if (type == RtError::WARNING) {
00023     std::cerr << '\n' << errorString_ << "\n\n";
00024   }
00025   else if (type == RtError::DEBUG_WARNING) {
00026 #if defined(__RTMIDI_DEBUG__)
00027     std::cerr << '\n' << errorString_ << "\n\n";
00028 #endif
00029   }
00030   else {
00031     std::cerr << '\n' << errorString_ << "\n\n";
00032     throw RtError( errorString_, type );
00033   }
00034 }
00035 
00036 RtMidiIn :: RtMidiIn() : RtMidi()
00037 {
00038 }
00039 
00040 RtMidiIn :: RtMidiIn( unsigned int portNumber, RtMidiCallback callback, void *userData ) : RtMidi()
00041 
00042 {
00043   // Attempt to make the connection.
00044   this->openPort( portNumber, callback, userData );
00045 }
00046 
00047 RtMidiIn :: ~RtMidiIn()
00048 {
00049   closePort();
00050 }
00051 
00052 RtMidiOut :: RtMidiOut() : RtMidi()
00053 {
00054 }
00055 
00056 RtMidiOut :: RtMidiOut( unsigned int portNumber ) : RtMidi()
00057 {
00058   // Attempt to make the connection.
00059   this->openPort( portNumber );
00060 }
00061 
00062 RtMidiOut :: ~RtMidiOut()
00063 {
00064   closePort();
00065 }
00066 
00067 
00068 #if defined(__MACOSX_CORE__)
00069 
00070 // The CoreMIDI API is based on the use of a callback function for
00071 // MIDI input.  We convert the system specific time stamps to delta
00072 // time values.
00073 
00074 // OS-X CoreMIDI header files.
00075 #include <CoreMIDI/CoreMIDI.h>
00076 #include <CoreAudio/HostTime.h>
00077 
00078 // A structure to hold variables related to the CoreMIDI API
00079 // implementation.
00080 struct CoreMidiData {
00081   MIDIClientRef client;
00082   MIDIPortRef port;
00083   MIDIEndpointRef destinationId;
00084 };
00085 
00086 void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef )
00087 {
00088   RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (procRef);
00089 
00090   bool continueSysex = false;
00091   unsigned char status;
00092   unsigned short nBytes, iByte, size;
00093   unsigned long long time;
00094   RtMidiIn::MidiMessage message;
00095   const MIDIPacket *packet = &list->packet[0];
00096   for ( unsigned int i=0; i<list->numPackets; ++i ) {
00097 
00098     // My interpretation of the CoreMIDI documentation: all message
00099     // types, except sysex, are complete within a packet and there may
00100     // be several of them in a single packet.  Sysex messages can be
00101     // broken across multiple packets but are bundled alone within a
00102     // packet.  I'm assuming that sysex messages, if segmented, must
00103     // be complete within the same MIDIPacketList.
00104 
00105     nBytes = packet->length;
00106     if ( nBytes == 0 ) continue;
00107 
00108     // Calculate time stamp.
00109     message.timeStamp = 0.0;
00110     time = packet->timeStamp;
00111     time -= data->lastTime;
00112     data->lastTime = packet->timeStamp;
00113     time = AudioConvertHostTimeToNanos( time );
00114     message.timeStamp = time * 0.000000001;
00115 
00116     iByte = 0;
00117     if ( continueSysex ) {
00118       // We have a continuing, segmented sysex message.
00119       if ( !(data->ignoreFlags & 0x01) ) {
00120         // If we're not ignoring sysex messages, copy the entire packet.
00121         message.bytes.assign( packet->data, &packet->data[nBytes] );
00122       }
00123       if ( packet->data[nBytes] == 0xF7 ) continueSysex = false;
00124       if ( !continueSysex ) {
00125         // If not a continuing sysex message, invoke the user callback function or queue the message.
00126         if ( data->usingCallback && message.bytes.size() > 0 ) {
00127           RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
00128           callback( message.timeStamp, &message.bytes, data->userData );
00129         }
00130         else {
00131           // As long as we haven't reached our queue size limit, push the message.
00132           if ( data->queueLimit > data->queue.size() )
00133             data->queue.push( message );
00134           else
00135             std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
00136         }
00137         message.bytes.clear();
00138       }
00139     }
00140     else {
00141       while ( iByte < nBytes ) {
00142         size = 0;
00143         // We are expecting that the next byte in the packet is a status byte.
00144         status = packet->data[iByte];
00145         if ( !(status & 0x80) ) break;
00146         // Determine the number of bytes in the MIDI message.
00147         if ( status < 0xC0 ) size = 3;
00148         else if ( status < 0xE0 ) size = 2;
00149         else if ( status < 0xF0 ) size = 3;
00150         else if ( status == 0xF0 ) {
00151           // A MIDI sysex
00152           if ( data->ignoreFlags & 0x01 ) {
00153             size = 0;
00154             iByte = nBytes;
00155           }
00156           else size = nBytes - iByte;
00157           if ( packet->data[nBytes] == 0xF7 ) continueSysex = false;
00158         }
00159         else if ( status < 0xF3 ) {
00160           if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) {
00161             // A MIDI time code message and we're ignoring it.
00162             size = 0;
00163             iByte += 3;
00164           }
00165           else size = 3;
00166         }
00167         else if ( status == 0xF3 ) size = 2;
00168         else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
00169           // A MIDI active sensing message and we're ignoring it.
00170           size = 0;
00171           iByte += 1;
00172         }
00173         else size = 1;
00174 
00175         // Copy the MIDI data to our vector.
00176         if ( size ) {
00177           message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );
00178           if ( !continueSysex ) {
00179             // If not a continuing sysex message, invoke the user callback function or queue the message.
00180             if ( data->usingCallback ) {
00181               RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
00182               callback( message.timeStamp, &message.bytes, data->userData );
00183             }
00184             else {
00185               // As long as we haven't reached our queue size limit, push the message.
00186               if ( data->queueLimit > data->queue.size() )
00187                 data->queue.push( message );
00188               else
00189                 std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
00190             }
00191             message.bytes.clear();
00192           }
00193           iByte += size;
00194         }
00195       }
00196     }
00197     packet = MIDIPacketNext(packet);
00198   }
00199 }
00200 
00201 void RtMidiIn :: openPort( unsigned int portNumber, RtMidiCallback callback, void *userData )
00202 {
00203   if ( connected_ ) {
00204     errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
00205     error( RtError::WARNING );
00206     return;
00207   }
00208 
00209   unsigned int nSrc = MIDIGetNumberOfSources();
00210   if (nSrc < 1) {
00211     errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
00212     error( RtError::NO_DEVICES_FOUND );
00213   }
00214 
00215   std::ostringstream ost;
00216   if ( portNumber >= nSrc ) {
00217     ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
00218     errorString_ = ost.str();
00219     error( RtError::INVALID_PARAMETER );
00220   }
00221 
00222   MIDIClientRef client;
00223   OSStatus result = MIDIClientCreate( CFSTR("RtMidi Input Client"), NULL, NULL, &client );
00224   if ( result != noErr ) {
00225     errorString_ = "RtMidiIn::openPort: error creating OS-X MIDI client object.";
00226     error( RtError::DRIVER_ERROR );
00227   }
00228 
00229   MIDIPortRef port;
00230   result = MIDIInputPortCreate( client, CFSTR("RtMidi Virtual MIDI Input Port"), midiInputCallback, (void *)&inputData_, &port );
00231   if ( result != noErr ) {
00232     MIDIClientDispose( client );
00233     errorString_ = "RtMidiIn::openPort: error creating OS-X MIDI input port.";
00234     error( RtError::DRIVER_ERROR );
00235   }
00236 
00237   // Get the desired input source identifier.
00238   MIDIEndpointRef src = MIDIGetSource( portNumber );
00239   if ( src == NULL ) {
00240     MIDIPortDispose( port );
00241     MIDIClientDispose( client );
00242     errorString_ = "RtMidiIn::openPort: error getting MIDI input source reference.";
00243     error( RtError::DRIVER_ERROR );
00244   }
00245 
00246   // Make the connection.
00247   result = MIDIPortConnectSource(port, src, NULL );
00248   if ( result != noErr ) {
00249     MIDIPortDispose( port );
00250     MIDIClientDispose( client );
00251     errorString_ = "RtMidiIn::openPort: error connecting OS-X MIDI input port.";
00252     error( RtError::DRIVER_ERROR );
00253   }
00254 
00255   // Save our api-specific connection information.
00256   CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
00257   data->client = client;
00258   data->port = port;
00259   apiData_ = (void *) data;
00260 
00261   if ( callback ) {
00262     inputData_.userCallback = (void *) callback;
00263     inputData_.userData = userData;
00264     inputData_.usingCallback = true;
00265   }
00266 
00267   inputData_.lastTime = AudioGetCurrentHostTime();
00268 
00269   connected_ = true;
00270 }
00271 
00272 void RtMidiIn :: closePort( void )
00273 {
00274   if ( connected_ ) {
00275     CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
00276     MIDIClientDispose( data->client );
00277     MIDIPortDispose( data->port );
00278     delete data;
00279     apiData_ = 0;
00280   }
00281 
00282   connected_ = false;
00283 }
00284 
00285 unsigned int RtMidiIn :: getPortCount()
00286 {
00287   return MIDIGetNumberOfSources();
00288 }
00289 
00290 std::string RtMidiIn :: getPortName( unsigned int portNumber )
00291 {
00292   CFStringRef nameRef;
00293   MIDIEndpointRef portRef;
00294   std::ostringstream ost;
00295   char name[128];
00296 
00297   if ( portNumber >= MIDIGetNumberOfSources() ) {
00298     ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
00299     errorString_ = ost.str();
00300     error( RtError::INVALID_PARAMETER );
00301   }
00302   portRef = MIDIGetSource( portNumber );
00303 
00304   MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
00305   CFStringGetCString( nameRef, name, sizeof(name), 0);
00306   CFRelease( nameRef );
00307   std::string stringName = name;
00308   return stringName;
00309 }
00310 
00311 void RtMidiIn :: setQueueSizeLimit( unsigned int queueSize )
00312 {
00313   inputData_.queueLimit = queueSize;
00314 }
00315 
00316 void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
00317 {
00318   inputData_.ignoreFlags = 0;
00319   if ( midiSysex ) inputData_.ignoreFlags = 0x01;
00320   if ( midiTime ) inputData_.ignoreFlags |= 0x02;
00321   if ( midiSense ) inputData_.ignoreFlags |= 0x04;
00322 }
00323 
00324 double RtMidiIn :: getMessage( std::vector<unsigned char> *message )
00325 {
00326   message->clear();
00327   if ( !connected_ ) {
00328     errorString_ = "RtMidiIn::getNextMessage: a port is not open!";
00329     error( RtError::WARNING );
00330     return 0.0;
00331   }
00332 
00333   if ( inputData_.usingCallback ) {
00334     errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";
00335     error( RtError::WARNING );
00336     return 0.0;
00337   }
00338 
00339   if ( inputData_.queue.size() == 0 ) return 0.0;
00340 
00341   // Copy queued message to the vector pointer argument and then "pop" it.
00342   std::vector<unsigned char> *bytes = &(inputData_.queue.front().bytes);
00343   message->assign( bytes->begin(), bytes->end() );
00344   double deltaTime = inputData_.queue.front().timeStamp;
00345   inputData_.queue.pop();
00346 
00347   return deltaTime;
00348 }
00349 
00350 unsigned int RtMidiOut :: getPortCount()
00351 {
00352   return MIDIGetNumberOfDestinations();
00353 }
00354 
00355 std::string RtMidiOut :: getPortName( unsigned int portNumber )
00356 {
00357   CFStringRef nameRef;
00358   MIDIEndpointRef portRef;
00359   std::ostringstream ost;
00360   char name[128];
00361 
00362   if ( portNumber >= MIDIGetNumberOfDestinations() ) {
00363     ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
00364     errorString_ = ost.str();
00365     error( RtError::INVALID_PARAMETER );
00366   }
00367   portRef = MIDIGetDestination( portNumber );
00368 
00369   MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
00370   CFStringGetCString( nameRef, name, sizeof(name), 0);
00371   CFRelease( nameRef );
00372   std::string stringName = name;
00373   return stringName;
00374 }
00375 
00376 void RtMidiOut :: openPort( unsigned int portNumber )
00377 {
00378   if ( connected_ ) {
00379     errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
00380     error( RtError::WARNING );
00381     return;
00382   }
00383 
00384   unsigned int nDest = MIDIGetNumberOfDestinations();
00385   if (nDest < 1) {
00386     errorString_ = "RtMidiOut::openPort: no MIDI output destinations found!";
00387     error( RtError::NO_DEVICES_FOUND );
00388   }
00389 
00390   std::ostringstream ost;
00391   if ( portNumber >= nDest ) {
00392     ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
00393     errorString_ = ost.str();
00394     error( RtError::INVALID_PARAMETER );
00395   }
00396 
00397   MIDIClientRef client;
00398   OSStatus result = MIDIClientCreate( CFSTR("RtMidi Output Client"), NULL, NULL, &client );
00399   if ( result != noErr ) {
00400     errorString_ = "RtMidiOut::openPort: error creating OS-X MIDI client object.";
00401     error( RtError::DRIVER_ERROR );
00402   }
00403 
00404   MIDIPortRef port;
00405   result = MIDIOutputPortCreate( client, CFSTR("RtMidi Virtual MIDI Output Port"), &port );
00406   if ( result != noErr ) {
00407     MIDIClientDispose( client );
00408     errorString_ = "RtMidiOut::openPort: error creating OS-X MIDI output port.";
00409     error( RtError::DRIVER_ERROR );
00410   }
00411 
00412   // Get the desired output port identifier.
00413   MIDIEndpointRef destinationId = MIDIGetDestination( portNumber );
00414   if ( destinationId == NULL ) {
00415     MIDIPortDispose( port );
00416     MIDIClientDispose( client );
00417     errorString_ = "RtMidiOut::openPort: error getting MIDI output destination reference.";
00418     error( RtError::DRIVER_ERROR );
00419   }
00420 
00421   // Save our api-specific connection information.
00422   CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
00423   data->client = client;
00424   data->port = port;
00425   data->destinationId = destinationId;
00426   apiData_ = (void *) data;
00427 
00428   connected_ = true;
00429 }
00430 
00431 void RtMidiOut :: closePort( void )
00432 {
00433   if ( connected_ ) {
00434     CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
00435     MIDIClientDispose( data->client );
00436     MIDIPortDispose( data->port );
00437     delete data;
00438     apiData_ = 0;
00439   }
00440 
00441   connected_ = false;
00442 }
00443 
00444 void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
00445 {
00446   if ( !connected_ ) {
00447     errorString_ = "RtMidiOut::sendMessage: a port is not open!";
00448     error( RtError::WARNING );
00449     return;
00450   }
00451 
00452   unsigned int nBytes = message->size();
00453   // Pad the buffer for extra (unknown) structure data.
00454   Byte buffer[nBytes+32];
00455   MIDIPacketList *pktlist = (MIDIPacketList *) buffer;
00456   MIDIPacket *curPacket = MIDIPacketListInit( pktlist );
00457 
00458   MIDITimeStamp timeStamp = 0;
00459   curPacket = MIDIPacketListAdd( pktlist, sizeof(buffer), curPacket, timeStamp, nBytes, &message->at(0) );
00460 
00461   CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
00462   OSStatus result = MIDISend( data->port, data->destinationId, pktlist );
00463   if ( result != noErr ) {
00464     errorString_ = "RtMidiOut::sendMessage: error sending MIDI message.";
00465     error( RtError::WARNING );
00466   }
00467 }
00468 
00469 #endif
00470 

The Synthesis ToolKit in C++ (STK)
©1995-2002 Perry R. Cook and Gary P. Scavone. All Rights Reserved.