annotate data/midi/rtmidi/RtMidi.cpp @ 1247:8f076d02569a piper

Make SVDEBUG always write to a log file -- formerly this was disabled in NDEBUG builds. I think there's little use to that, it just means that we keep adding more cerr debug output because we aren't getting the log we need. And SVDEBUG logging is not usually used in tight loops, I don't think the performance overhead is too serious. Also update the About box.
author Chris Cannam
date Thu, 03 Nov 2016 14:57:00 +0000
parents a68d407f9752
children b0533d195c83
rev   line source
Chris@559 1 /**********************************************************************/
Chris@559 2 /*! \class RtMidi
Chris@559 3 \brief An abstract base class for realtime MIDI input/output.
Chris@559 4
Chris@559 5 This class implements some common functionality for the realtime
Chris@559 6 MIDI input/output subclasses RtMidiIn and RtMidiOut.
Chris@559 7
Chris@559 8 RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
Chris@559 9
Chris@559 10 RtMidi: realtime MIDI i/o C++ classes
Chris@565 11 Copyright (c) 2003-2009 Gary P. Scavone
Chris@559 12
Chris@559 13 Permission is hereby granted, free of charge, to any person
Chris@559 14 obtaining a copy of this software and associated documentation files
Chris@559 15 (the "Software"), to deal in the Software without restriction,
Chris@559 16 including without limitation the rights to use, copy, modify, merge,
Chris@559 17 publish, distribute, sublicense, and/or sell copies of the Software,
Chris@559 18 and to permit persons to whom the Software is furnished to do so,
Chris@559 19 subject to the following conditions:
Chris@559 20
Chris@559 21 The above copyright notice and this permission notice shall be
Chris@559 22 included in all copies or substantial portions of the Software.
Chris@559 23
Chris@559 24 Any person wishing to distribute modifications to the Software is
Chris@559 25 requested to send the modifications to the original developer so that
Chris@559 26 they can be incorporated into the canonical version.
Chris@559 27
Chris@559 28 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Chris@559 29 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Chris@559 30 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
Chris@559 31 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
Chris@559 32 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
Chris@559 33 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
Chris@559 34 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Chris@559 35 */
Chris@559 36 /**********************************************************************/
Chris@559 37
Chris@565 38 // RtMidi: Version 1.0.8
Chris@559 39
Chris@559 40 #include "RtMidi.h"
Chris@559 41 #include <sstream>
Chris@559 42
Chris@843 43 using std::cerr;
Chris@843 44 using std::endl;
Chris@843 45
Chris@559 46 //*********************************************************************//
Chris@559 47 // Common RtMidi Definitions
Chris@559 48 //*********************************************************************//
Chris@559 49
Chris@559 50 RtMidi :: RtMidi()
Chris@559 51 : apiData_( 0 ), connected_( false )
Chris@559 52 {
Chris@559 53 }
Chris@559 54
Chris@559 55 void RtMidi :: error( RtError::Type type )
Chris@559 56 {
Chris@559 57 if (type == RtError::WARNING) {
Chris@843 58 cerr << '\n' << errorString_ << "\n\n";
Chris@559 59 }
Chris@559 60 else if (type == RtError::DEBUG_WARNING) {
Chris@559 61 #if defined(__RTMIDI_DEBUG__)
Chris@843 62 cerr << '\n' << errorString_ << "\n\n";
Chris@559 63 #endif
Chris@559 64 }
Chris@559 65 else {
Chris@1220 66 cerr << "\nRtMidi error: " << errorString_ << "\n\n";
Chris@559 67 throw RtError( errorString_, type );
Chris@559 68 }
Chris@559 69 }
Chris@559 70
Chris@559 71 //*********************************************************************//
Chris@559 72 // Common RtMidiIn Definitions
Chris@559 73 //*********************************************************************//
Chris@559 74
Chris@565 75 RtMidiIn :: RtMidiIn( const std::string clientName ) : RtMidi()
Chris@559 76 {
Chris@565 77 this->initialize( clientName );
Chris@559 78 }
Chris@559 79
Chris@559 80 void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData )
Chris@559 81 {
Chris@559 82 if ( inputData_.usingCallback ) {
Chris@559 83 errorString_ = "RtMidiIn::setCallback: a callback function is already set!";
Chris@559 84 error( RtError::WARNING );
Chris@559 85 return;
Chris@559 86 }
Chris@559 87
Chris@559 88 if ( !callback ) {
Chris@559 89 errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";
Chris@559 90 error( RtError::WARNING );
Chris@559 91 return;
Chris@559 92 }
Chris@559 93
Chris@559 94 inputData_.userCallback = (void *) callback;
Chris@559 95 inputData_.userData = userData;
Chris@559 96 inputData_.usingCallback = true;
Chris@559 97 }
Chris@559 98
Chris@559 99 void RtMidiIn :: cancelCallback()
Chris@559 100 {
Chris@559 101 if ( !inputData_.usingCallback ) {
Chris@559 102 errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";
Chris@559 103 error( RtError::WARNING );
Chris@559 104 return;
Chris@559 105 }
Chris@559 106
Chris@559 107 inputData_.userCallback = 0;
Chris@559 108 inputData_.userData = 0;
Chris@559 109 inputData_.usingCallback = false;
Chris@559 110 }
Chris@559 111
Chris@559 112 void RtMidiIn :: setQueueSizeLimit( unsigned int queueSize )
Chris@559 113 {
Chris@559 114 inputData_.queueLimit = queueSize;
Chris@559 115 }
Chris@559 116
Chris@559 117 void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
Chris@559 118 {
Chris@559 119 inputData_.ignoreFlags = 0;
Chris@559 120 if ( midiSysex ) inputData_.ignoreFlags = 0x01;
Chris@559 121 if ( midiTime ) inputData_.ignoreFlags |= 0x02;
Chris@559 122 if ( midiSense ) inputData_.ignoreFlags |= 0x04;
Chris@559 123 }
Chris@559 124
Chris@559 125 double RtMidiIn :: getMessage( std::vector<unsigned char> *message )
Chris@559 126 {
Chris@559 127 message->clear();
Chris@559 128
Chris@559 129 if ( inputData_.usingCallback ) {
Chris@559 130 errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";
Chris@559 131 error( RtError::WARNING );
Chris@559 132 return 0.0;
Chris@559 133 }
Chris@559 134
Chris@559 135 if ( inputData_.queue.size() == 0 ) return 0.0;
Chris@559 136
Chris@559 137 // Copy queued message to the vector pointer argument and then "pop" it.
Chris@559 138 std::vector<unsigned char> *bytes = &(inputData_.queue.front().bytes);
Chris@559 139 message->assign( bytes->begin(), bytes->end() );
Chris@559 140 double deltaTime = inputData_.queue.front().timeStamp;
Chris@559 141 inputData_.queue.pop();
Chris@559 142
Chris@559 143 return deltaTime;
Chris@559 144 }
Chris@559 145
Chris@559 146 //*********************************************************************//
Chris@559 147 // Common RtMidiOut Definitions
Chris@559 148 //*********************************************************************//
Chris@559 149
Chris@565 150 RtMidiOut :: RtMidiOut( const std::string clientName ) : RtMidi()
Chris@559 151 {
Chris@565 152 this->initialize( clientName );
Chris@559 153 }
Chris@559 154
Chris@559 155
Chris@559 156 //*********************************************************************//
Chris@559 157 // API: Macintosh OS-X
Chris@559 158 //*********************************************************************//
Chris@559 159
Chris@559 160 // API information found at:
Chris@559 161 // - http://developer. apple .com/audio/pdf/coreaudio.pdf
Chris@559 162
Chris@559 163 #if defined(__MACOSX_CORE__)
Chris@559 164
Chris@559 165 // The CoreMIDI API is based on the use of a callback function for
Chris@559 166 // MIDI input. We convert the system specific time stamps to delta
Chris@559 167 // time values.
Chris@559 168
Chris@559 169 // OS-X CoreMIDI header files.
Chris@559 170 #include <CoreMIDI/CoreMIDI.h>
Chris@559 171 #include <CoreAudio/HostTime.h>
Chris@559 172
Chris@559 173 // A structure to hold variables related to the CoreMIDI API
Chris@559 174 // implementation.
Chris@559 175 struct CoreMidiData {
Chris@559 176 MIDIClientRef client;
Chris@559 177 MIDIPortRef port;
Chris@559 178 MIDIEndpointRef endpoint;
Chris@559 179 MIDIEndpointRef destinationId;
Chris@559 180 unsigned long long lastTime;
Chris@559 181 };
Chris@559 182
Chris@559 183 //*********************************************************************//
Chris@559 184 // API: OS-X
Chris@559 185 // Class Definitions: RtMidiIn
Chris@559 186 //*********************************************************************//
Chris@559 187
Chris@559 188 void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef )
Chris@559 189 {
Chris@559 190 RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (procRef);
Chris@559 191 CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData);
Chris@559 192
Chris@559 193 unsigned char status;
Chris@559 194 unsigned short nBytes, iByte, size;
Chris@559 195 unsigned long long time;
Chris@565 196
Chris@565 197 bool& continueSysex = data->continueSysex;
Chris@565 198 RtMidiIn::MidiMessage& message = data->message;
Chris@565 199
Chris@559 200 const MIDIPacket *packet = &list->packet[0];
Chris@559 201 for ( unsigned int i=0; i<list->numPackets; ++i ) {
Chris@559 202
Chris@559 203 // My interpretation of the CoreMIDI documentation: all message
Chris@559 204 // types, except sysex, are complete within a packet and there may
Chris@559 205 // be several of them in a single packet. Sysex messages can be
Chris@565 206 // broken across multiple packets and PacketLists but are bundled
Chris@565 207 // alone within each packet (these packets do not contain other
Chris@565 208 // message types). If sysex messages are split across multiple
Chris@565 209 // MIDIPacketLists, they must be handled by multiple calls to this
Chris@565 210 // function.
Chris@559 211
Chris@559 212 nBytes = packet->length;
Chris@559 213 if ( nBytes == 0 ) continue;
Chris@559 214
Chris@559 215 // Calculate time stamp.
Chris@559 216 message.timeStamp = 0.0;
Chris@559 217 if ( data->firstMessage )
Chris@559 218 data->firstMessage = false;
Chris@559 219 else {
Chris@559 220 time = packet->timeStamp;
Chris@559 221 time -= apiData->lastTime;
Chris@559 222 time = AudioConvertHostTimeToNanos( time );
Chris@559 223 message.timeStamp = time * 0.000000001;
Chris@559 224 }
Chris@559 225 apiData->lastTime = packet->timeStamp;
Chris@559 226
Chris@559 227 iByte = 0;
Chris@559 228 if ( continueSysex ) {
Chris@559 229 // We have a continuing, segmented sysex message.
Chris@565 230 if ( !( data->ignoreFlags & 0x01 ) ) {
Chris@559 231 // If we're not ignoring sysex messages, copy the entire packet.
Chris@559 232 for ( unsigned int j=0; j<nBytes; j++ )
Chris@559 233 message.bytes.push_back( packet->data[j] );
Chris@559 234 }
Chris@565 235 continueSysex = packet->data[nBytes-1] != 0xF7;
Chris@565 236
Chris@559 237 if ( !continueSysex ) {
Chris@559 238 // If not a continuing sysex message, invoke the user callback function or queue the message.
Chris@559 239 if ( data->usingCallback && message.bytes.size() > 0 ) {
Chris@559 240 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
Chris@559 241 callback( message.timeStamp, &message.bytes, data->userData );
Chris@559 242 }
Chris@559 243 else {
Chris@559 244 // As long as we haven't reached our queue size limit, push the message.
Chris@559 245 if ( data->queueLimit > data->queue.size() )
Chris@559 246 data->queue.push( message );
Chris@559 247 else
Chris@843 248 cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
Chris@559 249 }
Chris@559 250 message.bytes.clear();
Chris@559 251 }
Chris@559 252 }
Chris@559 253 else {
Chris@559 254 while ( iByte < nBytes ) {
Chris@559 255 size = 0;
Chris@559 256 // We are expecting that the next byte in the packet is a status byte.
Chris@559 257 status = packet->data[iByte];
Chris@559 258 if ( !(status & 0x80) ) break;
Chris@559 259 // Determine the number of bytes in the MIDI message.
Chris@559 260 if ( status < 0xC0 ) size = 3;
Chris@559 261 else if ( status < 0xE0 ) size = 2;
Chris@559 262 else if ( status < 0xF0 ) size = 3;
Chris@559 263 else if ( status == 0xF0 ) {
Chris@559 264 // A MIDI sysex
Chris@559 265 if ( data->ignoreFlags & 0x01 ) {
Chris@559 266 size = 0;
Chris@559 267 iByte = nBytes;
Chris@559 268 }
Chris@559 269 else size = nBytes - iByte;
Chris@565 270 continueSysex = packet->data[nBytes-1] != 0xF7;
Chris@559 271 }
Chris@559 272 else if ( status < 0xF3 ) {
Chris@559 273 if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) {
Chris@559 274 // A MIDI time code message and we're ignoring it.
Chris@559 275 size = 0;
Chris@559 276 iByte += 3;
Chris@559 277 }
Chris@559 278 else size = 3;
Chris@559 279 }
Chris@559 280 else if ( status == 0xF3 ) size = 2;
Chris@559 281 else if ( status == 0xF8 ) {
Chris@559 282 size = 1;
Chris@559 283 if ( data->ignoreFlags & 0x02 ) {
Chris@559 284 // A MIDI timing tick message and we're ignoring it.
Chris@559 285 size = 0;
Chris@559 286 iByte += 3;
Chris@559 287 }
Chris@559 288 }
Chris@559 289 else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
Chris@559 290 // A MIDI active sensing message and we're ignoring it.
Chris@559 291 size = 0;
Chris@559 292 iByte += 1;
Chris@559 293 }
Chris@559 294 else size = 1;
Chris@559 295
Chris@559 296 // Copy the MIDI data to our vector.
Chris@559 297 if ( size ) {
Chris@559 298 message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );
Chris@559 299 if ( !continueSysex ) {
Chris@559 300 // If not a continuing sysex message, invoke the user callback function or queue the message.
Chris@559 301 if ( data->usingCallback ) {
Chris@559 302 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
Chris@559 303 callback( message.timeStamp, &message.bytes, data->userData );
Chris@559 304 }
Chris@559 305 else {
Chris@559 306 // As long as we haven't reached our queue size limit, push the message.
Chris@559 307 if ( data->queueLimit > data->queue.size() )
Chris@559 308 data->queue.push( message );
Chris@559 309 else
Chris@843 310 cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
Chris@559 311 }
Chris@559 312 message.bytes.clear();
Chris@559 313 }
Chris@559 314 iByte += size;
Chris@559 315 }
Chris@559 316 }
Chris@559 317 }
Chris@559 318 packet = MIDIPacketNext(packet);
Chris@559 319 }
Chris@559 320 }
Chris@559 321
Chris@565 322 void RtMidiIn :: initialize( const std::string& clientName )
Chris@559 323 {
Chris@559 324 // Set up our client.
Chris@559 325 MIDIClientRef client;
Chris@565 326 OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client );
Chris@559 327 if ( result != noErr ) {
Chris@559 328 errorString_ = "RtMidiIn::initialize: error creating OS-X MIDI client object.";
Chris@559 329 error( RtError::DRIVER_ERROR );
Chris@559 330 }
Chris@559 331
Chris@559 332 // Save our api-specific connection information.
Chris@559 333 CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
Chris@559 334 data->client = client;
Chris@559 335 data->endpoint = 0;
Chris@559 336 apiData_ = (void *) data;
Chris@559 337 inputData_.apiData = (void *) data;
Chris@559 338 }
Chris@559 339
Chris@565 340 void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName )
Chris@559 341 {
Chris@559 342 if ( connected_ ) {
Chris@559 343 errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
Chris@559 344 error( RtError::WARNING );
Chris@559 345 return;
Chris@559 346 }
Chris@559 347
Chris@559 348 unsigned int nSrc = MIDIGetNumberOfSources();
Chris@559 349 if (nSrc < 1) {
Chris@559 350 errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
Chris@559 351 error( RtError::NO_DEVICES_FOUND );
Chris@559 352 }
Chris@559 353
Chris@559 354 std::ostringstream ost;
Chris@559 355 if ( portNumber >= nSrc ) {
Chris@559 356 ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 357 errorString_ = ost.str();
Chris@559 358 error( RtError::INVALID_PARAMETER );
Chris@559 359 }
Chris@559 360
Chris@559 361 MIDIPortRef port;
Chris@559 362 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@565 363 OSStatus result = MIDIInputPortCreate( data->client,
Chris@565 364 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
Chris@565 365 midiInputCallback, (void *)&inputData_, &port );
Chris@559 366 if ( result != noErr ) {
Chris@559 367 MIDIClientDispose( data->client );
Chris@559 368 errorString_ = "RtMidiIn::openPort: error creating OS-X MIDI input port.";
Chris@559 369 error( RtError::DRIVER_ERROR );
Chris@559 370 }
Chris@559 371
Chris@559 372 // Get the desired input source identifier.
Chris@559 373 MIDIEndpointRef endpoint = MIDIGetSource( portNumber );
Chris@559 374 if ( endpoint == NULL ) {
Chris@559 375 MIDIPortDispose( port );
Chris@559 376 MIDIClientDispose( data->client );
Chris@559 377 errorString_ = "RtMidiIn::openPort: error getting MIDI input source reference.";
Chris@559 378 error( RtError::DRIVER_ERROR );
Chris@559 379 }
Chris@559 380
Chris@559 381 // Make the connection.
Chris@559 382 result = MIDIPortConnectSource( port, endpoint, NULL );
Chris@559 383 if ( result != noErr ) {
Chris@559 384 MIDIPortDispose( port );
Chris@559 385 MIDIClientDispose( data->client );
Chris@559 386 errorString_ = "RtMidiIn::openPort: error connecting OS-X MIDI input port.";
Chris@559 387 error( RtError::DRIVER_ERROR );
Chris@559 388 }
Chris@559 389
Chris@559 390 // Save our api-specific port information.
Chris@559 391 data->port = port;
Chris@559 392
Chris@559 393 connected_ = true;
Chris@559 394 }
Chris@559 395
Chris@559 396 void RtMidiIn :: openVirtualPort( const std::string portName )
Chris@559 397 {
Chris@559 398 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 399
Chris@559 400 // Create a virtual MIDI input destination.
Chris@559 401 MIDIEndpointRef endpoint;
Chris@559 402 OSStatus result = MIDIDestinationCreate( data->client,
Chris@559 403 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
Chris@559 404 midiInputCallback, (void *)&inputData_, &endpoint );
Chris@559 405 if ( result != noErr ) {
Chris@559 406 errorString_ = "RtMidiIn::openVirtualPort: error creating virtual OS-X MIDI destination.";
Chris@559 407 error( RtError::DRIVER_ERROR );
Chris@559 408 }
Chris@559 409
Chris@559 410 // Save our api-specific connection information.
Chris@559 411 data->endpoint = endpoint;
Chris@559 412 }
Chris@559 413
Chris@559 414 void RtMidiIn :: closePort( void )
Chris@559 415 {
Chris@559 416 if ( connected_ ) {
Chris@559 417 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 418 MIDIPortDispose( data->port );
Chris@559 419 connected_ = false;
Chris@559 420 }
Chris@559 421 }
Chris@559 422
Chris@559 423 RtMidiIn :: ~RtMidiIn()
Chris@559 424 {
Chris@559 425 // Close a connection if it exists.
Chris@559 426 closePort();
Chris@559 427
Chris@559 428 // Cleanup.
Chris@559 429 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 430 MIDIClientDispose( data->client );
Chris@559 431 if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
Chris@559 432 delete data;
Chris@559 433 }
Chris@559 434
Chris@559 435 unsigned int RtMidiIn :: getPortCount()
Chris@559 436 {
Chris@559 437 return MIDIGetNumberOfSources();
Chris@559 438 }
Chris@559 439
Chris@559 440 std::string RtMidiIn :: getPortName( unsigned int portNumber )
Chris@559 441 {
Chris@559 442 CFStringRef nameRef;
Chris@559 443 MIDIEndpointRef portRef;
Chris@559 444 std::ostringstream ost;
Chris@559 445 char name[128];
Chris@559 446
Chris@559 447 if ( portNumber >= MIDIGetNumberOfSources() ) {
Chris@559 448 ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 449 errorString_ = ost.str();
Chris@559 450 error( RtError::INVALID_PARAMETER );
Chris@559 451 }
Chris@559 452 portRef = MIDIGetSource( portNumber );
Chris@559 453
Chris@559 454 MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
Chris@559 455 CFStringGetCString( nameRef, name, sizeof(name), 0);
Chris@559 456 CFRelease( nameRef );
Chris@559 457 std::string stringName = name;
Chris@559 458 return stringName;
Chris@559 459 }
Chris@559 460
Chris@559 461 //*********************************************************************//
Chris@559 462 // API: OS-X
Chris@559 463 // Class Definitions: RtMidiOut
Chris@559 464 //*********************************************************************//
Chris@559 465
Chris@559 466 unsigned int RtMidiOut :: getPortCount()
Chris@559 467 {
Chris@559 468 return MIDIGetNumberOfDestinations();
Chris@559 469 }
Chris@559 470
Chris@559 471 std::string RtMidiOut :: getPortName( unsigned int portNumber )
Chris@559 472 {
Chris@559 473 CFStringRef nameRef;
Chris@559 474 MIDIEndpointRef portRef;
Chris@559 475 std::ostringstream ost;
Chris@559 476 char name[128];
Chris@559 477
Chris@559 478 if ( portNumber >= MIDIGetNumberOfDestinations() ) {
Chris@559 479 ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 480 errorString_ = ost.str();
Chris@559 481 error( RtError::INVALID_PARAMETER );
Chris@559 482 }
Chris@559 483 portRef = MIDIGetDestination( portNumber );
Chris@559 484
Chris@559 485 MIDIObjectGetStringProperty( portRef, kMIDIPropertyName, &nameRef );
Chris@559 486 CFStringGetCString( nameRef, name, sizeof(name), 0);
Chris@559 487 CFRelease( nameRef );
Chris@559 488 std::string stringName = name;
Chris@559 489 return stringName;
Chris@559 490 }
Chris@559 491
Chris@565 492 void RtMidiOut :: initialize( const std::string& clientName )
Chris@559 493 {
Chris@559 494 // Set up our client.
Chris@559 495 MIDIClientRef client;
Chris@565 496 OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client );
Chris@559 497 if ( result != noErr ) {
Chris@559 498 errorString_ = "RtMidiOut::initialize: error creating OS-X MIDI client object.";
Chris@559 499 error( RtError::DRIVER_ERROR );
Chris@559 500 }
Chris@559 501
Chris@559 502 // Save our api-specific connection information.
Chris@559 503 CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
Chris@559 504 data->client = client;
Chris@559 505 data->endpoint = 0;
Chris@559 506 apiData_ = (void *) data;
Chris@559 507 }
Chris@559 508
Chris@565 509 void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName )
Chris@559 510 {
Chris@559 511 if ( connected_ ) {
Chris@559 512 errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
Chris@559 513 error( RtError::WARNING );
Chris@559 514 return;
Chris@559 515 }
Chris@559 516
Chris@559 517 unsigned int nDest = MIDIGetNumberOfDestinations();
Chris@559 518 if (nDest < 1) {
Chris@559 519 errorString_ = "RtMidiOut::openPort: no MIDI output destinations found!";
Chris@559 520 error( RtError::NO_DEVICES_FOUND );
Chris@559 521 }
Chris@559 522
Chris@559 523 std::ostringstream ost;
Chris@559 524 if ( portNumber >= nDest ) {
Chris@559 525 ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 526 errorString_ = ost.str();
Chris@559 527 error( RtError::INVALID_PARAMETER );
Chris@559 528 }
Chris@559 529
Chris@559 530 MIDIPortRef port;
Chris@559 531 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@565 532 OSStatus result = MIDIOutputPortCreate( data->client,
Chris@565 533 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
Chris@565 534 &port );
Chris@559 535 if ( result != noErr ) {
Chris@559 536 MIDIClientDispose( data->client );
Chris@559 537 errorString_ = "RtMidiOut::openPort: error creating OS-X MIDI output port.";
Chris@559 538 error( RtError::DRIVER_ERROR );
Chris@559 539 }
Chris@559 540
Chris@559 541 // Get the desired output port identifier.
Chris@559 542 MIDIEndpointRef destination = MIDIGetDestination( portNumber );
Chris@559 543 if ( destination == NULL ) {
Chris@559 544 MIDIPortDispose( port );
Chris@559 545 MIDIClientDispose( data->client );
Chris@559 546 errorString_ = "RtMidiOut::openPort: error getting MIDI output destination reference.";
Chris@559 547 error( RtError::DRIVER_ERROR );
Chris@559 548 }
Chris@559 549
Chris@559 550 // Save our api-specific connection information.
Chris@559 551 data->port = port;
Chris@559 552 data->destinationId = destination;
Chris@559 553 connected_ = true;
Chris@559 554 }
Chris@559 555
Chris@559 556 void RtMidiOut :: closePort( void )
Chris@559 557 {
Chris@559 558 if ( connected_ ) {
Chris@559 559 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 560 MIDIPortDispose( data->port );
Chris@559 561 connected_ = false;
Chris@559 562 }
Chris@559 563 }
Chris@559 564
Chris@559 565 void RtMidiOut :: openVirtualPort( std::string portName )
Chris@559 566 {
Chris@559 567 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 568
Chris@559 569 if ( data->endpoint ) {
Chris@559 570 errorString_ = "RtMidiOut::openVirtualPort: a virtual output port already exists!";
Chris@559 571 error( RtError::WARNING );
Chris@559 572 return;
Chris@559 573 }
Chris@559 574
Chris@559 575 // Create a virtual MIDI output source.
Chris@559 576 MIDIEndpointRef endpoint;
Chris@559 577 OSStatus result = MIDISourceCreate( data->client,
Chris@559 578 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
Chris@559 579 &endpoint );
Chris@559 580 if ( result != noErr ) {
Chris@559 581 errorString_ = "RtMidiOut::initialize: error creating OS-X virtual MIDI source.";
Chris@559 582 error( RtError::DRIVER_ERROR );
Chris@559 583 }
Chris@559 584
Chris@559 585 // Save our api-specific connection information.
Chris@559 586 data->endpoint = endpoint;
Chris@559 587 }
Chris@559 588
Chris@559 589 RtMidiOut :: ~RtMidiOut()
Chris@559 590 {
Chris@559 591 // Close a connection if it exists.
Chris@559 592 closePort();
Chris@559 593
Chris@559 594 // Cleanup.
Chris@559 595 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 596 MIDIClientDispose( data->client );
Chris@559 597 if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
Chris@559 598 delete data;
Chris@559 599 }
Chris@559 600
Chris@559 601 void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
Chris@559 602 {
Chris@565 603 // The CoreMidi documentation indicates a maximum PackList size of
Chris@565 604 // 64K, so we may need to break long sysex messages into pieces and
Chris@565 605 // send via separate lists.
Chris@559 606 unsigned int nBytes = message->size();
Chris@565 607 if ( nBytes == 0 ) {
Chris@565 608 errorString_ = "RtMidiOut::sendMessage: no data in message argument!";
Chris@565 609 error( RtError::WARNING );
Chris@565 610 return;
Chris@565 611 }
Chris@559 612
Chris@565 613 if ( nBytes > 3 && ( message->at(0) != 0xF0 ) ) {
Chris@565 614 errorString_ = "RtMidiOut::sendMessage: message format problem ... not sysex but > 3 bytes?";
Chris@565 615 error( RtError::WARNING );
Chris@565 616 return;
Chris@565 617 }
Chris@565 618
Chris@565 619 unsigned int packetBytes, bytesLeft = nBytes;
Chris@565 620 unsigned int messageIndex = 0;
Chris@559 621 MIDITimeStamp timeStamp = 0;
Chris@559 622 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
Chris@559 623
Chris@565 624 while ( bytesLeft > 0 ) {
Chris@565 625
Chris@565 626 packetBytes = ( bytesLeft > 32736 ) ? 32736 : bytesLeft;
Chris@565 627 Byte buffer[packetBytes + 32]; // extra memory for other structure variables
Chris@565 628 MIDIPacketList *packetList = (MIDIPacketList *) buffer;
Chris@565 629 MIDIPacket *curPacket = MIDIPacketListInit( packetList );
Chris@565 630
Chris@565 631 curPacket = MIDIPacketListAdd( packetList, packetBytes+32, curPacket, timeStamp, packetBytes, (const Byte *) &message->at( messageIndex ) );
Chris@565 632 if ( !curPacket ) {
Chris@565 633 errorString_ = "RtMidiOut::sendMessage: could not allocate packet list";
Chris@565 634 error( RtError::DRIVER_ERROR );
Chris@559 635 }
Chris@565 636 messageIndex += packetBytes;
Chris@565 637 bytesLeft -= packetBytes;
Chris@559 638
Chris@565 639 // Send to any destinations that may have connected to us.
Chris@565 640 OSStatus result;
Chris@565 641 if ( data->endpoint ) {
Chris@565 642 result = MIDIReceived( data->endpoint, packetList );
Chris@565 643 if ( result != noErr ) {
Chris@565 644 errorString_ = "RtMidiOut::sendMessage: error sending MIDI to virtual destinations.";
Chris@565 645 error( RtError::WARNING );
Chris@565 646 }
Chris@565 647 }
Chris@565 648
Chris@565 649 // And send to an explicit destination port if we're connected.
Chris@565 650 if ( connected_ ) {
Chris@565 651 result = MIDISend( data->port, data->destinationId, packetList );
Chris@565 652 if ( result != noErr ) {
Chris@565 653 errorString_ = "RtMidiOut::sendMessage: error sending MIDI message to port.";
Chris@565 654 error( RtError::WARNING );
Chris@565 655 }
Chris@559 656 }
Chris@559 657 }
Chris@559 658 }
Chris@559 659
Chris@559 660 #endif // __MACOSX_CORE__
Chris@559 661
Chris@559 662
Chris@559 663 //*********************************************************************//
Chris@559 664 // API: LINUX ALSA SEQUENCER
Chris@559 665 //*********************************************************************//
Chris@559 666
Chris@559 667 // API information found at:
Chris@559 668 // - http://www.alsa-project.org/documentation.php#Library
Chris@559 669
Chris@559 670 #if defined(__LINUX_ALSASEQ__)
Chris@559 671
Chris@559 672 // The ALSA Sequencer API is based on the use of a callback function for
Chris@559 673 // MIDI input.
Chris@559 674 //
Chris@559 675 // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
Chris@559 676 // time stamps and other assorted fixes!!!
Chris@559 677
Chris@559 678 #include <pthread.h>
Chris@559 679 #include <sys/time.h>
Chris@559 680
Chris@559 681 // ALSA header file.
Chris@559 682 #include <alsa/asoundlib.h>
Chris@559 683
Chris@559 684 // A structure to hold variables related to the ALSA API
Chris@559 685 // implementation.
Chris@559 686 struct AlsaMidiData {
Chris@559 687 snd_seq_t *seq;
Chris@559 688 int vport;
Chris@559 689 snd_seq_port_subscribe_t *subscription;
Chris@559 690 snd_midi_event_t *coder;
Chris@559 691 unsigned int bufferSize;
Chris@559 692 unsigned char *buffer;
Chris@559 693 pthread_t thread;
Chris@559 694 unsigned long long lastTime;
Chris@559 695 int queue_id; // an input queue is needed to get timestamped events
Chris@559 696 };
Chris@559 697
Chris@559 698 #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
Chris@559 699
Chris@559 700 //*********************************************************************//
Chris@559 701 // API: LINUX ALSA
Chris@559 702 // Class Definitions: RtMidiIn
Chris@559 703 //*********************************************************************//
Chris@559 704
Chris@559 705 extern "C" void *alsaMidiHandler( void *ptr )
Chris@559 706 {
Chris@559 707 RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (ptr);
Chris@559 708 AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);
Chris@559 709
Chris@559 710 long nBytes;
Chris@559 711 unsigned long long time, lastTime;
Chris@559 712 bool continueSysex = false;
Chris@559 713 RtMidiIn::MidiMessage message;
Chris@559 714
Chris@559 715 snd_seq_event_t *ev;
Chris@559 716 int result;
Chris@559 717 apiData->bufferSize = 32;
Chris@559 718 result = snd_midi_event_new( 0, &apiData->coder );
Chris@559 719 if ( result < 0 ) {
Chris@559 720 data->doInput = false;
Chris@843 721 cerr << "\nRtMidiIn::alsaMidiHandler: error initializing MIDI event parser!\n\n";
Chris@559 722 return 0;
Chris@559 723 }
Chris@559 724 unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
Chris@559 725 if ( buffer == NULL ) {
Chris@559 726 data->doInput = false;
Chris@843 727 cerr << "\nRtMidiIn::alsaMidiHandler: error initializing buffer memory!\n\n";
Chris@559 728 return 0;
Chris@559 729 }
Chris@559 730 snd_midi_event_init( apiData->coder );
Chris@559 731 snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages
Chris@559 732
Chris@559 733 while ( data->doInput ) {
Chris@559 734
Chris@559 735 if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {
Chris@559 736 // No data pending ... sleep a bit.
Chris@559 737 usleep( 1000 );
Chris@559 738 continue;
Chris@559 739 }
Chris@559 740
Chris@559 741 // If here, there should be data.
Chris@559 742 result = snd_seq_event_input( apiData->seq, &ev );
Chris@559 743 if ( result == -ENOSPC ) {
Chris@843 744 cerr << "\nRtMidiIn::alsaMidiHandler: MIDI input buffer overrun!\n\n";
Chris@559 745 continue;
Chris@559 746 }
Chris@559 747 else if ( result <= 0 ) {
Chris@843 748 cerr << "RtMidiIn::alsaMidiHandler: unknown MIDI input error!\n";
Chris@559 749 continue;
Chris@559 750 }
Chris@559 751
Chris@559 752 // This is a bit weird, but we now have to decode an ALSA MIDI
Chris@559 753 // event (back) into MIDI bytes. We'll ignore non-MIDI types.
Chris@565 754 if ( !continueSysex )
Chris@565 755 message.bytes.clear();
Chris@565 756
Chris@559 757 switch ( ev->type ) {
Chris@559 758
Chris@559 759 case SND_SEQ_EVENT_PORT_SUBSCRIBED:
Chris@559 760 #if defined(__RTMIDI_DEBUG__)
Chris@843 761 cout << "RtMidiIn::alsaMidiHandler: port connection made!\n";
Chris@559 762 #endif
Chris@559 763 break;
Chris@559 764
Chris@559 765 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
Chris@565 766 #if defined(__RTMIDI_DEBUG__)
Chris@690 767 SVDEBUG << "RtMidiIn::alsaMidiHandler: port connection has closed!\n";
Chris@565 768 // FIXME: this is called for all unsubscribe events, even ones
Chris@565 769 //not related to this particular connection. As it stands, I
Chris@565 770 //see no data provided in the "source" and "dest" fields so
Chris@565 771 //there is nothing we can do about this at this time.
Chris@843 772 // cout << "sender = " << ev->source.client << ", dest = " << ev->dest.port << endl;
Chris@565 773 #endif
Chris@565 774 //data->doInput = false;
Chris@559 775 break;
Chris@559 776
Chris@559 777 case SND_SEQ_EVENT_QFRAME: // MIDI time code
Chris@559 778 if ( data->ignoreFlags & 0x02 ) break;
Chris@559 779
Chris@559 780 case SND_SEQ_EVENT_TICK: // MIDI timing tick
Chris@559 781 if ( data->ignoreFlags & 0x02 ) break;
Chris@559 782
Chris@559 783 case SND_SEQ_EVENT_SENSING: // Active sensing
Chris@559 784 if ( data->ignoreFlags & 0x04 ) break;
Chris@559 785
Chris@559 786 case SND_SEQ_EVENT_SYSEX:
Chris@559 787 if ( (data->ignoreFlags & 0x01) ) break;
Chris@559 788 if ( ev->data.ext.len > apiData->bufferSize ) {
Chris@559 789 apiData->bufferSize = ev->data.ext.len;
Chris@559 790 free( buffer );
Chris@559 791 buffer = (unsigned char *) malloc( apiData->bufferSize );
Chris@559 792 if ( buffer == NULL ) {
Chris@559 793 data->doInput = false;
Chris@843 794 cerr << "\nRtMidiIn::alsaMidiHandler: error resizing buffer memory!\n\n";
Chris@559 795 break;
Chris@559 796 }
Chris@559 797 }
Chris@559 798
Chris@559 799 default:
Chris@559 800 nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );
Chris@559 801 if ( nBytes <= 0 ) {
Chris@559 802 #if defined(__RTMIDI_DEBUG__)
Chris@843 803 cerr << "\nRtMidiIn::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
Chris@559 804 #endif
Chris@559 805 break;
Chris@559 806 }
Chris@559 807
Chris@559 808 // The ALSA sequencer has a maximum buffer size for MIDI sysex
Chris@559 809 // events of 256 bytes. If a device sends sysex messages larger
Chris@559 810 // than this, they are segmented into 256 byte chunks. So,
Chris@559 811 // we'll watch for this and concatenate sysex chunks into a
Chris@559 812 // single sysex message if necessary.
Chris@559 813 if ( !continueSysex )
Chris@559 814 message.bytes.assign( buffer, &buffer[nBytes] );
Chris@559 815 else
Chris@559 816 message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
Chris@559 817
Chris@565 818 continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
Chris@559 819 if ( continueSysex )
Chris@559 820 break;
Chris@559 821
Chris@559 822 // Calculate the time stamp:
Chris@559 823 message.timeStamp = 0.0;
Chris@559 824
Chris@559 825 // Method 1: Use the system time.
Chris@559 826 //(void)gettimeofday(&tv, (struct timezone *)NULL);
Chris@559 827 //time = (tv.tv_sec * 1000000) + tv.tv_usec;
Chris@559 828
Chris@559 829 // Method 2: Use the ALSA sequencer event time data.
Chris@559 830 // (thanks to Pedro Lopez-Cabanillas!).
Chris@559 831 time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );
Chris@559 832 lastTime = time;
Chris@559 833 time -= apiData->lastTime;
Chris@559 834 apiData->lastTime = lastTime;
Chris@559 835 if ( data->firstMessage == true )
Chris@559 836 data->firstMessage = false;
Chris@559 837 else
Chris@1038 838 message.timeStamp = double(time) * 0.000001;
Chris@559 839 }
Chris@559 840
Chris@559 841 snd_seq_free_event(ev);
Chris@559 842 if ( message.bytes.size() == 0 ) continue;
Chris@559 843
Chris@565 844 if ( data->usingCallback && !continueSysex ) {
Chris@559 845 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
Chris@559 846 callback( message.timeStamp, &message.bytes, data->userData );
Chris@559 847 }
Chris@559 848 else {
Chris@559 849 // As long as we haven't reached our queue size limit, push the message.
Chris@559 850 if ( data->queueLimit > data->queue.size() )
Chris@559 851 data->queue.push( message );
Chris@559 852 else
Chris@843 853 cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
Chris@559 854 }
Chris@559 855 }
Chris@559 856
Chris@559 857 if ( buffer ) free( buffer );
Chris@559 858 snd_midi_event_free( apiData->coder );
Chris@559 859 apiData->coder = 0;
Chris@559 860 return 0;
Chris@559 861 }
Chris@559 862
Chris@565 863 void RtMidiIn :: initialize( const std::string& clientName )
Chris@559 864 {
Chris@559 865 // Set up the ALSA sequencer client.
Chris@565 866 snd_seq_t *seq;
Chris@559 867 int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
Chris@559 868 if ( result < 0 ) {
Chris@559 869 errorString_ = "RtMidiIn::initialize: error creating ALSA sequencer input client object.";
Chris@559 870 error( RtError::DRIVER_ERROR );
Chris@559 871 }
Chris@559 872
Chris@559 873 // Set client name.
Chris@565 874 snd_seq_set_client_name( seq, clientName.c_str() );
Chris@559 875
Chris@559 876 // Save our api-specific connection information.
Chris@559 877 AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
Chris@559 878 data->seq = seq;
Chris@559 879 data->vport = -1;
Chris@559 880 apiData_ = (void *) data;
Chris@559 881 inputData_.apiData = (void *) data;
Chris@559 882
Chris@559 883 // Create the input queue
Chris@559 884 data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");
Chris@559 885 // Set arbitrary tempo (mm=100) and resolution (240)
Chris@559 886 snd_seq_queue_tempo_t *qtempo;
Chris@559 887 snd_seq_queue_tempo_alloca(&qtempo);
Chris@559 888 snd_seq_queue_tempo_set_tempo(qtempo, 600000);
Chris@559 889 snd_seq_queue_tempo_set_ppq(qtempo, 240);
Chris@559 890 snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo);
Chris@559 891 snd_seq_drain_output(data->seq);
Chris@559 892 }
Chris@559 893
Chris@559 894 // This function is used to count or get the pinfo structure for a given port number.
Chris@559 895 unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )
Chris@559 896 {
Chris@559 897 snd_seq_client_info_t *cinfo;
Chris@559 898 int client;
Chris@559 899 int count = 0;
Chris@559 900 snd_seq_client_info_alloca( &cinfo );
Chris@559 901
Chris@559 902 snd_seq_client_info_set_client( cinfo, -1 );
Chris@559 903 while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
Chris@559 904 client = snd_seq_client_info_get_client( cinfo );
Chris@559 905 if ( client == 0 ) continue;
Chris@559 906 // Reset query info
Chris@559 907 snd_seq_port_info_set_client( pinfo, client );
Chris@559 908 snd_seq_port_info_set_port( pinfo, -1 );
Chris@559 909 while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
Chris@565 910 unsigned int atyp = snd_seq_port_info_get_type( pinfo );
Chris@565 911 if ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) continue;
Chris@565 912 unsigned int caps = snd_seq_port_info_get_capability( pinfo );
Chris@565 913 if ( ( caps & type ) != type ) continue;
Chris@559 914 if ( count == portNumber ) return 1;
Chris@559 915 count++;
Chris@559 916 }
Chris@559 917 }
Chris@559 918
Chris@559 919 // If a negative portNumber was used, return the port count.
Chris@559 920 if ( portNumber < 0 ) return count;
Chris@559 921 return 0;
Chris@559 922 }
Chris@559 923
Chris@565 924 void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName )
Chris@559 925 {
Chris@559 926 if ( connected_ ) {
Chris@559 927 errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
Chris@559 928 error( RtError::WARNING );
Chris@559 929 return;
Chris@559 930 }
Chris@559 931
Chris@559 932 unsigned int nSrc = this->getPortCount();
Chris@559 933 if (nSrc < 1) {
Chris@559 934 errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
Chris@559 935 error( RtError::NO_DEVICES_FOUND );
Chris@559 936 }
Chris@559 937
Chris@565 938 snd_seq_port_info_t *pinfo;
Chris@565 939 snd_seq_port_info_alloca( &pinfo );
Chris@559 940 std::ostringstream ost;
Chris@559 941 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 942 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
Chris@559 943 ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 944 errorString_ = ost.str();
Chris@559 945 error( RtError::INVALID_PARAMETER );
Chris@559 946 }
Chris@559 947
Chris@559 948
Chris@559 949 snd_seq_addr_t sender, receiver;
Chris@1038 950 sender.client = (unsigned char)snd_seq_port_info_get_client( pinfo );
Chris@1038 951 sender.port = (unsigned char)snd_seq_port_info_get_port( pinfo );
Chris@1038 952 receiver.client = (unsigned char)snd_seq_client_id( data->seq );
Chris@559 953 if ( data->vport < 0 ) {
Chris@559 954 snd_seq_port_info_set_client( pinfo, 0 );
Chris@559 955 snd_seq_port_info_set_port( pinfo, 0 );
Chris@559 956 snd_seq_port_info_set_capability( pinfo,
Chris@559 957 SND_SEQ_PORT_CAP_WRITE |
Chris@559 958 SND_SEQ_PORT_CAP_SUBS_WRITE );
Chris@559 959 snd_seq_port_info_set_type( pinfo,
Chris@559 960 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
Chris@559 961 SND_SEQ_PORT_TYPE_APPLICATION );
Chris@559 962 snd_seq_port_info_set_midi_channels(pinfo, 16);
Chris@559 963 snd_seq_port_info_set_timestamping(pinfo, 1);
Chris@559 964 snd_seq_port_info_set_timestamp_real(pinfo, 1);
Chris@559 965 snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
Chris@565 966 snd_seq_port_info_set_name(pinfo, portName.c_str() );
Chris@559 967 data->vport = snd_seq_create_port(data->seq, pinfo);
Chris@559 968
Chris@559 969 if ( data->vport < 0 ) {
Chris@559 970 errorString_ = "RtMidiIn::openPort: ALSA error creating input port.";
Chris@559 971 error( RtError::DRIVER_ERROR );
Chris@559 972 }
Chris@559 973 }
Chris@559 974
Chris@1038 975 receiver.port = (unsigned char)data->vport;
Chris@559 976
Chris@559 977 // Make subscription
Chris@559 978 snd_seq_port_subscribe_malloc( &data->subscription );
Chris@559 979 snd_seq_port_subscribe_set_sender(data->subscription, &sender);
Chris@559 980 snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
Chris@559 981 if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
Chris@559 982 errorString_ = "RtMidiIn::openPort: ALSA error making port connection.";
Chris@559 983 error( RtError::DRIVER_ERROR );
Chris@559 984 }
Chris@559 985
Chris@559 986 if ( inputData_.doInput == false ) {
Chris@559 987 // Start the input queue
Chris@559 988 snd_seq_start_queue( data->seq, data->queue_id, NULL );
Chris@559 989 snd_seq_drain_output( data->seq );
Chris@559 990 // Start our MIDI input thread.
Chris@559 991 pthread_attr_t attr;
Chris@559 992 pthread_attr_init(&attr);
Chris@559 993 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
Chris@559 994 pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
Chris@559 995
Chris@559 996 inputData_.doInput = true;
Chris@559 997 int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
Chris@559 998 pthread_attr_destroy(&attr);
Chris@559 999 if (err) {
Chris@559 1000 snd_seq_unsubscribe_port( data->seq, data->subscription );
Chris@559 1001 snd_seq_port_subscribe_free( data->subscription );
Chris@559 1002 inputData_.doInput = false;
Chris@559 1003 errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!";
Chris@559 1004 error( RtError::THREAD_ERROR );
Chris@559 1005 }
Chris@559 1006 }
Chris@559 1007
Chris@559 1008 connected_ = true;
Chris@559 1009 }
Chris@559 1010
Chris@559 1011 void RtMidiIn :: openVirtualPort( std::string portName )
Chris@559 1012 {
Chris@559 1013 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1014 if ( data->vport < 0 ) {
Chris@559 1015 snd_seq_port_info_t *pinfo;
Chris@559 1016 snd_seq_port_info_alloca( &pinfo );
Chris@559 1017 snd_seq_port_info_set_capability( pinfo,
Chris@559 1018 SND_SEQ_PORT_CAP_WRITE |
Chris@559 1019 SND_SEQ_PORT_CAP_SUBS_WRITE );
Chris@559 1020 snd_seq_port_info_set_type( pinfo,
Chris@559 1021 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
Chris@559 1022 SND_SEQ_PORT_TYPE_APPLICATION );
Chris@559 1023 snd_seq_port_info_set_midi_channels(pinfo, 16);
Chris@559 1024 snd_seq_port_info_set_timestamping(pinfo, 1);
Chris@559 1025 snd_seq_port_info_set_timestamp_real(pinfo, 1);
Chris@559 1026 snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
Chris@559 1027 snd_seq_port_info_set_name(pinfo, portName.c_str());
Chris@559 1028 data->vport = snd_seq_create_port(data->seq, pinfo);
Chris@559 1029
Chris@559 1030 if ( data->vport < 0 ) {
Chris@559 1031 errorString_ = "RtMidiIn::openVirtualPort: ALSA error creating virtual port.";
Chris@559 1032 error( RtError::DRIVER_ERROR );
Chris@559 1033 }
Chris@559 1034 }
Chris@559 1035
Chris@559 1036 if ( inputData_.doInput == false ) {
Chris@559 1037 // Start the input queue
Chris@559 1038 snd_seq_start_queue( data->seq, data->queue_id, NULL );
Chris@559 1039 snd_seq_drain_output( data->seq );
Chris@559 1040 // Start our MIDI input thread.
Chris@559 1041 pthread_attr_t attr;
Chris@559 1042 pthread_attr_init(&attr);
Chris@559 1043 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
Chris@559 1044 pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
Chris@559 1045
Chris@559 1046 inputData_.doInput = true;
Chris@559 1047 int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
Chris@559 1048 pthread_attr_destroy(&attr);
Chris@559 1049 if (err) {
Chris@559 1050 snd_seq_unsubscribe_port( data->seq, data->subscription );
Chris@559 1051 snd_seq_port_subscribe_free( data->subscription );
Chris@559 1052 inputData_.doInput = false;
Chris@559 1053 errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!";
Chris@559 1054 error( RtError::THREAD_ERROR );
Chris@559 1055 }
Chris@559 1056 }
Chris@559 1057 }
Chris@559 1058
Chris@559 1059 void RtMidiIn :: closePort( void )
Chris@559 1060 {
Chris@559 1061 if ( connected_ ) {
Chris@559 1062 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1063 snd_seq_unsubscribe_port( data->seq, data->subscription );
Chris@559 1064 snd_seq_port_subscribe_free( data->subscription );
Chris@559 1065 // Stop the input queue
Chris@559 1066 snd_seq_stop_queue( data->seq, data->queue_id, NULL );
Chris@559 1067 snd_seq_drain_output( data->seq );
Chris@559 1068 connected_ = false;
Chris@559 1069 }
Chris@559 1070 }
Chris@559 1071
Chris@559 1072 RtMidiIn :: ~RtMidiIn()
Chris@559 1073 {
Chris@559 1074 // Close a connection if it exists.
Chris@559 1075 closePort();
Chris@559 1076
Chris@559 1077 // Shutdown the input thread.
Chris@559 1078 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1079 if ( inputData_.doInput ) {
Chris@559 1080 inputData_.doInput = false;
Chris@559 1081 pthread_join( data->thread, NULL );
Chris@559 1082 }
Chris@559 1083
Chris@559 1084 // Cleanup.
Chris@559 1085 if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
Chris@559 1086 snd_seq_free_queue( data->seq, data->queue_id );
Chris@559 1087 snd_seq_close( data->seq );
Chris@559 1088 delete data;
Chris@559 1089 }
Chris@559 1090
Chris@559 1091 unsigned int RtMidiIn :: getPortCount()
Chris@559 1092 {
Chris@559 1093 snd_seq_port_info_t *pinfo;
Chris@559 1094 snd_seq_port_info_alloca( &pinfo );
Chris@559 1095
Chris@559 1096 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1097 return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
Chris@559 1098 }
Chris@559 1099
Chris@559 1100 std::string RtMidiIn :: getPortName( unsigned int portNumber )
Chris@559 1101 {
Chris@565 1102 snd_seq_client_info_t *cinfo;
Chris@565 1103 snd_seq_port_info_t *pinfo;
Chris@565 1104 snd_seq_client_info_alloca( &cinfo );
Chris@565 1105 snd_seq_port_info_alloca( &pinfo );
Chris@559 1106
Chris@559 1107 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1108 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
Chris@565 1109 int cnum = snd_seq_port_info_get_client( pinfo );
Chris@565 1110 snd_seq_get_any_client_info( data->seq, cnum, cinfo );
Chris@565 1111 std::ostringstream os;
Chris@565 1112 os << snd_seq_client_info_get_name( cinfo );
Chris@565 1113 os << ":";
Chris@565 1114 os << snd_seq_port_info_get_port( pinfo );
Chris@565 1115 std::string stringName = os.str();
Chris@559 1116 return stringName;
Chris@559 1117 }
Chris@559 1118
Chris@559 1119 // If we get here, we didn't find a match.
Chris@559 1120 errorString_ = "RtMidiIn::getPortName: error looking for port name!";
Chris@559 1121 error( RtError::INVALID_PARAMETER );
Chris@559 1122 return 0;
Chris@559 1123 }
Chris@559 1124
Chris@559 1125 //*********************************************************************//
Chris@559 1126 // API: LINUX ALSA
Chris@559 1127 // Class Definitions: RtMidiOut
Chris@559 1128 //*********************************************************************//
Chris@559 1129
Chris@559 1130 unsigned int RtMidiOut :: getPortCount()
Chris@559 1131 {
Chris@559 1132 snd_seq_port_info_t *pinfo;
Chris@559 1133 snd_seq_port_info_alloca( &pinfo );
Chris@559 1134
Chris@559 1135 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1136 return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
Chris@559 1137 }
Chris@559 1138
Chris@559 1139 std::string RtMidiOut :: getPortName( unsigned int portNumber )
Chris@559 1140 {
Chris@565 1141 snd_seq_client_info_t *cinfo;
Chris@565 1142 snd_seq_port_info_t *pinfo;
Chris@565 1143 snd_seq_client_info_alloca( &cinfo );
Chris@565 1144 snd_seq_port_info_alloca( &pinfo );
Chris@559 1145
Chris@559 1146 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1147 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {
Chris@565 1148 int cnum = snd_seq_port_info_get_client(pinfo);
Chris@565 1149 snd_seq_get_any_client_info( data->seq, cnum, cinfo );
Chris@565 1150 std::ostringstream os;
Chris@565 1151 os << snd_seq_client_info_get_name(cinfo);
Chris@565 1152 os << ":";
Chris@565 1153 os << snd_seq_port_info_get_port(pinfo);
Chris@565 1154 std::string stringName = os.str();
Chris@559 1155 return stringName;
Chris@559 1156 }
Chris@559 1157
Chris@559 1158 // If we get here, we didn't find a match.
Chris@559 1159 errorString_ = "RtMidiOut::getPortName: error looking for port name!";
Chris@559 1160 error( RtError::INVALID_PARAMETER );
Chris@559 1161 return 0;
Chris@559 1162 }
Chris@559 1163
Chris@565 1164 void RtMidiOut :: initialize( const std::string& clientName )
Chris@559 1165 {
Chris@559 1166 // Set up the ALSA sequencer client.
Chris@565 1167 snd_seq_t *seq;
Chris@565 1168 int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
Chris@559 1169 if ( result < 0 ) {
Chris@559 1170 errorString_ = "RtMidiOut::initialize: error creating ALSA sequencer client object.";
Chris@559 1171 error( RtError::DRIVER_ERROR );
Chris@559 1172 }
Chris@559 1173
Chris@559 1174 // Set client name.
Chris@565 1175 snd_seq_set_client_name( seq, clientName.c_str() );
Chris@559 1176
Chris@559 1177 // Save our api-specific connection information.
Chris@559 1178 AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
Chris@559 1179 data->seq = seq;
Chris@559 1180 data->vport = -1;
Chris@559 1181 data->bufferSize = 32;
Chris@559 1182 data->coder = 0;
Chris@559 1183 data->buffer = 0;
Chris@559 1184 result = snd_midi_event_new( data->bufferSize, &data->coder );
Chris@559 1185 if ( result < 0 ) {
Chris@559 1186 delete data;
Chris@559 1187 errorString_ = "RtMidiOut::initialize: error initializing MIDI event parser!\n\n";
Chris@559 1188 error( RtError::DRIVER_ERROR );
Chris@559 1189 }
Chris@559 1190 data->buffer = (unsigned char *) malloc( data->bufferSize );
Chris@559 1191 if ( data->buffer == NULL ) {
Chris@559 1192 delete data;
Chris@559 1193 errorString_ = "RtMidiOut::initialize: error allocating buffer memory!\n\n";
Chris@559 1194 error( RtError::MEMORY_ERROR );
Chris@559 1195 }
Chris@559 1196 snd_midi_event_init( data->coder );
Chris@559 1197 apiData_ = (void *) data;
Chris@559 1198 }
Chris@559 1199
Chris@565 1200 void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName )
Chris@559 1201 {
Chris@559 1202 if ( connected_ ) {
Chris@559 1203 errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
Chris@559 1204 error( RtError::WARNING );
Chris@559 1205 return;
Chris@559 1206 }
Chris@559 1207
Chris@559 1208 unsigned int nSrc = this->getPortCount();
Chris@559 1209 if (nSrc < 1) {
Chris@559 1210 errorString_ = "RtMidiOut::openPort: no MIDI output sources found!";
Chris@559 1211 error( RtError::NO_DEVICES_FOUND );
Chris@559 1212 }
Chris@559 1213
Chris@565 1214 snd_seq_port_info_t *pinfo;
Chris@565 1215 snd_seq_port_info_alloca( &pinfo );
Chris@559 1216 std::ostringstream ost;
Chris@559 1217 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1218 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {
Chris@559 1219 ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1220 errorString_ = ost.str();
Chris@559 1221 error( RtError::INVALID_PARAMETER );
Chris@559 1222 }
Chris@559 1223
Chris@559 1224 snd_seq_addr_t sender, receiver;
Chris@1038 1225 receiver.client = (unsigned char)snd_seq_port_info_get_client( pinfo );
Chris@1038 1226 receiver.port = (unsigned char)snd_seq_port_info_get_port( pinfo );
Chris@1038 1227 sender.client = (unsigned char)snd_seq_client_id( data->seq );
Chris@559 1228
Chris@559 1229 if ( data->vport < 0 ) {
Chris@565 1230 data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
Chris@559 1231 SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
Chris@559 1232 SND_SEQ_PORT_TYPE_MIDI_GENERIC );
Chris@559 1233 if ( data->vport < 0 ) {
Chris@559 1234 errorString_ = "RtMidiOut::openPort: ALSA error creating output port.";
Chris@559 1235 error( RtError::DRIVER_ERROR );
Chris@559 1236 }
Chris@559 1237 }
Chris@559 1238
Chris@1038 1239 sender.port = (unsigned char)data->vport;
Chris@559 1240
Chris@559 1241 // Make subscription
Chris@559 1242 snd_seq_port_subscribe_malloc( &data->subscription );
Chris@559 1243 snd_seq_port_subscribe_set_sender(data->subscription, &sender);
Chris@559 1244 snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
Chris@559 1245 snd_seq_port_subscribe_set_time_update(data->subscription, 1);
Chris@559 1246 snd_seq_port_subscribe_set_time_real(data->subscription, 1);
Chris@559 1247 if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
Chris@559 1248 errorString_ = "RtMidiOut::openPort: ALSA error making port connection.";
Chris@559 1249 error( RtError::DRIVER_ERROR );
Chris@559 1250 }
Chris@559 1251
Chris@559 1252 connected_ = true;
Chris@559 1253 }
Chris@559 1254
Chris@559 1255 void RtMidiOut :: closePort( void )
Chris@559 1256 {
Chris@559 1257 if ( connected_ ) {
Chris@559 1258 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1259 snd_seq_unsubscribe_port( data->seq, data->subscription );
Chris@559 1260 snd_seq_port_subscribe_free( data->subscription );
Chris@559 1261 connected_ = false;
Chris@559 1262 }
Chris@559 1263 }
Chris@559 1264
Chris@559 1265 void RtMidiOut :: openVirtualPort( std::string portName )
Chris@559 1266 {
Chris@559 1267 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1268 if ( data->vport < 0 ) {
Chris@559 1269 data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
Chris@559 1270 SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
Chris@559 1271 SND_SEQ_PORT_TYPE_MIDI_GENERIC );
Chris@559 1272
Chris@559 1273 if ( data->vport < 0 ) {
Chris@559 1274 errorString_ = "RtMidiOut::openVirtualPort: ALSA error creating virtual port.";
Chris@559 1275 error( RtError::DRIVER_ERROR );
Chris@559 1276 }
Chris@559 1277 }
Chris@559 1278 }
Chris@559 1279
Chris@559 1280 RtMidiOut :: ~RtMidiOut()
Chris@559 1281 {
Chris@559 1282 // Close a connection if it exists.
Chris@559 1283 closePort();
Chris@559 1284
Chris@559 1285 // Cleanup.
Chris@559 1286 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@559 1287 if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
Chris@559 1288 if ( data->coder ) snd_midi_event_free( data->coder );
Chris@559 1289 if ( data->buffer ) free( data->buffer );
Chris@559 1290 snd_seq_close( data->seq );
Chris@559 1291 delete data;
Chris@559 1292 }
Chris@559 1293
Chris@559 1294 void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
Chris@559 1295 {
Chris@559 1296 int result;
Chris@559 1297 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
Chris@1038 1298 unsigned int nBytes = (unsigned int) message->size();
Chris@559 1299 if ( nBytes > data->bufferSize ) {
Chris@559 1300 data->bufferSize = nBytes;
Chris@559 1301 result = snd_midi_event_resize_buffer ( data->coder, nBytes);
Chris@559 1302 if ( result != 0 ) {
Chris@559 1303 errorString_ = "RtMidiOut::sendMessage: ALSA error resizing MIDI event buffer.";
Chris@559 1304 error( RtError::DRIVER_ERROR );
Chris@559 1305 }
Chris@559 1306 free (data->buffer);
Chris@559 1307 data->buffer = (unsigned char *) malloc( data->bufferSize );
Chris@559 1308 if ( data->buffer == NULL ) {
Chris@559 1309 errorString_ = "RtMidiOut::initialize: error allocating buffer memory!\n\n";
Chris@559 1310 error( RtError::MEMORY_ERROR );
Chris@559 1311 }
Chris@559 1312 }
Chris@559 1313
Chris@559 1314 snd_seq_event_t ev;
Chris@559 1315 snd_seq_ev_clear(&ev);
Chris@1038 1316 snd_seq_ev_set_source(&ev, (unsigned char) data->vport);
Chris@559 1317 snd_seq_ev_set_subs(&ev);
Chris@559 1318 snd_seq_ev_set_direct(&ev);
Chris@559 1319 for ( unsigned int i=0; i<nBytes; i++ ) data->buffer[i] = message->at(i);
Chris@1038 1320 result = (int) snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );
Chris@559 1321 if ( result < (int)nBytes ) {
Chris@559 1322 errorString_ = "RtMidiOut::sendMessage: event parsing error!";
Chris@559 1323 error( RtError::WARNING );
Chris@559 1324 return;
Chris@559 1325 }
Chris@559 1326
Chris@559 1327 // Send the event.
Chris@559 1328 result = snd_seq_event_output(data->seq, &ev);
Chris@559 1329 if ( result < 0 ) {
Chris@559 1330 errorString_ = "RtMidiOut::sendMessage: error sending MIDI message to port.";
Chris@559 1331 error( RtError::WARNING );
Chris@559 1332 }
Chris@559 1333 snd_seq_drain_output(data->seq);
Chris@559 1334 }
Chris@559 1335
Chris@559 1336 #endif // __LINUX_ALSA__
Chris@559 1337
Chris@559 1338
Chris@559 1339 //*********************************************************************//
Chris@559 1340 // API: IRIX MD
Chris@559 1341 //*********************************************************************//
Chris@559 1342
Chris@559 1343 // API information gleamed from:
Chris@559 1344 // http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?cmd=getdoc&coll=0650&db=man&fname=3%20mdIntro
Chris@559 1345
Chris@559 1346 // If the Makefile doesn't work, try the following:
Chris@559 1347 // CC -o midiinfo -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp midiinfo.cpp -lpthread -lmd
Chris@559 1348 // CC -o midiout -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp midiout.cpp -lpthread -lmd
Chris@559 1349 // CC -o qmidiin -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp qmidiin.cpp -lpthread -lmd
Chris@559 1350 // CC -o cmidiin -LANG:std -D__IRIX_MD__ -I../ ../RtMidi.cpp cmidiin.cpp -lpthread -lmd
Chris@559 1351
Chris@559 1352 #if defined(__IRIX_MD__)
Chris@559 1353
Chris@559 1354 #include <pthread.h>
Chris@559 1355 #include <sys/time.h>
Chris@559 1356 #include <unistd.h>
Chris@559 1357
Chris@559 1358 // Irix MIDI header file.
Chris@559 1359 #include <dmedia/midi.h>
Chris@559 1360
Chris@559 1361 // A structure to hold variables related to the IRIX API
Chris@559 1362 // implementation.
Chris@559 1363 struct IrixMidiData {
Chris@559 1364 MDport port;
Chris@559 1365 pthread_t thread;
Chris@559 1366 };
Chris@559 1367
Chris@559 1368 //*********************************************************************//
Chris@559 1369 // API: IRIX
Chris@559 1370 // Class Definitions: RtMidiIn
Chris@559 1371 //*********************************************************************//
Chris@559 1372
Chris@559 1373 extern "C" void *irixMidiHandler( void *ptr )
Chris@559 1374 {
Chris@559 1375 RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (ptr);
Chris@559 1376 IrixMidiData *apiData = static_cast<IrixMidiData *> (data->apiData);
Chris@559 1377
Chris@559 1378 bool continueSysex = false;
Chris@559 1379 unsigned char status;
Chris@559 1380 unsigned short size;
Chris@559 1381 MDevent event;
Chris@559 1382 int fd = mdGetFd( apiData->port );
Chris@559 1383 if ( fd < 0 ) {
Chris@559 1384 data->doInput = false;
Chris@843 1385 cerr << "\nRtMidiIn::irixMidiHandler: error getting port descriptor!\n\n";
Chris@559 1386 return 0;
Chris@559 1387 }
Chris@559 1388
Chris@559 1389 fd_set mask, rmask;
Chris@559 1390 FD_ZERO( &mask );
Chris@559 1391 FD_SET( fd, &mask );
Chris@559 1392 struct timeval timeout = {0, 0};
Chris@559 1393 RtMidiIn::MidiMessage message;
Chris@559 1394 int result;
Chris@559 1395
Chris@559 1396 while ( data->doInput ) {
Chris@559 1397
Chris@559 1398 rmask = mask;
Chris@559 1399 timeout.tv_sec = 0;
Chris@559 1400 timeout.tv_usec = 0;
Chris@559 1401 if ( select( fd+1, &rmask, NULL, NULL, &timeout ) <= 0 ) {
Chris@559 1402 // No data pending ... sleep a bit.
Chris@559 1403 usleep( 1000 );
Chris@559 1404 continue;
Chris@559 1405 }
Chris@559 1406
Chris@559 1407 // If here, there should be data.
Chris@559 1408 result = mdReceive( apiData->port, &event, 1);
Chris@559 1409 if ( result <= 0 ) {
Chris@843 1410 cerr << "\nRtMidiIn::irixMidiHandler: MIDI input read error!\n\n";
Chris@559 1411 continue;
Chris@559 1412 }
Chris@559 1413
Chris@559 1414 message.timeStamp = event.stamp * 0.000000001;
Chris@559 1415
Chris@559 1416 size = 0;
Chris@559 1417 status = event.msg[0];
Chris@559 1418 if ( !(status & 0x80) ) continue;
Chris@559 1419 if ( status == 0xF0 ) {
Chris@559 1420 // Sysex message ... can be segmented across multiple messages.
Chris@559 1421 if ( !(data->ignoreFlags & 0x01) ) {
Chris@559 1422 if ( continueSysex ) {
Chris@559 1423 // We have a continuing, segmented sysex message. Append
Chris@559 1424 // the new bytes to our existing message.
Chris@559 1425 for ( int i=0; i<event.msglen; i++ )
Chris@559 1426 message.bytes.push_back( event.sysexmsg[i] );
Chris@559 1427 if ( event.sysexmsg[event.msglen-1] == 0xF7 ) continueSysex = false;
Chris@559 1428 if ( !continueSysex ) {
Chris@559 1429 // If not a continuing sysex message, invoke the user callback function or queue the message.
Chris@559 1430 if ( data->usingCallback && message.bytes.size() > 0 ) {
Chris@559 1431 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
Chris@559 1432 callback( message.timeStamp, &message.bytes, data->userData );
Chris@559 1433 }
Chris@559 1434 else {
Chris@559 1435 // As long as we haven't reached our queue size limit, push the message.
Chris@559 1436 if ( data->queueLimit > data->queue.size() )
Chris@559 1437 data->queue.push( message );
Chris@559 1438 else
Chris@843 1439 cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
Chris@559 1440 }
Chris@559 1441 message.bytes.clear();
Chris@559 1442 }
Chris@559 1443 }
Chris@559 1444 }
Chris@559 1445 mdFree( NULL );
Chris@559 1446 continue;
Chris@559 1447 }
Chris@559 1448 else if ( status < 0xC0 ) size = 3;
Chris@559 1449 else if ( status < 0xE0 ) size = 2;
Chris@559 1450 else if ( status < 0xF0 ) size = 3;
Chris@559 1451 else if ( status < 0xF3 ) {
Chris@559 1452 if ( status == 0xF1 && !(data->ignoreFlags & 0x02) ) {
Chris@559 1453 // A MIDI time code message and we're not ignoring it.
Chris@559 1454 size = 3;
Chris@559 1455 }
Chris@559 1456 }
Chris@559 1457 else if ( status == 0xF3 ) size = 2;
Chris@559 1458 else if ( status == 0xF8 ) {
Chris@559 1459 if ( !(data->ignoreFlags & 0x02) ) {
Chris@559 1460 // A MIDI timing tick message and we're not ignoring it.
Chris@559 1461 size = 1;
Chris@559 1462 }
Chris@559 1463 }
Chris@559 1464 else if ( status == 0xFE ) { // MIDI active sensing
Chris@559 1465 if ( !(data->ignoreFlags & 0x04) )
Chris@559 1466 size = 1;
Chris@559 1467 }
Chris@559 1468 else size = 1;
Chris@559 1469
Chris@559 1470 // Copy the MIDI data to our vector.
Chris@559 1471 if ( size ) {
Chris@559 1472 message.bytes.assign( &event.msg[0], &event.msg[size] );
Chris@559 1473 // Invoke the user callback function or queue the message.
Chris@559 1474 if ( data->usingCallback ) {
Chris@559 1475 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
Chris@559 1476 callback( message.timeStamp, &message.bytes, data->userData );
Chris@559 1477 }
Chris@559 1478 else {
Chris@559 1479 // As long as we haven't reached our queue size limit, push the message.
Chris@559 1480 if ( data->queueLimit > data->queue.size() )
Chris@559 1481 data->queue.push( message );
Chris@559 1482 else
Chris@843 1483 cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
Chris@559 1484 }
Chris@559 1485 message.bytes.clear();
Chris@559 1486 }
Chris@559 1487 }
Chris@559 1488
Chris@559 1489 return 0;
Chris@559 1490 }
Chris@559 1491
Chris@565 1492 void RtMidiIn :: initialize( const std::string& /*clientName*/ )
Chris@559 1493 {
Chris@559 1494 // Initialize the Irix MIDI system. At the moment, we will not
Chris@559 1495 // worry about a return value of zero (ports) because there is a
Chris@559 1496 // chance the user could plug something in after instantiation.
Chris@559 1497 int nPorts = mdInit();
Chris@559 1498
Chris@559 1499 // Create our api-specific connection information.
Chris@559 1500 IrixMidiData *data = (IrixMidiData *) new IrixMidiData;
Chris@559 1501 apiData_ = (void *) data;
Chris@559 1502 inputData_.apiData = (void *) data;
Chris@559 1503 }
Chris@559 1504
Chris@565 1505 void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ )
Chris@559 1506 {
Chris@559 1507 if ( connected_ ) {
Chris@559 1508 errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
Chris@559 1509 error( RtError::WARNING );
Chris@559 1510 return;
Chris@559 1511 }
Chris@559 1512
Chris@559 1513 int nPorts = mdInit();
Chris@559 1514 if (nPorts < 1) {
Chris@559 1515 errorString_ = "RtMidiIn::openPort: no Irix MIDI input sources found!";
Chris@559 1516 error( RtError::NO_DEVICES_FOUND );
Chris@559 1517 }
Chris@559 1518
Chris@559 1519 std::ostringstream ost;
Chris@559 1520 if ( portNumber >= nPorts ) {
Chris@559 1521 ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1522 errorString_ = ost.str();
Chris@559 1523 error( RtError::INVALID_PARAMETER );
Chris@559 1524 }
Chris@559 1525
Chris@559 1526 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1527 data->port = mdOpenInPort( mdGetName(portNumber) );
Chris@559 1528 if ( data->port == NULL ) {
Chris@559 1529 ost << "RtMidiIn::openPort: Irix error opening the port (" << portNumber << ").";
Chris@559 1530 errorString_ = ost.str();
Chris@559 1531 error( RtError::DRIVER_ERROR );
Chris@559 1532 }
Chris@559 1533 mdSetStampMode(data->port, MD_DELTASTAMP);
Chris@559 1534
Chris@559 1535 // Start our MIDI input thread.
Chris@559 1536 pthread_attr_t attr;
Chris@559 1537 pthread_attr_init(&attr);
Chris@559 1538 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
Chris@559 1539 pthread_attr_setschedpolicy(&attr, SCHED_RR);
Chris@559 1540
Chris@559 1541 inputData_.doInput = true;
Chris@559 1542 int err = pthread_create(&data->thread, &attr, irixMidiHandler, &inputData_);
Chris@559 1543 pthread_attr_destroy(&attr);
Chris@559 1544 if (err) {
Chris@559 1545 mdClosePort( data->port );
Chris@559 1546 inputData_.doInput = false;
Chris@559 1547 errorString_ = "RtMidiIn::openPort: error starting MIDI input thread!";
Chris@559 1548 error( RtError::THREAD_ERROR );
Chris@559 1549 }
Chris@559 1550
Chris@559 1551 connected_ = true;
Chris@559 1552 }
Chris@559 1553
Chris@559 1554 void RtMidiIn :: openVirtualPort( std::string portName )
Chris@559 1555 {
Chris@559 1556 // This function cannot be implemented for the Irix MIDI API.
Chris@559 1557 errorString_ = "RtMidiIn::openVirtualPort: cannot be implemented in Irix MIDI API!";
Chris@559 1558 error( RtError::WARNING );
Chris@559 1559 }
Chris@559 1560
Chris@559 1561 void RtMidiIn :: closePort( void )
Chris@559 1562 {
Chris@559 1563 if ( connected_ ) {
Chris@559 1564 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1565 mdClosePort( data->port );
Chris@559 1566 connected_ = false;
Chris@559 1567
Chris@559 1568 // Shutdown the input thread.
Chris@559 1569 inputData_.doInput = false;
Chris@559 1570 pthread_join( data->thread, NULL );
Chris@559 1571 }
Chris@559 1572 }
Chris@559 1573
Chris@559 1574 RtMidiIn :: ~RtMidiIn()
Chris@559 1575 {
Chris@559 1576 // Close a connection if it exists.
Chris@559 1577 closePort();
Chris@559 1578
Chris@559 1579 // Cleanup.
Chris@559 1580 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1581 delete data;
Chris@559 1582 }
Chris@559 1583
Chris@559 1584 unsigned int RtMidiIn :: getPortCount()
Chris@559 1585 {
Chris@559 1586 int nPorts = mdInit();
Chris@559 1587 if ( nPorts >= 0 ) return nPorts;
Chris@559 1588 else return 0;
Chris@559 1589 }
Chris@559 1590
Chris@559 1591 std::string RtMidiIn :: getPortName( unsigned int portNumber )
Chris@559 1592 {
Chris@559 1593 int nPorts = mdInit();
Chris@559 1594
Chris@559 1595 std::ostringstream ost;
Chris@559 1596 if ( portNumber >= nPorts ) {
Chris@559 1597 ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1598 errorString_ = ost.str();
Chris@559 1599 error( RtError::INVALID_PARAMETER );
Chris@559 1600 }
Chris@559 1601
Chris@559 1602 std::string stringName = std::string( mdGetName( portNumber ) );
Chris@559 1603 return stringName;
Chris@559 1604 }
Chris@559 1605
Chris@559 1606 //*********************************************************************//
Chris@559 1607 // API: IRIX MD
Chris@559 1608 // Class Definitions: RtMidiOut
Chris@559 1609 //*********************************************************************//
Chris@559 1610
Chris@559 1611 unsigned int RtMidiOut :: getPortCount()
Chris@559 1612 {
Chris@559 1613 int nPorts = mdInit();
Chris@559 1614 if ( nPorts >= 0 ) return nPorts;
Chris@559 1615 else return 0;
Chris@559 1616 }
Chris@559 1617
Chris@559 1618 std::string RtMidiOut :: getPortName( unsigned int portNumber )
Chris@559 1619 {
Chris@559 1620 int nPorts = mdInit();
Chris@559 1621
Chris@559 1622 std::ostringstream ost;
Chris@559 1623 if ( portNumber >= nPorts ) {
Chris@559 1624 ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1625 errorString_ = ost.str();
Chris@559 1626 error( RtError::INVALID_PARAMETER );
Chris@559 1627 }
Chris@559 1628
Chris@559 1629 std::string stringName = std::string( mdGetName( portNumber ) );
Chris@559 1630 return stringName;
Chris@559 1631 }
Chris@559 1632
Chris@565 1633 void RtMidiOut :: initialize( const std::string& /*clientName*/ )
Chris@559 1634 {
Chris@559 1635 // Initialize the Irix MIDI system. At the moment, we will not
Chris@559 1636 // worry about a return value of zero (ports) because there is a
Chris@559 1637 // chance the user could plug something in after instantiation.
Chris@559 1638 int nPorts = mdInit();
Chris@559 1639
Chris@559 1640 // Create our api-specific connection information.
Chris@559 1641 IrixMidiData *data = (IrixMidiData *) new IrixMidiData;
Chris@559 1642 apiData_ = (void *) data;
Chris@559 1643 }
Chris@559 1644
Chris@565 1645 void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ )
Chris@559 1646 {
Chris@559 1647 if ( connected_ ) {
Chris@559 1648 errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
Chris@559 1649 error( RtError::WARNING );
Chris@559 1650 return;
Chris@559 1651 }
Chris@559 1652
Chris@559 1653 int nPorts = mdInit();
Chris@559 1654 if (nPorts < 1) {
Chris@559 1655 errorString_ = "RtMidiOut::openPort: no Irix MIDI output sources found!";
Chris@559 1656 error( RtError::NO_DEVICES_FOUND );
Chris@559 1657 }
Chris@559 1658
Chris@559 1659 std::ostringstream ost;
Chris@559 1660 if ( portNumber >= nPorts ) {
Chris@559 1661 ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1662 errorString_ = ost.str();
Chris@559 1663 error( RtError::INVALID_PARAMETER );
Chris@559 1664 }
Chris@559 1665
Chris@559 1666 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1667 data->port = mdOpenOutPort( mdGetName(portNumber) );
Chris@559 1668 if ( data->port == NULL ) {
Chris@559 1669 ost << "RtMidiOut::openPort: Irix error opening the port (" << portNumber << ").";
Chris@559 1670 errorString_ = ost.str();
Chris@559 1671 error( RtError::DRIVER_ERROR );
Chris@559 1672 }
Chris@559 1673 mdSetStampMode(data->port, MD_NOSTAMP);
Chris@559 1674
Chris@559 1675 connected_ = true;
Chris@559 1676 }
Chris@559 1677
Chris@559 1678 void RtMidiOut :: closePort( void )
Chris@559 1679 {
Chris@559 1680 if ( connected_ ) {
Chris@559 1681 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1682 mdClosePort( data->port );
Chris@559 1683 connected_ = false;
Chris@559 1684 }
Chris@559 1685 }
Chris@559 1686
Chris@559 1687 void RtMidiOut :: openVirtualPort( std::string portName )
Chris@559 1688 {
Chris@559 1689 // This function cannot be implemented for the Irix MIDI API.
Chris@559 1690 errorString_ = "RtMidiOut::openVirtualPort: cannot be implemented in Irix MIDI API!";
Chris@559 1691 error( RtError::WARNING );
Chris@559 1692 }
Chris@559 1693
Chris@559 1694 RtMidiOut :: ~RtMidiOut()
Chris@559 1695 {
Chris@559 1696 // Close a connection if it exists.
Chris@559 1697 closePort();
Chris@559 1698
Chris@559 1699 // Cleanup.
Chris@559 1700 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1701 delete data;
Chris@559 1702 }
Chris@559 1703
Chris@559 1704 void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
Chris@559 1705 {
Chris@559 1706 int result;
Chris@559 1707 MDevent event;
Chris@559 1708 IrixMidiData *data = static_cast<IrixMidiData *> (apiData_);
Chris@559 1709 char *buffer = 0;
Chris@559 1710
Chris@559 1711 unsigned int nBytes = message->size();
Chris@559 1712 if ( nBytes == 0 ) return;
Chris@559 1713 event.stamp = 0;
Chris@559 1714 if ( message->at(0) == 0xF0 ) {
Chris@559 1715 if ( nBytes < 3 ) return; // check for bogus sysex
Chris@559 1716 event.msg[0] = 0xF0;
Chris@559 1717 event.msglen = nBytes;
Chris@559 1718 buffer = (char *) malloc( nBytes );
Chris@559 1719 for ( int i=0; i<nBytes; i++ ) buffer[i] = message->at(i);
Chris@559 1720 event.sysexmsg = buffer;
Chris@559 1721 }
Chris@559 1722 else {
Chris@559 1723 for ( int i=0; i<nBytes; i++ )
Chris@559 1724 event.msg[i] = message->at(i);
Chris@559 1725 }
Chris@559 1726
Chris@559 1727 // Send the event.
Chris@559 1728 result = mdSend( data->port, &event, 1 );
Chris@559 1729 if ( buffer ) free( buffer );
Chris@559 1730 if ( result < 1 ) {
Chris@559 1731 errorString_ = "RtMidiOut::sendMessage: IRIX error sending MIDI message!";
Chris@559 1732 error( RtError::WARNING );
Chris@559 1733 return;
Chris@559 1734 }
Chris@559 1735 }
Chris@559 1736
Chris@559 1737 #endif // __IRIX_MD__
Chris@559 1738
Chris@559 1739 //*********************************************************************//
Chris@559 1740 // API: Windows Multimedia Library (MM)
Chris@559 1741 //*********************************************************************//
Chris@559 1742
Chris@559 1743 // API information deciphered from:
Chris@559 1744 // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp
Chris@559 1745
Chris@559 1746 // Thanks to Jean-Baptiste Berruchon for the sysex code.
Chris@559 1747
Chris@559 1748 #if defined(__WINDOWS_MM__)
Chris@559 1749
Chris@559 1750 // The Windows MM API is based on the use of a callback function for
Chris@559 1751 // MIDI input. We convert the system specific time stamps to delta
Chris@559 1752 // time values.
Chris@559 1753
Chris@559 1754 // Windows MM MIDI header files.
Chris@559 1755 #include <windows.h>
Chris@559 1756 #include <mmsystem.h>
Chris@559 1757
Chris@559 1758 // A structure to hold variables related to the CoreMIDI API
Chris@559 1759 // implementation.
Chris@559 1760 struct WinMidiData {
Chris@559 1761 HMIDIIN inHandle; // Handle to Midi Input Device
Chris@559 1762 HMIDIOUT outHandle; // Handle to Midi Output Device
Chris@559 1763 DWORD lastTime;
Chris@559 1764 RtMidiIn::MidiMessage message;
Chris@559 1765 LPMIDIHDR sysexBuffer;
Chris@559 1766 };
Chris@559 1767
Chris@559 1768 #define RT_SYSEX_BUFFER_SIZE 1024
Chris@559 1769
Chris@559 1770 //*********************************************************************//
Chris@559 1771 // API: Windows MM
Chris@559 1772 // Class Definitions: RtMidiIn
Chris@559 1773 //*********************************************************************//
Chris@559 1774
Chris@559 1775 static void CALLBACK midiInputCallback( HMIDIOUT hmin,
Chris@559 1776 UINT inputStatus,
Chris@559 1777 DWORD instancePtr,
Chris@559 1778 DWORD midiMessage,
Chris@559 1779 DWORD timestamp )
Chris@559 1780 {
Chris@559 1781 if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA ) return;
Chris@559 1782
Chris@559 1783 //RtMidiIn::RtMidiInData *data = static_cast<RtMidiIn::RtMidiInData *> (instancePtr);
Chris@559 1784 RtMidiIn::RtMidiInData *data = (RtMidiIn::RtMidiInData *)instancePtr;
Chris@559 1785 WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);
Chris@559 1786
Chris@559 1787 // Calculate time stamp.
Chris@559 1788 apiData->message.timeStamp = 0.0;
Chris@559 1789 if ( data->firstMessage == true ) data->firstMessage = false;
Chris@559 1790 else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;
Chris@559 1791 apiData->lastTime = timestamp;
Chris@559 1792
Chris@559 1793 if ( inputStatus == MIM_DATA ) { // Channel or system message
Chris@559 1794
Chris@559 1795 // Make sure the first byte is a status byte.
Chris@559 1796 unsigned char status = (unsigned char) (midiMessage & 0x000000FF);
Chris@559 1797 if ( !(status & 0x80) ) return;
Chris@559 1798
Chris@559 1799 // Determine the number of bytes in the MIDI message.
Chris@559 1800 unsigned short nBytes = 1;
Chris@559 1801 if ( status < 0xC0 ) nBytes = 3;
Chris@559 1802 else if ( status < 0xE0 ) nBytes = 2;
Chris@559 1803 else if ( status < 0xF0 ) nBytes = 3;
Chris@559 1804 else if ( status < 0xF3 ) {
Chris@559 1805 // A MIDI time code message and we're ignoring it.
Chris@559 1806 if ( status == 0xF1 && (data->ignoreFlags & 0x02) ) return;
Chris@559 1807 nBytes = 3;
Chris@559 1808 }
Chris@559 1809 else if ( status == 0xF3 ) nBytes = 2;
Chris@559 1810 else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) {
Chris@559 1811 // A MIDI timing tick message and we're ignoring it.
Chris@559 1812 return;
Chris@559 1813 }
Chris@559 1814 else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
Chris@559 1815 // A MIDI active sensing message and we're ignoring it.
Chris@559 1816 return;
Chris@559 1817 }
Chris@559 1818
Chris@559 1819 // Copy bytes to our MIDI message.
Chris@559 1820 unsigned char *ptr = (unsigned char *) &midiMessage;
Chris@559 1821 for ( int i=0; i<nBytes; i++ ) apiData->message.bytes.push_back( *ptr++ );
Chris@559 1822 }
Chris@565 1823 else { // Sysex message ( MIM_LONGDATA )
Chris@565 1824 MIDIHDR *sysex = ( MIDIHDR *) midiMessage;
Chris@565 1825 if ( !( data->ignoreFlags & 0x01 ) ) {
Chris@565 1826 // Sysex message and we're not ignoring it
Chris@565 1827 for ( int i=0; i<(int)sysex->dwBytesRecorded; i++ )
Chris@565 1828 apiData->message.bytes.push_back( sysex->lpData[i] );
Chris@565 1829 }
Chris@559 1830
Chris@565 1831 // The WinMM API requires that the sysex buffer be requeued after
Chris@565 1832 // input of each sysex message. Even if we are ignoring sysex
Chris@565 1833 // messages, we still need to requeue the buffer in case the user
Chris@565 1834 // decides to not ignore sysex messages in the future. However,
Chris@565 1835 // it seems that WinMM calls this function with an empty sysex
Chris@565 1836 // buffer when an application closes and in this case, we should
Chris@565 1837 // avoid requeueing it, else the computer suddenly reboots after
Chris@565 1838 // one or two minutes.
Chris@559 1839 if ( apiData->sysexBuffer->dwBytesRecorded > 0 ) {
Chris@565 1840 //if ( sysex->dwBytesRecorded > 0 ) {
Chris@559 1841 MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer, sizeof(MIDIHDR) );
Chris@559 1842 if ( result != MMSYSERR_NOERROR )
Chris@843 1843 cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";
Chris@565 1844
Chris@565 1845 if ( data->ignoreFlags & 0x01 ) return;
Chris@559 1846 }
Chris@565 1847 else return;
Chris@559 1848 }
Chris@559 1849
Chris@559 1850 if ( data->usingCallback ) {
Chris@559 1851 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
Chris@559 1852 callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData );
Chris@559 1853 }
Chris@559 1854 else {
Chris@559 1855 // As long as we haven't reached our queue size limit, push the message.
Chris@559 1856 if ( data->queueLimit > data->queue.size() )
Chris@559 1857 data->queue.push( apiData->message );
Chris@559 1858 else
Chris@843 1859 cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
Chris@559 1860 }
Chris@559 1861
Chris@565 1862 // Clear the vector for the next input message.
Chris@559 1863 apiData->message.bytes.clear();
Chris@559 1864 }
Chris@559 1865
Chris@565 1866 void RtMidiIn :: initialize( const std::string& /*clientName*/ )
Chris@559 1867 {
Chris@559 1868 // We'll issue a warning here if no devices are available but not
Chris@559 1869 // throw an error since the user can plugin something later.
Chris@559 1870 unsigned int nDevices = midiInGetNumDevs();
Chris@559 1871 if ( nDevices == 0 ) {
Chris@559 1872 errorString_ = "RtMidiIn::initialize: no MIDI input devices currently available.";
Chris@559 1873 error( RtError::WARNING );
Chris@559 1874 }
Chris@559 1875
Chris@559 1876 // Save our api-specific connection information.
Chris@559 1877 WinMidiData *data = (WinMidiData *) new WinMidiData;
Chris@559 1878 apiData_ = (void *) data;
Chris@559 1879 inputData_.apiData = (void *) data;
Chris@559 1880 data->message.bytes.clear(); // needs to be empty for first input message
Chris@559 1881 }
Chris@559 1882
Chris@565 1883 void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ )
Chris@559 1884 {
Chris@559 1885 if ( connected_ ) {
Chris@559 1886 errorString_ = "RtMidiIn::openPort: a valid connection already exists!";
Chris@559 1887 error( RtError::WARNING );
Chris@559 1888 return;
Chris@559 1889 }
Chris@559 1890
Chris@559 1891 unsigned int nDevices = midiInGetNumDevs();
Chris@559 1892 if (nDevices == 0) {
Chris@559 1893 errorString_ = "RtMidiIn::openPort: no MIDI input sources found!";
Chris@559 1894 error( RtError::NO_DEVICES_FOUND );
Chris@559 1895 }
Chris@559 1896
Chris@559 1897 std::ostringstream ost;
Chris@559 1898 if ( portNumber >= nDevices ) {
Chris@559 1899 ost << "RtMidiIn::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1900 errorString_ = ost.str();
Chris@559 1901 error( RtError::INVALID_PARAMETER );
Chris@559 1902 }
Chris@559 1903
Chris@559 1904 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 1905 MMRESULT result = midiInOpen( &data->inHandle,
Chris@559 1906 portNumber,
Chris@559 1907 (DWORD)&midiInputCallback,
Chris@559 1908 (DWORD)&inputData_,
Chris@559 1909 CALLBACK_FUNCTION );
Chris@559 1910 if ( result != MMSYSERR_NOERROR ) {
Chris@559 1911 errorString_ = "RtMidiIn::openPort: error creating Windows MM MIDI input port.";
Chris@559 1912 error( RtError::DRIVER_ERROR );
Chris@559 1913 }
Chris@559 1914
Chris@559 1915 // Allocate and init the sysex buffer.
Chris@559 1916 data->sysexBuffer = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
Chris@565 1917 data->sysexBuffer->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];
Chris@565 1918 data->sysexBuffer->dwBufferLength = RT_SYSEX_BUFFER_SIZE;
Chris@559 1919 data->sysexBuffer->dwFlags = 0;
Chris@559 1920
Chris@559 1921 result = midiInPrepareHeader( data->inHandle, data->sysexBuffer, sizeof(MIDIHDR) );
Chris@559 1922 if ( result != MMSYSERR_NOERROR ) {
Chris@559 1923 midiInClose( data->inHandle );
Chris@559 1924 errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port (PrepareHeader).";
Chris@559 1925 error( RtError::DRIVER_ERROR );
Chris@559 1926 }
Chris@559 1927
Chris@559 1928 // Register the buffer.
Chris@559 1929 result = midiInAddBuffer( data->inHandle, data->sysexBuffer, sizeof(MIDIHDR) );
Chris@559 1930 if ( result != MMSYSERR_NOERROR ) {
Chris@559 1931 midiInClose( data->inHandle );
Chris@559 1932 errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port (AddBuffer).";
Chris@559 1933 error( RtError::DRIVER_ERROR );
Chris@559 1934 }
Chris@559 1935
Chris@559 1936 result = midiInStart( data->inHandle );
Chris@559 1937 if ( result != MMSYSERR_NOERROR ) {
Chris@559 1938 midiInClose( data->inHandle );
Chris@559 1939 errorString_ = "RtMidiIn::openPort: error starting Windows MM MIDI input port.";
Chris@559 1940 error( RtError::DRIVER_ERROR );
Chris@559 1941 }
Chris@559 1942
Chris@559 1943 connected_ = true;
Chris@559 1944 }
Chris@559 1945
Chris@559 1946 void RtMidiIn :: openVirtualPort( std::string portName )
Chris@559 1947 {
Chris@559 1948 // This function cannot be implemented for the Windows MM MIDI API.
Chris@559 1949 errorString_ = "RtMidiIn::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
Chris@559 1950 error( RtError::WARNING );
Chris@559 1951 }
Chris@559 1952
Chris@559 1953 void RtMidiIn :: closePort( void )
Chris@559 1954 {
Chris@559 1955 if ( connected_ ) {
Chris@559 1956 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 1957 midiInReset( data->inHandle );
Chris@559 1958 midiInStop( data->inHandle );
Chris@559 1959
Chris@559 1960 int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer, sizeof(MIDIHDR));
Chris@559 1961 delete [] data->sysexBuffer->lpData;
Chris@559 1962 delete [] data->sysexBuffer;
Chris@559 1963 if ( result != MMSYSERR_NOERROR ) {
Chris@559 1964 midiInClose( data->inHandle );
Chris@559 1965 errorString_ = "RtMidiIn::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";
Chris@559 1966 error( RtError::DRIVER_ERROR );
Chris@559 1967 }
Chris@559 1968
Chris@559 1969 midiInClose( data->inHandle );
Chris@559 1970 connected_ = false;
Chris@559 1971 }
Chris@559 1972 }
Chris@559 1973
Chris@559 1974 RtMidiIn :: ~RtMidiIn()
Chris@559 1975 {
Chris@559 1976 // Close a connection if it exists.
Chris@559 1977 closePort();
Chris@559 1978
Chris@559 1979 // Cleanup.
Chris@559 1980 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 1981 delete data;
Chris@559 1982 }
Chris@559 1983
Chris@559 1984 unsigned int RtMidiIn :: getPortCount()
Chris@559 1985 {
Chris@559 1986 return midiInGetNumDevs();
Chris@559 1987 }
Chris@559 1988
Chris@559 1989 std::string RtMidiIn :: getPortName( unsigned int portNumber )
Chris@559 1990 {
Chris@559 1991 unsigned int nDevices = midiInGetNumDevs();
Chris@559 1992 if ( portNumber >= nDevices ) {
Chris@559 1993 std::ostringstream ost;
Chris@559 1994 ost << "RtMidiIn::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 1995 errorString_ = ost.str();
Chris@559 1996 error( RtError::INVALID_PARAMETER );
Chris@559 1997 }
Chris@559 1998
Chris@559 1999 MIDIINCAPS deviceCaps;
Chris@559 2000 midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));
Chris@559 2001
Chris@559 2002 // For some reason, we need to copy character by character with
Chris@559 2003 // UNICODE (thanks to Eduardo Coutinho!).
Chris@559 2004 //std::string stringName = std::string( deviceCaps.szPname );
Chris@559 2005 char nameString[MAXPNAMELEN];
Chris@559 2006 for( int i=0; i<MAXPNAMELEN; i++ )
Chris@559 2007 nameString[i] = (char)( deviceCaps.szPname[i] );
Chris@559 2008
Chris@559 2009 std::string stringName( nameString );
Chris@559 2010 return stringName;
Chris@559 2011 }
Chris@559 2012
Chris@559 2013 //*********************************************************************//
Chris@559 2014 // API: Windows MM
Chris@559 2015 // Class Definitions: RtMidiOut
Chris@559 2016 //*********************************************************************//
Chris@559 2017
Chris@559 2018 unsigned int RtMidiOut :: getPortCount()
Chris@559 2019 {
Chris@559 2020 return midiOutGetNumDevs();
Chris@559 2021 }
Chris@559 2022
Chris@559 2023 std::string RtMidiOut :: getPortName( unsigned int portNumber )
Chris@559 2024 {
Chris@559 2025 unsigned int nDevices = midiOutGetNumDevs();
Chris@559 2026 if ( portNumber >= nDevices ) {
Chris@559 2027 std::ostringstream ost;
Chris@559 2028 ost << "RtMidiOut::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 2029 errorString_ = ost.str();
Chris@559 2030 error( RtError::INVALID_PARAMETER );
Chris@559 2031 }
Chris@559 2032
Chris@559 2033 MIDIOUTCAPS deviceCaps;
Chris@559 2034 midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS));
Chris@559 2035
Chris@559 2036 // For some reason, we need to copy character by character with
Chris@559 2037 // UNICODE (thanks to Eduardo Coutinho!).
Chris@559 2038 //std::string stringName = std::string( deviceCaps.szPname );
Chris@559 2039 char nameString[MAXPNAMELEN];
Chris@559 2040 for( int i=0; i<MAXPNAMELEN; i++ )
Chris@559 2041 nameString[i] = (char)( deviceCaps.szPname[i] );
Chris@559 2042
Chris@559 2043 std::string stringName( nameString );
Chris@559 2044 return stringName;
Chris@559 2045 }
Chris@559 2046
Chris@565 2047 void RtMidiOut :: initialize( const std::string& /*clientName*/ )
Chris@559 2048 {
Chris@559 2049 // We'll issue a warning here if no devices are available but not
Chris@559 2050 // throw an error since the user can plug something in later.
Chris@559 2051 unsigned int nDevices = midiOutGetNumDevs();
Chris@559 2052 if ( nDevices == 0 ) {
Chris@559 2053 errorString_ = "RtMidiOut::initialize: no MIDI output devices currently available.";
Chris@559 2054 error( RtError::WARNING );
Chris@559 2055 }
Chris@559 2056
Chris@559 2057 // Save our api-specific connection information.
Chris@559 2058 WinMidiData *data = (WinMidiData *) new WinMidiData;
Chris@559 2059 apiData_ = (void *) data;
Chris@559 2060 }
Chris@559 2061
Chris@565 2062 void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ )
Chris@559 2063 {
Chris@559 2064 if ( connected_ ) {
Chris@559 2065 errorString_ = "RtMidiOut::openPort: a valid connection already exists!";
Chris@559 2066 error( RtError::WARNING );
Chris@559 2067 return;
Chris@559 2068 }
Chris@559 2069
Chris@559 2070 unsigned int nDevices = midiOutGetNumDevs();
Chris@559 2071 if (nDevices < 1) {
Chris@559 2072 errorString_ = "RtMidiOut::openPort: no MIDI output destinations found!";
Chris@559 2073 error( RtError::NO_DEVICES_FOUND );
Chris@559 2074 }
Chris@559 2075
Chris@559 2076 std::ostringstream ost;
Chris@559 2077 if ( portNumber >= nDevices ) {
Chris@559 2078 ost << "RtMidiOut::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
Chris@559 2079 errorString_ = ost.str();
Chris@559 2080 error( RtError::INVALID_PARAMETER );
Chris@559 2081 }
Chris@559 2082
Chris@559 2083 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 2084 MMRESULT result = midiOutOpen( &data->outHandle,
Chris@559 2085 portNumber,
Chris@559 2086 (DWORD)NULL,
Chris@559 2087 (DWORD)NULL,
Chris@559 2088 CALLBACK_NULL );
Chris@559 2089 if ( result != MMSYSERR_NOERROR ) {
Chris@559 2090 errorString_ = "RtMidiOut::openPort: error creating Windows MM MIDI output port.";
Chris@559 2091 error( RtError::DRIVER_ERROR );
Chris@559 2092 }
Chris@559 2093
Chris@559 2094 connected_ = true;
Chris@559 2095 }
Chris@559 2096
Chris@559 2097 void RtMidiOut :: closePort( void )
Chris@559 2098 {
Chris@559 2099 if ( connected_ ) {
Chris@559 2100 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 2101 midiOutReset( data->outHandle );
Chris@559 2102 midiOutClose( data->outHandle );
Chris@559 2103 connected_ = false;
Chris@559 2104 }
Chris@559 2105 }
Chris@559 2106
Chris@559 2107 void RtMidiOut :: openVirtualPort( std::string portName )
Chris@559 2108 {
Chris@559 2109 // This function cannot be implemented for the Windows MM MIDI API.
Chris@559 2110 errorString_ = "RtMidiOut::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
Chris@559 2111 error( RtError::WARNING );
Chris@559 2112 }
Chris@559 2113
Chris@559 2114 RtMidiOut :: ~RtMidiOut()
Chris@559 2115 {
Chris@559 2116 // Close a connection if it exists.
Chris@559 2117 closePort();
Chris@559 2118
Chris@559 2119 // Cleanup.
Chris@559 2120 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 2121 delete data;
Chris@559 2122 }
Chris@559 2123
Chris@559 2124 void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
Chris@559 2125 {
Chris@559 2126 unsigned int nBytes = message->size();
Chris@559 2127 if ( nBytes == 0 ) {
Chris@559 2128 errorString_ = "RtMidiOut::sendMessage: message argument is empty!";
Chris@559 2129 error( RtError::WARNING );
Chris@559 2130 return;
Chris@559 2131 }
Chris@559 2132
Chris@559 2133 MMRESULT result;
Chris@559 2134 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
Chris@559 2135 if ( message->at(0) == 0xF0 ) { // Sysex message
Chris@559 2136
Chris@559 2137 // Allocate buffer for sysex data.
Chris@559 2138 char *buffer = (char *) malloc( nBytes );
Chris@559 2139 if ( buffer == NULL ) {
Chris@559 2140 errorString_ = "RtMidiOut::sendMessage: error allocating sysex message memory!";
Chris@559 2141 error( RtError::MEMORY_ERROR );
Chris@559 2142 }
Chris@559 2143
Chris@559 2144 // Copy data to buffer.
Chris@559 2145 for ( unsigned int i=0; i<nBytes; i++ ) buffer[i] = message->at(i);
Chris@559 2146
Chris@559 2147 // Create and prepare MIDIHDR structure.
Chris@559 2148 MIDIHDR sysex;
Chris@559 2149 sysex.lpData = (LPSTR) buffer;
Chris@559 2150 sysex.dwBufferLength = nBytes;
Chris@559 2151 sysex.dwFlags = 0;
Chris@559 2152 result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) );
Chris@559 2153 if ( result != MMSYSERR_NOERROR ) {
Chris@559 2154 free( buffer );
Chris@559 2155 errorString_ = "RtMidiOut::sendMessage: error preparing sysex header.";
Chris@559 2156 error( RtError::DRIVER_ERROR );
Chris@559 2157 }
Chris@559 2158
Chris@559 2159 // Send the message.
Chris@559 2160 result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) );
Chris@559 2161 if ( result != MMSYSERR_NOERROR ) {
Chris@559 2162 free( buffer );
Chris@559 2163 errorString_ = "RtMidiOut::sendMessage: error sending sysex message.";
Chris@559 2164 error( RtError::DRIVER_ERROR );
Chris@559 2165 }
Chris@559 2166
Chris@559 2167 // Unprepare the buffer and MIDIHDR.
Chris@559 2168 while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 );
Chris@559 2169 free( buffer );
Chris@559 2170
Chris@559 2171 }
Chris@559 2172 else { // Channel or system message.
Chris@559 2173
Chris@559 2174 // Make sure the message size isn't too big.
Chris@559 2175 if ( nBytes > 3 ) {
Chris@559 2176 errorString_ = "RtMidiOut::sendMessage: message size is greater than 3 bytes (and not sysex)!";
Chris@559 2177 error( RtError::WARNING );
Chris@559 2178 return;
Chris@559 2179 }
Chris@559 2180
Chris@559 2181 // Pack MIDI bytes into double word.
Chris@559 2182 DWORD packet;
Chris@559 2183 unsigned char *ptr = (unsigned char *) &packet;
Chris@559 2184 for ( unsigned int i=0; i<nBytes; i++ ) {
Chris@559 2185 *ptr = message->at(i);
Chris@559 2186 ptr++;
Chris@559 2187 }
Chris@559 2188
Chris@559 2189 // Send the message immediately.
Chris@559 2190 result = midiOutShortMsg( data->outHandle, packet );
Chris@559 2191 if ( result != MMSYSERR_NOERROR ) {
Chris@559 2192 errorString_ = "RtMidiOut::sendMessage: error sending MIDI message.";
Chris@559 2193 error( RtError::DRIVER_ERROR );
Chris@559 2194 }
Chris@559 2195 }
Chris@559 2196 }
Chris@559 2197
Chris@559 2198 #endif // __WINDOWS_MM__
Chris@609 2199
Chris@609 2200 #ifdef __RTMIDI_DUMMY_ONLY__
Chris@609 2201
Chris@609 2202 void RtMidiIn :: initialize( const std::string& /*clientName*/ )
Chris@609 2203 {
Chris@609 2204 }
Chris@609 2205
Chris@609 2206 void RtMidiIn :: openPort( unsigned int portNumber, const std::string /*portName*/ )
Chris@609 2207 {
Chris@609 2208 }
Chris@609 2209
Chris@609 2210 void RtMidiIn :: openVirtualPort( std::string portName )
Chris@609 2211 {
Chris@609 2212 }
Chris@609 2213
Chris@609 2214 void RtMidiIn :: closePort( void )
Chris@609 2215 {
Chris@609 2216 }
Chris@609 2217
Chris@609 2218 RtMidiIn :: ~RtMidiIn()
Chris@609 2219 {
Chris@609 2220 }
Chris@609 2221
Chris@609 2222 unsigned int RtMidiIn :: getPortCount()
Chris@609 2223 {
Chris@609 2224 return 0;
Chris@609 2225 }
Chris@609 2226
Chris@609 2227 std::string RtMidiIn :: getPortName( unsigned int portNumber )
Chris@609 2228 {
Chris@609 2229 return "";
Chris@609 2230 }
Chris@609 2231
Chris@609 2232 unsigned int RtMidiOut :: getPortCount()
Chris@609 2233 {
Chris@609 2234 return 0;
Chris@609 2235 }
Chris@609 2236
Chris@609 2237 std::string RtMidiOut :: getPortName( unsigned int portNumber )
Chris@609 2238 {
Chris@609 2239 return "";
Chris@609 2240 }
Chris@609 2241
Chris@609 2242 void RtMidiOut :: initialize( const std::string& /*clientName*/ )
Chris@609 2243 {
Chris@609 2244 }
Chris@609 2245
Chris@609 2246 void RtMidiOut :: openPort( unsigned int portNumber, const std::string /*portName*/ )
Chris@609 2247 {
Chris@609 2248 }
Chris@609 2249
Chris@609 2250 void RtMidiOut :: closePort( void )
Chris@609 2251 {
Chris@609 2252 }
Chris@609 2253
Chris@609 2254 void RtMidiOut :: openVirtualPort( std::string portName )
Chris@609 2255 {
Chris@609 2256 }
Chris@609 2257
Chris@609 2258 RtMidiOut :: ~RtMidiOut()
Chris@609 2259 {
Chris@609 2260 }
Chris@609 2261
Chris@609 2262 void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
Chris@609 2263 {
Chris@609 2264 }
Chris@609 2265
Chris@929 2266 #endif /* __RTMIDI_DUMMY_ONLY__ */
Chris@609 2267