RtMidi.cpp (126409B)
1 /**********************************************************************/ 2 /*! \class RtMidi 3 \brief An abstract base class for realtime MIDI input/output. 4 5 This class implements some common functionality for the realtime 6 MIDI input/output subclasses RtMidiIn and RtMidiOut. 7 8 RtMidi GitHub site: https://github.com/thestk/rtmidi 9 RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ 10 11 RtMidi: realtime MIDI i/o C++ classes 12 Copyright (c) 2003-2021 Gary P. Scavone 13 14 Permission is hereby granted, free of charge, to any person 15 obtaining a copy of this software and associated documentation files 16 (the "Software"), to deal in the Software without restriction, 17 including without limitation the rights to use, copy, modify, merge, 18 publish, distribute, sublicense, and/or sell copies of the Software, 19 and to permit persons to whom the Software is furnished to do so, 20 subject to the following conditions: 21 22 The above copyright notice and this permission notice shall be 23 included in all copies or substantial portions of the Software. 24 25 Any person wishing to distribute modifications to the Software is 26 asked to send the modifications to the original developer so that 27 they can be incorporated into the canonical version. This is, 28 however, not a binding provision of this license. 29 30 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 31 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 32 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 33 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 34 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 35 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 36 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37 */ 38 /**********************************************************************/ 39 40 #include "RtMidi.h" 41 #include <sstream> 42 #if defined(__APPLE__) 43 #include <TargetConditionals.h> 44 #endif 45 46 #if (TARGET_OS_IPHONE == 1) 47 48 #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime 49 #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos 50 51 #include <mach/mach_time.h> 52 class CTime2nsFactor 53 { 54 public: 55 CTime2nsFactor() 56 { 57 mach_timebase_info_data_t tinfo; 58 mach_timebase_info(&tinfo); 59 Factor = (double)tinfo.numer / tinfo.denom; 60 } 61 static double Factor; 62 }; 63 double CTime2nsFactor::Factor; 64 static CTime2nsFactor InitTime2nsFactor; 65 #undef AudioGetCurrentHostTime 66 #undef AudioConvertHostTimeToNanos 67 #define AudioGetCurrentHostTime (uint64_t) mach_absolute_time 68 #define AudioConvertHostTimeToNanos(t) t *CTime2nsFactor::Factor 69 #define EndianS32_BtoN(n) n 70 71 #endif 72 73 // Default for Windows is to add an identifier to the port names; this 74 // flag can be defined (e.g. in your project file) to disable this behaviour. 75 //#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES 76 77 // **************************************************************** // 78 // 79 // MidiInApi and MidiOutApi subclass prototypes. 80 // 81 // **************************************************************** // 82 83 #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) 84 #define __RTMIDI_DUMMY__ 85 #endif 86 87 #if defined(__MACOSX_CORE__) 88 #include <CoreMIDI/CoreMIDI.h> 89 90 class MidiInCore: public MidiInApi 91 { 92 public: 93 MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ); 94 ~MidiInCore( void ); 95 RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; 96 void openPort( unsigned int portNumber, const std::string &portName ); 97 void openVirtualPort( const std::string &portName ); 98 void closePort( void ); 99 void setClientName( const std::string &clientName ); 100 void setPortName( const std::string &portName ); 101 unsigned int getPortCount( void ); 102 std::string getPortName( unsigned int portNumber ); 103 104 protected: 105 MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); 106 void initialize( const std::string& clientName ); 107 }; 108 109 class MidiOutCore: public MidiOutApi 110 { 111 public: 112 MidiOutCore( const std::string &clientName ); 113 ~MidiOutCore( void ); 114 RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; 115 void openPort( unsigned int portNumber, const std::string &portName ); 116 void openVirtualPort( const std::string &portName ); 117 void closePort( void ); 118 void setClientName( const std::string &clientName ); 119 void setPortName( const std::string &portName ); 120 unsigned int getPortCount( void ); 121 std::string getPortName( unsigned int portNumber ); 122 void sendMessage( const unsigned char *message, size_t size ); 123 124 protected: 125 MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); 126 void initialize( const std::string& clientName ); 127 }; 128 129 #endif 130 131 #if defined(__UNIX_JACK__) 132 133 class MidiInJack: public MidiInApi 134 { 135 public: 136 MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ); 137 ~MidiInJack( void ); 138 RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; 139 void openPort( unsigned int portNumber, const std::string &portName ); 140 void openVirtualPort( const std::string &portName ); 141 void closePort( void ); 142 void setClientName( const std::string &clientName ); 143 void setPortName( const std::string &portName); 144 unsigned int getPortCount( void ); 145 std::string getPortName( unsigned int portNumber ); 146 147 protected: 148 std::string clientName; 149 150 void connect( void ); 151 void initialize( const std::string& clientName ); 152 }; 153 154 class MidiOutJack: public MidiOutApi 155 { 156 public: 157 MidiOutJack( const std::string &clientName ); 158 ~MidiOutJack( void ); 159 RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; 160 void openPort( unsigned int portNumber, const std::string &portName ); 161 void openVirtualPort( const std::string &portName ); 162 void closePort( void ); 163 void setClientName( const std::string &clientName ); 164 void setPortName( const std::string &portName); 165 unsigned int getPortCount( void ); 166 std::string getPortName( unsigned int portNumber ); 167 void sendMessage( const unsigned char *message, size_t size ); 168 169 protected: 170 std::string clientName; 171 172 void connect( void ); 173 void initialize( const std::string& clientName ); 174 }; 175 176 #endif 177 178 #if defined(__LINUX_ALSA__) 179 180 class MidiInAlsa: public MidiInApi 181 { 182 public: 183 MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ); 184 ~MidiInAlsa( void ); 185 RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; 186 void openPort( unsigned int portNumber, const std::string &portName ); 187 void openVirtualPort( const std::string &portName ); 188 void closePort( void ); 189 void setClientName( const std::string &clientName ); 190 void setPortName( const std::string &portName); 191 unsigned int getPortCount( void ); 192 std::string getPortName( unsigned int portNumber ); 193 194 protected: 195 void initialize( const std::string& clientName ); 196 }; 197 198 class MidiOutAlsa: public MidiOutApi 199 { 200 public: 201 MidiOutAlsa( const std::string &clientName ); 202 ~MidiOutAlsa( void ); 203 RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; 204 void openPort( unsigned int portNumber, const std::string &portName ); 205 void openVirtualPort( const std::string &portName ); 206 void closePort( void ); 207 void setClientName( const std::string &clientName ); 208 void setPortName( const std::string &portName ); 209 unsigned int getPortCount( void ); 210 std::string getPortName( unsigned int portNumber ); 211 void sendMessage( const unsigned char *message, size_t size ); 212 213 protected: 214 void initialize( const std::string& clientName ); 215 }; 216 217 #endif 218 219 #if defined(__WINDOWS_MM__) 220 221 class MidiInWinMM: public MidiInApi 222 { 223 public: 224 MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ); 225 ~MidiInWinMM( void ); 226 RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; 227 void openPort( unsigned int portNumber, const std::string &portName ); 228 void openVirtualPort( const std::string &portName ); 229 void closePort( void ); 230 void setClientName( const std::string &clientName ); 231 void setPortName( const std::string &portName ); 232 unsigned int getPortCount( void ); 233 std::string getPortName( unsigned int portNumber ); 234 235 protected: 236 void initialize( const std::string& clientName ); 237 }; 238 239 class MidiOutWinMM: public MidiOutApi 240 { 241 public: 242 MidiOutWinMM( const std::string &clientName ); 243 ~MidiOutWinMM( void ); 244 RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; 245 void openPort( unsigned int portNumber, const std::string &portName ); 246 void openVirtualPort( const std::string &portName ); 247 void closePort( void ); 248 void setClientName( const std::string &clientName ); 249 void setPortName( const std::string &portName ); 250 unsigned int getPortCount( void ); 251 std::string getPortName( unsigned int portNumber ); 252 void sendMessage( const unsigned char *message, size_t size ); 253 254 protected: 255 void initialize( const std::string& clientName ); 256 }; 257 258 #endif 259 260 #if defined(__WEB_MIDI_API__) 261 262 class MidiInWeb : public MidiInApi 263 { 264 std::string client_name{}; 265 std::string web_midi_id{}; 266 int open_port_number{-1}; 267 268 public: 269 MidiInWeb(const std::string &/*clientName*/, unsigned int queueSizeLimit ); 270 ~MidiInWeb( void ); 271 RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; }; 272 void openPort( unsigned int portNumber, const std::string &portName ); 273 void openVirtualPort( const std::string &portName ); 274 void closePort( void ); 275 void setClientName( const std::string &clientName ); 276 void setPortName( const std::string &portName ); 277 unsigned int getPortCount( void ); 278 std::string getPortName( unsigned int portNumber ); 279 280 void onMidiMessage( uint8_t* data, double domHishResTimeStamp ); 281 282 protected: 283 void initialize( const std::string& clientName ); 284 }; 285 286 class MidiOutWeb: public MidiOutApi 287 { 288 std::string client_name{}; 289 std::string web_midi_id{}; 290 int open_port_number{-1}; 291 292 public: 293 MidiOutWeb( const std::string &clientName ); 294 ~MidiOutWeb( void ); 295 RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; }; 296 void openPort( unsigned int portNumber, const std::string &portName ); 297 void openVirtualPort( const std::string &portName ); 298 void closePort( void ); 299 void setClientName( const std::string &clientName ); 300 void setPortName( const std::string &portName ); 301 unsigned int getPortCount( void ); 302 std::string getPortName( unsigned int portNumber ); 303 void sendMessage( const unsigned char *message, size_t size ); 304 305 protected: 306 void initialize( const std::string& clientName ); 307 }; 308 309 #endif 310 311 #if defined(__RTMIDI_DUMMY__) 312 313 class MidiInDummy: public MidiInApi 314 { 315 public: 316 MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } 317 RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } 318 void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} 319 void openVirtualPort( const std::string &/*portName*/ ) {} 320 void closePort( void ) {} 321 void setClientName( const std::string &/*clientName*/ ) {}; 322 void setPortName( const std::string &/*portName*/ ) {}; 323 unsigned int getPortCount( void ) { return 0; } 324 std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } 325 326 protected: 327 void initialize( const std::string& /*clientName*/ ) {} 328 }; 329 330 class MidiOutDummy: public MidiOutApi 331 { 332 public: 333 MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } 334 RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } 335 void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} 336 void openVirtualPort( const std::string &/*portName*/ ) {} 337 void closePort( void ) {} 338 void setClientName( const std::string &/*clientName*/ ) {}; 339 void setPortName( const std::string &/*portName*/ ) {}; 340 unsigned int getPortCount( void ) { return 0; } 341 std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } 342 void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {} 343 344 protected: 345 void initialize( const std::string& /*clientName*/ ) {} 346 }; 347 348 #endif 349 350 //*********************************************************************// 351 // RtMidi Definitions 352 //*********************************************************************// 353 354 RtMidi :: RtMidi() 355 : rtapi_(0) 356 { 357 } 358 359 RtMidi :: ~RtMidi() 360 { 361 delete rtapi_; 362 rtapi_ = 0; 363 } 364 365 RtMidi::RtMidi(RtMidi&& other) noexcept { 366 rtapi_ = other.rtapi_; 367 other.rtapi_ = nullptr; 368 } 369 370 std::string RtMidi :: getVersion( void ) throw() 371 { 372 return std::string( RTMIDI_VERSION ); 373 } 374 375 // Define API names and display names. 376 // Must be in same order as API enum. 377 extern "C" { 378 const char* rtmidi_api_names[][2] = { 379 { "unspecified" , "Unknown" }, 380 { "core" , "CoreMidi" }, 381 { "alsa" , "ALSA" }, 382 { "jack" , "Jack" }, 383 { "winmm" , "Windows MultiMedia" }, 384 { "web" , "Web MIDI API" }, 385 { "dummy" , "Dummy" }, 386 }; 387 const unsigned int rtmidi_num_api_names = 388 sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]); 389 390 // The order here will control the order of RtMidi's API search in 391 // the constructor. 392 extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { 393 #if defined(__MACOSX_CORE__) 394 RtMidi::MACOSX_CORE, 395 #endif 396 #if defined(__LINUX_ALSA__) 397 RtMidi::LINUX_ALSA, 398 #endif 399 #if defined(__UNIX_JACK__) 400 RtMidi::UNIX_JACK, 401 #endif 402 #if defined(__WINDOWS_MM__) 403 RtMidi::WINDOWS_MM, 404 #endif 405 #if defined(__WEB_MIDI_API__) 406 RtMidi::WEB_MIDI_API, 407 #endif 408 #if defined(__RTMIDI_DUMMY__) 409 RtMidi::RTMIDI_DUMMY, 410 #endif 411 RtMidi::UNSPECIFIED, 412 }; 413 extern "C" const unsigned int rtmidi_num_compiled_apis = 414 sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1; 415 } 416 417 void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw() 418 { 419 apis = std::vector<RtMidi::Api>(rtmidi_compiled_apis, 420 rtmidi_compiled_apis + rtmidi_num_compiled_apis); 421 } 422 423 std::string RtMidi :: getApiName( RtMidi::Api api ) 424 { 425 if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) 426 return ""; 427 return rtmidi_api_names[api][0]; 428 } 429 430 std::string RtMidi :: getApiDisplayName( RtMidi::Api api ) 431 { 432 if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) 433 return "Unknown"; 434 return rtmidi_api_names[api][1]; 435 } 436 437 RtMidi::Api RtMidi :: getCompiledApiByName( const std::string &name ) 438 { 439 unsigned int i=0; 440 for (i = 0; i < rtmidi_num_compiled_apis; ++i) 441 if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0]) 442 return rtmidi_compiled_apis[i]; 443 return RtMidi::UNSPECIFIED; 444 } 445 446 void RtMidi :: setClientName( const std::string &clientName ) 447 { 448 rtapi_->setClientName( clientName ); 449 } 450 451 void RtMidi :: setPortName( const std::string &portName ) 452 { 453 rtapi_->setPortName( portName ); 454 } 455 456 457 //*********************************************************************// 458 // RtMidiIn Definitions 459 //*********************************************************************// 460 461 void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) 462 { 463 delete rtapi_; 464 rtapi_ = 0; 465 466 #if defined(__UNIX_JACK__) 467 if ( api == UNIX_JACK ) 468 rtapi_ = new MidiInJack( clientName, queueSizeLimit ); 469 #endif 470 #if defined(__LINUX_ALSA__) 471 if ( api == LINUX_ALSA ) 472 rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); 473 #endif 474 #if defined(__WINDOWS_MM__) 475 if ( api == WINDOWS_MM ) 476 rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); 477 #endif 478 #if defined(__MACOSX_CORE__) 479 if ( api == MACOSX_CORE ) 480 rtapi_ = new MidiInCore( clientName, queueSizeLimit ); 481 #endif 482 #if defined(__WEB_MIDI_API__) 483 if ( api == WEB_MIDI_API ) 484 rtapi_ = new MidiInWeb( clientName, queueSizeLimit ); 485 #endif 486 #if defined(__RTMIDI_DUMMY__) 487 if ( api == RTMIDI_DUMMY ) 488 rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); 489 #endif 490 } 491 492 RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) 493 : RtMidi() 494 { 495 if ( api != UNSPECIFIED ) { 496 // Attempt to open the specified API. 497 openMidiApi( api, clientName, queueSizeLimit ); 498 if ( rtapi_ ) return; 499 500 // No compiled support for specified API value. Issue a warning 501 // and continue as if no API was specified. 502 std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; 503 } 504 505 // Iterate through the compiled APIs and return as soon as we find 506 // one with at least one port or we reach the end of the list. 507 std::vector< RtMidi::Api > apis; 508 getCompiledApi( apis ); 509 for ( unsigned int i=0; i<apis.size(); i++ ) { 510 openMidiApi( apis[i], clientName, queueSizeLimit ); 511 if ( rtapi_ && rtapi_->getPortCount() ) break; 512 } 513 514 if ( rtapi_ ) return; 515 516 // It should not be possible to get here because the preprocessor 517 // definition __RTMIDI_DUMMY__ is automatically defined if no 518 // API-specific definitions are passed to the compiler. But just in 519 // case something weird happens, we'll throw an error. 520 std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; 521 throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); 522 } 523 524 RtMidiIn :: ~RtMidiIn() throw() 525 { 526 } 527 528 529 //*********************************************************************// 530 // RtMidiOut Definitions 531 //*********************************************************************// 532 533 void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) 534 { 535 delete rtapi_; 536 rtapi_ = 0; 537 538 #if defined(__UNIX_JACK__) 539 if ( api == UNIX_JACK ) 540 rtapi_ = new MidiOutJack( clientName ); 541 #endif 542 #if defined(__LINUX_ALSA__) 543 if ( api == LINUX_ALSA ) 544 rtapi_ = new MidiOutAlsa( clientName ); 545 #endif 546 #if defined(__WINDOWS_MM__) 547 if ( api == WINDOWS_MM ) 548 rtapi_ = new MidiOutWinMM( clientName ); 549 #endif 550 #if defined(__MACOSX_CORE__) 551 if ( api == MACOSX_CORE ) 552 rtapi_ = new MidiOutCore( clientName ); 553 #endif 554 #if defined(__WEB_MIDI_API__) 555 if ( api == WEB_MIDI_API ) 556 rtapi_ = new MidiOutWeb( clientName ); 557 #endif 558 #if defined(__RTMIDI_DUMMY__) 559 if ( api == RTMIDI_DUMMY ) 560 rtapi_ = new MidiOutDummy( clientName ); 561 #endif 562 } 563 564 RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName) 565 { 566 if ( api != UNSPECIFIED ) { 567 // Attempt to open the specified API. 568 openMidiApi( api, clientName ); 569 if ( rtapi_ ) return; 570 571 // No compiled support for specified API value. Issue a warning 572 // and continue as if no API was specified. 573 std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; 574 } 575 576 // Iterate through the compiled APIs and return as soon as we find 577 // one with at least one port or we reach the end of the list. 578 std::vector< RtMidi::Api > apis; 579 getCompiledApi( apis ); 580 for ( unsigned int i=0; i<apis.size(); i++ ) { 581 openMidiApi( apis[i], clientName ); 582 if ( rtapi_ && rtapi_->getPortCount() ) break; 583 } 584 585 if ( rtapi_ ) return; 586 587 // It should not be possible to get here because the preprocessor 588 // definition __RTMIDI_DUMMY__ is automatically defined if no 589 // API-specific definitions are passed to the compiler. But just in 590 // case something weird happens, we'll thrown an error. 591 std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; 592 throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); 593 } 594 595 RtMidiOut :: ~RtMidiOut() throw() 596 { 597 } 598 599 //*********************************************************************// 600 // Common MidiApi Definitions 601 //*********************************************************************// 602 603 MidiApi :: MidiApi( void ) 604 : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0) 605 { 606 } 607 608 MidiApi :: ~MidiApi( void ) 609 { 610 } 611 612 void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 ) 613 { 614 errorCallback_ = errorCallback; 615 errorCallbackUserData_ = userData; 616 } 617 618 void MidiApi :: error( RtMidiError::Type type, std::string errorString ) 619 { 620 if ( errorCallback_ ) { 621 622 if ( firstErrorOccurred_ ) 623 return; 624 625 firstErrorOccurred_ = true; 626 const std::string errorMessage = errorString; 627 628 errorCallback_( type, errorMessage, errorCallbackUserData_ ); 629 firstErrorOccurred_ = false; 630 return; 631 } 632 633 if ( type == RtMidiError::WARNING ) { 634 std::cerr << '\n' << errorString << "\n\n"; 635 } 636 else if ( type == RtMidiError::DEBUG_WARNING ) { 637 #if defined(__RTMIDI_DEBUG__) 638 std::cerr << '\n' << errorString << "\n\n"; 639 #endif 640 } 641 else { 642 std::cerr << '\n' << errorString << "\n\n"; 643 throw RtMidiError( errorString, type ); 644 } 645 } 646 647 //*********************************************************************// 648 // Common MidiInApi Definitions 649 //*********************************************************************// 650 651 MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) 652 : MidiApi() 653 { 654 // Allocate the MIDI queue. 655 inputData_.queue.ringSize = queueSizeLimit; 656 if ( inputData_.queue.ringSize > 0 ) 657 inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; 658 } 659 660 MidiInApi :: ~MidiInApi( void ) 661 { 662 // Delete the MIDI queue. 663 if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; 664 } 665 666 void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) 667 { 668 if ( inputData_.usingCallback ) { 669 errorString_ = "MidiInApi::setCallback: a callback function is already set!"; 670 error( RtMidiError::WARNING, errorString_ ); 671 return; 672 } 673 674 if ( !callback ) { 675 errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; 676 error( RtMidiError::WARNING, errorString_ ); 677 return; 678 } 679 680 inputData_.userCallback = callback; 681 inputData_.userData = userData; 682 inputData_.usingCallback = true; 683 } 684 685 void MidiInApi :: cancelCallback() 686 { 687 if ( !inputData_.usingCallback ) { 688 errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; 689 error( RtMidiError::WARNING, errorString_ ); 690 return; 691 } 692 693 inputData_.userCallback = 0; 694 inputData_.userData = 0; 695 inputData_.usingCallback = false; 696 } 697 698 void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) 699 { 700 inputData_.ignoreFlags = 0; 701 if ( midiSysex ) inputData_.ignoreFlags = 0x01; 702 if ( midiTime ) inputData_.ignoreFlags |= 0x02; 703 if ( midiSense ) inputData_.ignoreFlags |= 0x04; 704 } 705 706 double MidiInApi :: getMessage( std::vector<unsigned char> *message ) 707 { 708 message->clear(); 709 710 if ( inputData_.usingCallback ) { 711 errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; 712 error( RtMidiError::WARNING, errorString_ ); 713 return 0.0; 714 } 715 716 double timeStamp; 717 if ( !inputData_.queue.pop( message, &timeStamp ) ) 718 return 0.0; 719 720 return timeStamp; 721 } 722 723 void MidiInApi :: setBufferSize( unsigned int size, unsigned int count ) 724 { 725 inputData_.bufferSize = size; 726 inputData_.bufferCount = count; 727 } 728 729 unsigned int MidiInApi::MidiQueue::size( unsigned int *__back, 730 unsigned int *__front ) 731 { 732 // Access back/front members exactly once and make stack copies for 733 // size calculation 734 unsigned int _back = back, _front = front, _size; 735 if ( _back >= _front ) 736 _size = _back - _front; 737 else 738 _size = ringSize - _front + _back; 739 740 // Return copies of back/front so no new and unsynchronized accesses 741 // to member variables are needed. 742 if ( __back ) *__back = _back; 743 if ( __front ) *__front = _front; 744 return _size; 745 } 746 747 // As long as we haven't reached our queue size limit, push the message. 748 bool MidiInApi::MidiQueue::push( const MidiInApi::MidiMessage& msg ) 749 { 750 // Local stack copies of front/back 751 unsigned int _back, _front, _size; 752 753 // Get back/front indexes exactly once and calculate current size 754 _size = size( &_back, &_front ); 755 756 if ( _size < ringSize-1 ) 757 { 758 ring[_back] = msg; 759 back = (back+1)%ringSize; 760 return true; 761 } 762 763 return false; 764 } 765 766 bool MidiInApi::MidiQueue::pop( std::vector<unsigned char> *msg, double* timeStamp ) 767 { 768 // Local stack copies of front/back 769 unsigned int _back, _front, _size; 770 771 // Get back/front indexes exactly once and calculate current size 772 _size = size( &_back, &_front ); 773 774 if ( _size == 0 ) 775 return false; 776 777 // Copy queued message to the vector pointer argument and then "pop" it. 778 msg->assign( ring[_front].bytes.begin(), ring[_front].bytes.end() ); 779 *timeStamp = ring[_front].timeStamp; 780 781 // Update front 782 front = (front+1)%ringSize; 783 return true; 784 } 785 786 //*********************************************************************// 787 // Common MidiOutApi Definitions 788 //*********************************************************************// 789 790 MidiOutApi :: MidiOutApi( void ) 791 : MidiApi() 792 { 793 } 794 795 MidiOutApi :: ~MidiOutApi( void ) 796 { 797 } 798 799 // *************************************************** // 800 // 801 // OS/API-specific methods. 802 // 803 // *************************************************** // 804 805 #if defined(__MACOSX_CORE__) 806 807 // The CoreMIDI API is based on the use of a callback function for 808 // MIDI input. We convert the system specific time stamps to delta 809 // time values. 810 811 // These are not available on iOS. 812 #if (TARGET_OS_IPHONE == 0) 813 #include <CoreAudio/HostTime.h> 814 #include <CoreServices/CoreServices.h> 815 #endif 816 817 // A structure to hold variables related to the CoreMIDI API 818 // implementation. 819 struct CoreMidiData { 820 MIDIClientRef client; 821 MIDIPortRef port; 822 MIDIEndpointRef endpoint; 823 MIDIEndpointRef destinationId; 824 unsigned long long lastTime; 825 MIDISysexSendRequest sysexreq; 826 }; 827 828 static MIDIClientRef CoreMidiClientSingleton = 0; 829 830 void RtMidi_setCoreMidiClientSingleton(MIDIClientRef client){ 831 CoreMidiClientSingleton = client; 832 } 833 834 void RtMidi_disposeCoreMidiClientSingleton(){ 835 if (CoreMidiClientSingleton == 0){ 836 return; 837 } 838 MIDIClientDispose( CoreMidiClientSingleton ); 839 CoreMidiClientSingleton = 0; 840 } 841 842 //*********************************************************************// 843 // API: OS-X 844 // Class Definitions: MidiInCore 845 //*********************************************************************// 846 847 static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) 848 { 849 MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef); 850 CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData); 851 852 unsigned char status; 853 unsigned short nBytes, iByte, size; 854 unsigned long long time; 855 856 bool& continueSysex = data->continueSysex; 857 MidiInApi::MidiMessage& message = data->message; 858 859 const MIDIPacket *packet = &list->packet[0]; 860 for ( unsigned int i=0; i<list->numPackets; ++i ) { 861 862 // My interpretation of the CoreMIDI documentation: all message 863 // types, except sysex, are complete within a packet and there may 864 // be several of them in a single packet. Sysex messages can be 865 // broken across multiple packets and PacketLists but are bundled 866 // alone within each packet (these packets do not contain other 867 // message types). If sysex messages are split across multiple 868 // MIDIPacketLists, they must be handled by multiple calls to this 869 // function. 870 871 nBytes = packet->length; 872 if ( nBytes == 0 ) { 873 packet = MIDIPacketNext( packet ); 874 continue; 875 } 876 877 // Calculate time stamp. 878 if ( data->firstMessage ) { 879 message.timeStamp = 0.0; 880 data->firstMessage = false; 881 } 882 else { 883 time = packet->timeStamp; 884 if ( time == 0 ) { // this happens when receiving asynchronous sysex messages 885 time = AudioGetCurrentHostTime(); 886 } 887 time -= apiData->lastTime; 888 time = AudioConvertHostTimeToNanos( time ); 889 if ( !continueSysex ) 890 message.timeStamp = time * 0.000000001; 891 } 892 893 // Track whether any non-filtered messages were found in this 894 // packet for timestamp calculation 895 bool foundNonFiltered = false; 896 897 iByte = 0; 898 if ( continueSysex ) { 899 // We have a continuing, segmented sysex message. 900 if ( !( data->ignoreFlags & 0x01 ) ) { 901 // If we're not ignoring sysex messages, copy the entire packet. 902 for ( unsigned int j=0; j<nBytes; ++j ) 903 message.bytes.push_back( packet->data[j] ); 904 } 905 continueSysex = packet->data[nBytes-1] != 0xF7; 906 907 if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { 908 // If not a continuing sysex message, invoke the user callback function or queue the message. 909 if ( data->usingCallback ) { 910 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 911 callback( message.timeStamp, &message.bytes, data->userData ); 912 } 913 else { 914 // As long as we haven't reached our queue size limit, push the message. 915 if ( !data->queue.push( message ) ) 916 std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; 917 } 918 message.bytes.clear(); 919 } 920 } 921 else { 922 while ( iByte < nBytes ) { 923 size = 0; 924 // We are expecting that the next byte in the packet is a status byte. 925 status = packet->data[iByte]; 926 if ( !(status & 0x80) ) break; 927 // Determine the number of bytes in the MIDI message. 928 if ( status < 0xC0 ) size = 3; 929 else if ( status < 0xE0 ) size = 2; 930 else if ( status < 0xF0 ) size = 3; 931 else if ( status == 0xF0 ) { 932 // A MIDI sysex 933 if ( data->ignoreFlags & 0x01 ) { 934 size = 0; 935 iByte = nBytes; 936 } 937 else size = nBytes - iByte; 938 continueSysex = packet->data[nBytes-1] != 0xF7; 939 } 940 else if ( status == 0xF1 ) { 941 // A MIDI time code message 942 if ( data->ignoreFlags & 0x02 ) { 943 size = 0; 944 iByte += 2; 945 } 946 else size = 2; 947 } 948 else if ( status == 0xF2 ) size = 3; 949 else if ( status == 0xF3 ) size = 2; 950 else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { 951 // A MIDI timing tick message and we're ignoring it. 952 size = 0; 953 iByte += 1; 954 } 955 else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { 956 // A MIDI active sensing message and we're ignoring it. 957 size = 0; 958 iByte += 1; 959 } 960 else size = 1; 961 962 // Copy the MIDI data to our vector. 963 if ( size ) { 964 foundNonFiltered = true; 965 message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); 966 if ( !continueSysex ) { 967 // If not a continuing sysex message, invoke the user callback function or queue the message. 968 if ( data->usingCallback ) { 969 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 970 callback( message.timeStamp, &message.bytes, data->userData ); 971 } 972 else { 973 // As long as we haven't reached our queue size limit, push the message. 974 if ( !data->queue.push( message ) ) 975 std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; 976 } 977 message.bytes.clear(); 978 } 979 iByte += size; 980 } 981 } 982 } 983 984 // Save the time of the last non-filtered message 985 if ( foundNonFiltered ) { 986 apiData->lastTime = packet->timeStamp; 987 if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages 988 apiData->lastTime = AudioGetCurrentHostTime(); 989 } 990 } 991 992 packet = MIDIPacketNext(packet); 993 } 994 } 995 996 MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ) 997 : MidiInApi( queueSizeLimit ) 998 { 999 MidiInCore::initialize( clientName ); 1000 } 1001 1002 MidiInCore :: ~MidiInCore( void ) 1003 { 1004 // Close a connection if it exists. 1005 MidiInCore::closePort(); 1006 1007 // Cleanup. 1008 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1009 if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); 1010 delete data; 1011 } 1012 1013 MIDIClientRef MidiInCore::getCoreMidiClientSingleton(const std::string& clientName) throw() { 1014 1015 if (CoreMidiClientSingleton == 0){ 1016 // Set up our client. 1017 MIDIClientRef client; 1018 1019 CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); 1020 OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); 1021 if ( result != noErr ) { 1022 std::ostringstream ost; 1023 ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; 1024 errorString_ = ost.str(); 1025 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1026 return 0; 1027 } 1028 CFRelease( name ); 1029 1030 CoreMidiClientSingleton = client; 1031 } 1032 1033 return CoreMidiClientSingleton; 1034 } 1035 1036 void MidiInCore :: initialize( const std::string& clientName ) 1037 { 1038 // Set up our client. 1039 MIDIClientRef client = getCoreMidiClientSingleton(clientName); 1040 1041 // Save our api-specific connection information. 1042 CoreMidiData *data = (CoreMidiData *) new CoreMidiData; 1043 data->client = client; 1044 data->endpoint = 0; 1045 apiData_ = (void *) data; 1046 inputData_.apiData = (void *) data; 1047 } 1048 1049 void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName ) 1050 { 1051 if ( connected_ ) { 1052 errorString_ = "MidiInCore::openPort: a valid connection already exists!"; 1053 error( RtMidiError::WARNING, errorString_ ); 1054 return; 1055 } 1056 1057 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1058 unsigned int nSrc = MIDIGetNumberOfSources(); 1059 if ( nSrc < 1 ) { 1060 errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; 1061 error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 1062 return; 1063 } 1064 1065 if ( portNumber >= nSrc ) { 1066 std::ostringstream ost; 1067 ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 1068 errorString_ = ost.str(); 1069 error( RtMidiError::INVALID_PARAMETER, errorString_ ); 1070 return; 1071 } 1072 1073 MIDIPortRef port; 1074 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1075 CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1076 OSStatus result = MIDIInputPortCreate( data->client, 1077 portNameRef, 1078 midiInputCallback, (void *)&inputData_, &port ); 1079 CFRelease( portNameRef ); 1080 1081 if ( result != noErr ) { 1082 errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; 1083 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1084 return; 1085 } 1086 1087 // Get the desired input source identifier. 1088 MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); 1089 if ( endpoint == 0 ) { 1090 MIDIPortDispose( port ); 1091 errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; 1092 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1093 return; 1094 } 1095 1096 // Make the connection. 1097 result = MIDIPortConnectSource( port, endpoint, NULL ); 1098 if ( result != noErr ) { 1099 MIDIPortDispose( port ); 1100 errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; 1101 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1102 return; 1103 } 1104 1105 // Save our api-specific port information. 1106 data->port = port; 1107 1108 connected_ = true; 1109 } 1110 1111 void MidiInCore :: openVirtualPort( const std::string &portName ) 1112 { 1113 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1114 1115 // Create a virtual MIDI input destination. 1116 MIDIEndpointRef endpoint; 1117 CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1118 OSStatus result = MIDIDestinationCreate( data->client, 1119 portNameRef, 1120 midiInputCallback, (void *)&inputData_, &endpoint ); 1121 CFRelease( portNameRef ); 1122 1123 if ( result != noErr ) { 1124 errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; 1125 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1126 return; 1127 } 1128 1129 // Save our api-specific connection information. 1130 data->endpoint = endpoint; 1131 } 1132 1133 void MidiInCore :: closePort( void ) 1134 { 1135 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1136 1137 if ( data->endpoint ) { 1138 MIDIEndpointDispose( data->endpoint ); 1139 data->endpoint = 0; 1140 } 1141 1142 if ( data->port ) { 1143 MIDIPortDispose( data->port ); 1144 data->port = 0; 1145 } 1146 1147 connected_ = false; 1148 } 1149 1150 void MidiInCore :: setClientName ( const std::string& ) 1151 { 1152 1153 errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; 1154 error( RtMidiError::WARNING, errorString_ ); 1155 1156 } 1157 1158 void MidiInCore :: setPortName ( const std::string& ) 1159 { 1160 1161 errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; 1162 error( RtMidiError::WARNING, errorString_ ); 1163 1164 } 1165 1166 unsigned int MidiInCore :: getPortCount() 1167 { 1168 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1169 return MIDIGetNumberOfSources(); 1170 } 1171 1172 // This function was submitted by Douglas Casey Tucker and apparently 1173 // derived largely from PortMidi. 1174 CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) 1175 { 1176 CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); 1177 CFStringRef str; 1178 1179 // Begin with the endpoint's name. 1180 str = NULL; 1181 MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); 1182 if ( str != NULL ) { 1183 CFStringAppend( result, str ); 1184 CFRelease( str ); 1185 } 1186 1187 // some MIDI devices have a leading space in endpoint name. trim 1188 CFStringRef space = CFStringCreateWithCString(NULL, " ", kCFStringEncodingUTF8); 1189 CFStringTrim(result, space); 1190 CFRelease(space); 1191 1192 MIDIEntityRef entity = 0; 1193 MIDIEndpointGetEntity( endpoint, &entity ); 1194 if ( entity == 0 ) 1195 // probably virtual 1196 return result; 1197 1198 if ( CFStringGetLength( result ) == 0 ) { 1199 // endpoint name has zero length -- try the entity 1200 str = NULL; 1201 MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); 1202 if ( str != NULL ) { 1203 CFStringAppend( result, str ); 1204 CFRelease( str ); 1205 } 1206 } 1207 // now consider the device's name 1208 MIDIDeviceRef device = 0; 1209 MIDIEntityGetDevice( entity, &device ); 1210 if ( device == 0 ) 1211 return result; 1212 1213 str = NULL; 1214 MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); 1215 if ( CFStringGetLength( result ) == 0 ) { 1216 CFRelease( result ); 1217 return str; 1218 } 1219 if ( str != NULL ) { 1220 // if an external device has only one entity, throw away 1221 // the endpoint name and just use the device name 1222 if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { 1223 CFRelease( result ); 1224 return str; 1225 } else { 1226 if ( CFStringGetLength( str ) == 0 ) { 1227 CFRelease( str ); 1228 return result; 1229 } 1230 // does the entity name already start with the device name? 1231 // (some drivers do this though they shouldn't) 1232 // if so, do not prepend 1233 if ( CFStringCompareWithOptions( result, /* endpoint name */ 1234 str /* device name */, 1235 CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { 1236 // prepend the device name to the entity name 1237 if ( CFStringGetLength( result ) > 0 ) 1238 CFStringInsert( result, 0, CFSTR(" ") ); 1239 1240 CFStringInsert( result, 0, str ); 1241 } 1242 CFRelease( str ); 1243 } 1244 } 1245 return result; 1246 } 1247 1248 // This function was submitted by Douglas Casey Tucker and apparently 1249 // derived largely from PortMidi. 1250 static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) 1251 { 1252 CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); 1253 CFStringRef str; 1254 OSStatus err; 1255 int i; 1256 1257 // Does the endpoint have connections? 1258 CFDataRef connections = NULL; 1259 int nConnected = 0; 1260 bool anyStrings = false; 1261 err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); 1262 if ( connections != NULL ) { 1263 // It has connections, follow them 1264 // Concatenate the names of all connected devices 1265 nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); 1266 if ( nConnected ) { 1267 const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); 1268 for ( i=0; i<nConnected; ++i, ++pid ) { 1269 MIDIUniqueID id = EndianS32_BtoN( *pid ); 1270 MIDIObjectRef connObject; 1271 MIDIObjectType connObjectType; 1272 err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType ); 1273 if ( err == noErr ) { 1274 if ( connObjectType == kMIDIObjectType_ExternalSource || 1275 connObjectType == kMIDIObjectType_ExternalDestination ) { 1276 // Connected to an external device's endpoint (10.3 and later). 1277 str = EndpointName( (MIDIEndpointRef)(connObject), true ); 1278 } else { 1279 // Connected to an external device (10.2) (or something else, catch- 1280 str = NULL; 1281 MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str ); 1282 } 1283 if ( str != NULL ) { 1284 if ( anyStrings ) 1285 CFStringAppend( result, CFSTR(", ") ); 1286 else 1287 anyStrings = true; 1288 CFStringAppend( result, str ); 1289 CFRelease( str ); 1290 } 1291 } 1292 } 1293 } 1294 CFRelease( connections ); 1295 } 1296 if ( anyStrings ) 1297 return result; 1298 1299 CFRelease( result ); 1300 1301 // Here, either the endpoint had no connections, or we failed to obtain names 1302 return EndpointName( endpoint, false ); 1303 } 1304 1305 std::string MidiInCore :: getPortName( unsigned int portNumber ) 1306 { 1307 CFStringRef nameRef; 1308 MIDIEndpointRef portRef; 1309 char name[128]; 1310 1311 std::string stringName; 1312 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1313 if ( portNumber >= MIDIGetNumberOfSources() ) { 1314 std::ostringstream ost; 1315 ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 1316 errorString_ = ost.str(); 1317 error( RtMidiError::WARNING, errorString_ ); 1318 return stringName; 1319 } 1320 1321 portRef = MIDIGetSource( portNumber ); 1322 nameRef = ConnectedEndpointName( portRef ); 1323 CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); 1324 CFRelease( nameRef ); 1325 1326 return stringName = name; 1327 } 1328 1329 //*********************************************************************// 1330 // API: OS-X 1331 // Class Definitions: MidiOutCore 1332 //*********************************************************************// 1333 1334 MidiOutCore :: MidiOutCore( const std::string &clientName ) 1335 : MidiOutApi() 1336 { 1337 MidiOutCore::initialize( clientName ); 1338 } 1339 1340 MidiOutCore :: ~MidiOutCore( void ) 1341 { 1342 // Close a connection if it exists. 1343 MidiOutCore::closePort(); 1344 1345 // Cleanup. 1346 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1347 if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); 1348 delete data; 1349 } 1350 1351 MIDIClientRef MidiOutCore::getCoreMidiClientSingleton(const std::string& clientName) throw() { 1352 1353 if (CoreMidiClientSingleton == 0){ 1354 // Set up our client. 1355 MIDIClientRef client; 1356 1357 CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); 1358 OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); 1359 if ( result != noErr ) { 1360 std::ostringstream ost; 1361 ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; 1362 errorString_ = ost.str(); 1363 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1364 return 0; 1365 } 1366 CFRelease( name ); 1367 1368 CoreMidiClientSingleton = client; 1369 } 1370 1371 return CoreMidiClientSingleton; 1372 } 1373 1374 void MidiOutCore :: initialize( const std::string& clientName ) 1375 { 1376 // Set up our client. 1377 MIDIClientRef client = getCoreMidiClientSingleton(clientName); 1378 1379 // Save our api-specific connection information. 1380 CoreMidiData *data = (CoreMidiData *) new CoreMidiData; 1381 data->client = client; 1382 data->endpoint = 0; 1383 apiData_ = (void *) data; 1384 } 1385 1386 unsigned int MidiOutCore :: getPortCount() 1387 { 1388 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1389 return MIDIGetNumberOfDestinations(); 1390 } 1391 1392 std::string MidiOutCore :: getPortName( unsigned int portNumber ) 1393 { 1394 CFStringRef nameRef; 1395 MIDIEndpointRef portRef; 1396 char name[128]; 1397 1398 std::string stringName; 1399 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1400 if ( portNumber >= MIDIGetNumberOfDestinations() ) { 1401 std::ostringstream ost; 1402 ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 1403 errorString_ = ost.str(); 1404 error( RtMidiError::WARNING, errorString_ ); 1405 return stringName; 1406 } 1407 1408 portRef = MIDIGetDestination( portNumber ); 1409 nameRef = ConnectedEndpointName(portRef); 1410 CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); 1411 CFRelease( nameRef ); 1412 1413 return stringName = name; 1414 } 1415 1416 void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName ) 1417 { 1418 if ( connected_ ) { 1419 errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; 1420 error( RtMidiError::WARNING, errorString_ ); 1421 return; 1422 } 1423 1424 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1425 unsigned int nDest = MIDIGetNumberOfDestinations(); 1426 if (nDest < 1) { 1427 errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; 1428 error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 1429 return; 1430 } 1431 1432 if ( portNumber >= nDest ) { 1433 std::ostringstream ost; 1434 ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 1435 errorString_ = ost.str(); 1436 error( RtMidiError::INVALID_PARAMETER, errorString_ ); 1437 return; 1438 } 1439 1440 MIDIPortRef port; 1441 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1442 CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1443 OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); 1444 CFRelease( portNameRef ); 1445 if ( result != noErr ) { 1446 errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; 1447 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1448 return; 1449 } 1450 1451 // Get the desired output port identifier. 1452 MIDIEndpointRef destination = MIDIGetDestination( portNumber ); 1453 if ( destination == 0 ) { 1454 MIDIPortDispose( port ); 1455 errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; 1456 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1457 return; 1458 } 1459 1460 // Save our api-specific connection information. 1461 data->port = port; 1462 data->destinationId = destination; 1463 connected_ = true; 1464 } 1465 1466 void MidiOutCore :: closePort( void ) 1467 { 1468 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1469 1470 if ( data->endpoint ) { 1471 MIDIEndpointDispose( data->endpoint ); 1472 data->endpoint = 0; 1473 } 1474 1475 if ( data->port ) { 1476 MIDIPortDispose( data->port ); 1477 data->port = 0; 1478 } 1479 1480 connected_ = false; 1481 } 1482 1483 void MidiOutCore :: setClientName ( const std::string& ) 1484 { 1485 1486 errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; 1487 error( RtMidiError::WARNING, errorString_ ); 1488 1489 } 1490 1491 void MidiOutCore :: setPortName ( const std::string& ) 1492 { 1493 1494 errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; 1495 error( RtMidiError::WARNING, errorString_ ); 1496 1497 } 1498 1499 void MidiOutCore :: openVirtualPort( const std::string &portName ) 1500 { 1501 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1502 1503 if ( data->endpoint ) { 1504 errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; 1505 error( RtMidiError::WARNING, errorString_ ); 1506 return; 1507 } 1508 1509 // Create a virtual MIDI output source. 1510 MIDIEndpointRef endpoint; 1511 CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1512 OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint ); 1513 CFRelease( portNameRef ); 1514 1515 if ( result != noErr ) { 1516 errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; 1517 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1518 return; 1519 } 1520 1521 // Save our api-specific connection information. 1522 data->endpoint = endpoint; 1523 } 1524 1525 void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) 1526 { 1527 // We use the MIDISendSysex() function to asynchronously send sysex 1528 // messages. Otherwise, we use a single CoreMidi MIDIPacket. 1529 unsigned int nBytes = static_cast<unsigned int> (size); 1530 if ( nBytes == 0 ) { 1531 errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; 1532 error( RtMidiError::WARNING, errorString_ ); 1533 return; 1534 } 1535 1536 if ( message[0] != 0xF0 && nBytes > 3 ) { 1537 errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; 1538 error( RtMidiError::WARNING, errorString_ ); 1539 return; 1540 } 1541 1542 MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); 1543 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); 1544 OSStatus result; 1545 1546 ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes; 1547 Byte buffer[bufsize+16]; // pad for other struct members 1548 ByteCount listSize = sizeof( buffer ); 1549 MIDIPacketList *packetList = (MIDIPacketList*)buffer; 1550 1551 ByteCount remainingBytes = nBytes; 1552 while ( remainingBytes ) { 1553 MIDIPacket *packet = MIDIPacketListInit( packetList ); 1554 // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer, 1555 // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one 1556 // MIDIPacket. Here, we reuse the memory allocated above on the stack for all. 1557 ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; 1558 const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes]; 1559 packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr ); 1560 remainingBytes -= bytesForPacket; 1561 1562 if ( !packet ) { 1563 errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; 1564 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1565 return; 1566 } 1567 1568 // Send to any destinations that may have connected to us. 1569 if ( data->endpoint ) { 1570 result = MIDIReceived( data->endpoint, packetList ); 1571 if ( result != noErr ) { 1572 errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; 1573 error( RtMidiError::WARNING, errorString_ ); 1574 } 1575 } 1576 1577 // And send to an explicit destination port if we're connected. 1578 if ( connected_ ) { 1579 result = MIDISend( data->port, data->destinationId, packetList ); 1580 if ( result != noErr ) { 1581 errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; 1582 error( RtMidiError::WARNING, errorString_ ); 1583 } 1584 } 1585 } 1586 } 1587 1588 #endif // __MACOSX_CORE__ 1589 1590 1591 //*********************************************************************// 1592 // API: LINUX ALSA SEQUENCER 1593 //*********************************************************************// 1594 1595 // API information found at: 1596 // - http://www.alsa-project.org/documentation.php#Library 1597 1598 #if defined(__LINUX_ALSA__) 1599 1600 // The ALSA Sequencer API is based on the use of a callback function for 1601 // MIDI input. 1602 // 1603 // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer 1604 // time stamps and other assorted fixes!!! 1605 1606 // If you don't need timestamping for incoming MIDI events, define the 1607 // preprocessor definition AVOID_TIMESTAMPING to save resources 1608 // associated with the ALSA sequencer queues. 1609 1610 #include <pthread.h> 1611 #include <sys/time.h> 1612 1613 // ALSA header file. 1614 #include <alsa/asoundlib.h> 1615 1616 // A structure to hold variables related to the ALSA API 1617 // implementation. 1618 struct AlsaMidiData { 1619 snd_seq_t *seq; 1620 unsigned int portNum; 1621 int vport; 1622 snd_seq_port_subscribe_t *subscription; 1623 snd_midi_event_t *coder; 1624 unsigned int bufferSize; 1625 unsigned int requestedBufferSize; 1626 unsigned char *buffer; 1627 pthread_t thread; 1628 pthread_t dummy_thread_id; 1629 snd_seq_real_time_t lastTime; 1630 int queue_id; // an input queue is needed to get timestamped events 1631 int trigger_fds[2]; 1632 }; 1633 1634 #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) 1635 1636 //*********************************************************************// 1637 // API: LINUX ALSA 1638 // Class Definitions: MidiInAlsa 1639 //*********************************************************************// 1640 1641 static void *alsaMidiHandler( void *ptr ) 1642 { 1643 MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr); 1644 AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData); 1645 1646 long nBytes; 1647 double time; 1648 bool continueSysex = false; 1649 bool doDecode = false; 1650 MidiInApi::MidiMessage message; 1651 int poll_fd_count; 1652 struct pollfd *poll_fds; 1653 1654 snd_seq_event_t *ev; 1655 int result; 1656 result = snd_midi_event_new( 0, &apiData->coder ); 1657 if ( result < 0 ) { 1658 data->doInput = false; 1659 std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; 1660 return 0; 1661 } 1662 unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); 1663 if ( buffer == NULL ) { 1664 data->doInput = false; 1665 snd_midi_event_free( apiData->coder ); 1666 apiData->coder = 0; 1667 std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; 1668 return 0; 1669 } 1670 snd_midi_event_init( apiData->coder ); 1671 snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages 1672 1673 poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; 1674 poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); 1675 snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); 1676 poll_fds[0].fd = apiData->trigger_fds[0]; 1677 poll_fds[0].events = POLLIN; 1678 1679 while ( data->doInput ) { 1680 1681 if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { 1682 // No data pending 1683 if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { 1684 if ( poll_fds[0].revents & POLLIN ) { 1685 bool dummy; 1686 int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); 1687 (void) res; 1688 } 1689 } 1690 continue; 1691 } 1692 1693 // If here, there should be data. 1694 result = snd_seq_event_input( apiData->seq, &ev ); 1695 if ( result == -ENOSPC ) { 1696 std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; 1697 continue; 1698 } 1699 else if ( result <= 0 ) { 1700 std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; 1701 perror("System reports"); 1702 continue; 1703 } 1704 1705 // This is a bit weird, but we now have to decode an ALSA MIDI 1706 // event (back) into MIDI bytes. We'll ignore non-MIDI types. 1707 if ( !continueSysex ) message.bytes.clear(); 1708 1709 doDecode = false; 1710 switch ( ev->type ) { 1711 1712 case SND_SEQ_EVENT_PORT_SUBSCRIBED: 1713 #if defined(__RTMIDI_DEBUG__) 1714 std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; 1715 #endif 1716 break; 1717 1718 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: 1719 #if defined(__RTMIDI_DEBUG__) 1720 std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; 1721 std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" 1722 << (int) ev->data.connect.sender.port 1723 << ", dest = " << (int) ev->data.connect.dest.client << ":" 1724 << (int) ev->data.connect.dest.port 1725 << std::endl; 1726 #endif 1727 break; 1728 1729 case SND_SEQ_EVENT_QFRAME: // MIDI time code 1730 if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1731 break; 1732 1733 case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick 1734 if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1735 break; 1736 1737 case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick 1738 if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1739 break; 1740 1741 case SND_SEQ_EVENT_SENSING: // Active sensing 1742 if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; 1743 break; 1744 1745 case SND_SEQ_EVENT_SYSEX: 1746 if ( (data->ignoreFlags & 0x01) ) break; 1747 if ( ev->data.ext.len > apiData->bufferSize ) { 1748 apiData->bufferSize = ev->data.ext.len; 1749 free( buffer ); 1750 buffer = (unsigned char *) malloc( apiData->bufferSize ); 1751 if ( buffer == NULL ) { 1752 data->doInput = false; 1753 std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; 1754 break; 1755 } 1756 } 1757 doDecode = true; 1758 break; 1759 1760 default: 1761 doDecode = true; 1762 } 1763 1764 if ( doDecode ) { 1765 1766 nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); 1767 if ( nBytes > 0 ) { 1768 // The ALSA sequencer has a maximum buffer size for MIDI sysex 1769 // events of 256 bytes. If a device sends sysex messages larger 1770 // than this, they are segmented into 256 byte chunks. So, 1771 // we'll watch for this and concatenate sysex chunks into a 1772 // single sysex message if necessary. 1773 if ( !continueSysex ) 1774 message.bytes.assign( buffer, &buffer[nBytes] ); 1775 else 1776 message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); 1777 1778 continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); 1779 if ( !continueSysex ) { 1780 1781 // Calculate the time stamp: 1782 message.timeStamp = 0.0; 1783 1784 // Method 1: Use the system time. 1785 //(void)gettimeofday(&tv, (struct timezone *)NULL); 1786 //time = (tv.tv_sec * 1000000) + tv.tv_usec; 1787 1788 // Method 2: Use the ALSA sequencer event time data. 1789 // (thanks to Pedro Lopez-Cabanillas!). 1790 1791 // Using method from: 1792 // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html 1793 1794 // Perform the carry for the later subtraction by updating y. 1795 // Temp var y is timespec because computation requires signed types, 1796 // while snd_seq_real_time_t has unsigned types. 1797 snd_seq_real_time_t &x( ev->time.time ); 1798 struct timespec y; 1799 y.tv_nsec = apiData->lastTime.tv_nsec; 1800 y.tv_sec = apiData->lastTime.tv_sec; 1801 if ( x.tv_nsec < y.tv_nsec ) { 1802 int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1; 1803 y.tv_nsec -= 1000000000 * nsec; 1804 y.tv_sec += nsec; 1805 } 1806 if ( x.tv_nsec - y.tv_nsec > 1000000000 ) { 1807 int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000; 1808 y.tv_nsec += 1000000000 * nsec; 1809 y.tv_sec -= nsec; 1810 } 1811 1812 // Compute the time difference. 1813 time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9; 1814 1815 apiData->lastTime = ev->time.time; 1816 1817 if ( data->firstMessage == true ) 1818 data->firstMessage = false; 1819 else 1820 message.timeStamp = time; 1821 } 1822 else { 1823 #if defined(__RTMIDI_DEBUG__) 1824 std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; 1825 #endif 1826 } 1827 } 1828 } 1829 1830 snd_seq_free_event( ev ); 1831 if ( message.bytes.size() == 0 || continueSysex ) continue; 1832 1833 if ( data->usingCallback ) { 1834 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 1835 callback( message.timeStamp, &message.bytes, data->userData ); 1836 } 1837 else { 1838 // As long as we haven't reached our queue size limit, push the message. 1839 if ( !data->queue.push( message ) ) 1840 std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; 1841 } 1842 } 1843 1844 if ( buffer ) free( buffer ); 1845 snd_midi_event_free( apiData->coder ); 1846 apiData->coder = 0; 1847 apiData->thread = apiData->dummy_thread_id; 1848 return 0; 1849 } 1850 1851 MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ) 1852 : MidiInApi( queueSizeLimit ) 1853 { 1854 MidiInAlsa::initialize( clientName ); 1855 } 1856 1857 MidiInAlsa :: ~MidiInAlsa() 1858 { 1859 // Close a connection if it exists. 1860 MidiInAlsa::closePort(); 1861 1862 // Shutdown the input thread. 1863 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 1864 if ( inputData_.doInput ) { 1865 inputData_.doInput = false; 1866 int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); 1867 (void) res; 1868 if ( !pthread_equal(data->thread, data->dummy_thread_id) ) 1869 pthread_join( data->thread, NULL ); 1870 } 1871 1872 // Cleanup. 1873 close ( data->trigger_fds[0] ); 1874 close ( data->trigger_fds[1] ); 1875 if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); 1876 #ifndef AVOID_TIMESTAMPING 1877 snd_seq_free_queue( data->seq, data->queue_id ); 1878 #endif 1879 snd_seq_close( data->seq ); 1880 delete data; 1881 } 1882 1883 void MidiInAlsa :: initialize( const std::string& clientName ) 1884 { 1885 // Set up the ALSA sequencer client. 1886 snd_seq_t *seq; 1887 int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK ); 1888 if ( result < 0 ) { 1889 errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; 1890 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1891 return; 1892 } 1893 1894 // Set client name. 1895 snd_seq_set_client_name( seq, clientName.c_str() ); 1896 1897 // Save our api-specific connection information. 1898 AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; 1899 data->seq = seq; 1900 data->portNum = -1; 1901 data->vport = -1; 1902 data->subscription = 0; 1903 data->dummy_thread_id = pthread_self(); 1904 data->thread = data->dummy_thread_id; 1905 data->trigger_fds[0] = -1; 1906 data->trigger_fds[1] = -1; 1907 data->bufferSize = inputData_.bufferSize; 1908 apiData_ = (void *) data; 1909 inputData_.apiData = (void *) data; 1910 1911 if ( pipe(data->trigger_fds) == -1 ) { 1912 errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; 1913 error( RtMidiError::DRIVER_ERROR, errorString_ ); 1914 return; 1915 } 1916 1917 // Create the input queue 1918 #ifndef AVOID_TIMESTAMPING 1919 data->queue_id = snd_seq_alloc_named_queue( seq, "RtMidi Queue" ); 1920 // Set arbitrary tempo (mm=100) and resolution (240) 1921 snd_seq_queue_tempo_t *qtempo; 1922 snd_seq_queue_tempo_alloca( &qtempo ); 1923 snd_seq_queue_tempo_set_tempo( qtempo, 600000 ); 1924 snd_seq_queue_tempo_set_ppq( qtempo, 240 ); 1925 snd_seq_set_queue_tempo( data->seq, data->queue_id, qtempo ); 1926 snd_seq_drain_output( data->seq ); 1927 #endif 1928 } 1929 1930 // This function is used to count or get the pinfo structure for a given port number. 1931 unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) 1932 { 1933 snd_seq_client_info_t *cinfo; 1934 int client; 1935 int count = 0; 1936 snd_seq_client_info_alloca( &cinfo ); 1937 1938 snd_seq_client_info_set_client( cinfo, -1 ); 1939 while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { 1940 client = snd_seq_client_info_get_client( cinfo ); 1941 if ( client == 0 ) continue; 1942 // Reset query info 1943 snd_seq_port_info_set_client( pinfo, client ); 1944 snd_seq_port_info_set_port( pinfo, -1 ); 1945 while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { 1946 unsigned int atyp = snd_seq_port_info_get_type( pinfo ); 1947 if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && 1948 ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) && 1949 ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue; 1950 1951 unsigned int caps = snd_seq_port_info_get_capability( pinfo ); 1952 if ( ( caps & type ) != type ) continue; 1953 if ( count == portNumber ) return 1; 1954 ++count; 1955 } 1956 } 1957 1958 // If a negative portNumber was used, return the port count. 1959 if ( portNumber < 0 ) return count; 1960 return 0; 1961 } 1962 1963 unsigned int MidiInAlsa :: getPortCount() 1964 { 1965 snd_seq_port_info_t *pinfo; 1966 snd_seq_port_info_alloca( &pinfo ); 1967 1968 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 1969 return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); 1970 } 1971 1972 std::string MidiInAlsa :: getPortName( unsigned int portNumber ) 1973 { 1974 snd_seq_client_info_t *cinfo; 1975 snd_seq_port_info_t *pinfo; 1976 snd_seq_client_info_alloca( &cinfo ); 1977 snd_seq_port_info_alloca( &pinfo ); 1978 1979 std::string stringName; 1980 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 1981 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { 1982 int cnum = snd_seq_port_info_get_client( pinfo ); 1983 snd_seq_get_any_client_info( data->seq, cnum, cinfo ); 1984 std::ostringstream os; 1985 os << snd_seq_client_info_get_name( cinfo ); 1986 os << ":"; 1987 os << snd_seq_port_info_get_name( pinfo ); 1988 os << " "; // These lines added to make sure devices are listed 1989 os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names 1990 os << ":"; 1991 os << snd_seq_port_info_get_port( pinfo ); 1992 stringName = os.str(); 1993 return stringName; 1994 } 1995 1996 // If we get here, we didn't find a match. 1997 errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; 1998 error( RtMidiError::WARNING, errorString_ ); 1999 return stringName; 2000 } 2001 2002 void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portName ) 2003 { 2004 if ( connected_ ) { 2005 errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; 2006 error( RtMidiError::WARNING, errorString_ ); 2007 return; 2008 } 2009 2010 unsigned int nSrc = this->getPortCount(); 2011 if ( nSrc < 1 ) { 2012 errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; 2013 error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2014 return; 2015 } 2016 2017 snd_seq_port_info_t *src_pinfo; 2018 snd_seq_port_info_alloca( &src_pinfo ); 2019 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2020 if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { 2021 std::ostringstream ost; 2022 ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2023 errorString_ = ost.str(); 2024 error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2025 return; 2026 } 2027 2028 snd_seq_addr_t sender, receiver; 2029 sender.client = snd_seq_port_info_get_client( src_pinfo ); 2030 sender.port = snd_seq_port_info_get_port( src_pinfo ); 2031 receiver.client = snd_seq_client_id( data->seq ); 2032 2033 snd_seq_port_info_t *pinfo; 2034 snd_seq_port_info_alloca( &pinfo ); 2035 if ( data->vport < 0 ) { 2036 snd_seq_port_info_set_client( pinfo, 0 ); 2037 snd_seq_port_info_set_port( pinfo, 0 ); 2038 snd_seq_port_info_set_capability( pinfo, 2039 SND_SEQ_PORT_CAP_WRITE | 2040 SND_SEQ_PORT_CAP_SUBS_WRITE ); 2041 snd_seq_port_info_set_type( pinfo, 2042 SND_SEQ_PORT_TYPE_MIDI_GENERIC | 2043 SND_SEQ_PORT_TYPE_APPLICATION ); 2044 snd_seq_port_info_set_midi_channels(pinfo, 16); 2045 #ifndef AVOID_TIMESTAMPING 2046 snd_seq_port_info_set_timestamping( pinfo, 1 ); 2047 snd_seq_port_info_set_timestamp_real( pinfo, 1 ); 2048 snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); 2049 #endif 2050 snd_seq_port_info_set_name( pinfo, portName.c_str() ); 2051 data->vport = snd_seq_create_port( data->seq, pinfo ); 2052 2053 if ( data->vport < 0 ) { 2054 errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; 2055 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2056 return; 2057 } 2058 data->vport = snd_seq_port_info_get_port( pinfo ); 2059 } 2060 2061 receiver.port = data->vport; 2062 2063 if ( !data->subscription ) { 2064 // Make subscription 2065 if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { 2066 errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; 2067 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2068 return; 2069 } 2070 snd_seq_port_subscribe_set_sender( data->subscription, &sender ); 2071 snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); 2072 if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { 2073 snd_seq_port_subscribe_free( data->subscription ); 2074 data->subscription = 0; 2075 errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; 2076 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2077 return; 2078 } 2079 } 2080 2081 if ( inputData_.doInput == false ) { 2082 // Start the input queue 2083 #ifndef AVOID_TIMESTAMPING 2084 snd_seq_start_queue( data->seq, data->queue_id, NULL ); 2085 snd_seq_drain_output( data->seq ); 2086 #endif 2087 // Start our MIDI input thread. 2088 pthread_attr_t attr; 2089 pthread_attr_init( &attr ); 2090 pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); 2091 pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); 2092 2093 inputData_.doInput = true; 2094 int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); 2095 pthread_attr_destroy( &attr ); 2096 if ( err ) { 2097 snd_seq_unsubscribe_port( data->seq, data->subscription ); 2098 snd_seq_port_subscribe_free( data->subscription ); 2099 data->subscription = 0; 2100 inputData_.doInput = false; 2101 errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; 2102 error( RtMidiError::THREAD_ERROR, errorString_ ); 2103 return; 2104 } 2105 } 2106 2107 connected_ = true; 2108 } 2109 2110 void MidiInAlsa :: openVirtualPort( const std::string &portName ) 2111 { 2112 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2113 if ( data->vport < 0 ) { 2114 snd_seq_port_info_t *pinfo; 2115 snd_seq_port_info_alloca( &pinfo ); 2116 snd_seq_port_info_set_capability( pinfo, 2117 SND_SEQ_PORT_CAP_WRITE | 2118 SND_SEQ_PORT_CAP_SUBS_WRITE ); 2119 snd_seq_port_info_set_type( pinfo, 2120 SND_SEQ_PORT_TYPE_MIDI_GENERIC | 2121 SND_SEQ_PORT_TYPE_APPLICATION ); 2122 snd_seq_port_info_set_midi_channels( pinfo, 16 ); 2123 #ifndef AVOID_TIMESTAMPING 2124 snd_seq_port_info_set_timestamping( pinfo, 1 ); 2125 snd_seq_port_info_set_timestamp_real( pinfo, 1 ); 2126 snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); 2127 #endif 2128 snd_seq_port_info_set_name( pinfo, portName.c_str() ); 2129 data->vport = snd_seq_create_port( data->seq, pinfo ); 2130 2131 if ( data->vport < 0 ) { 2132 errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; 2133 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2134 return; 2135 } 2136 data->vport = snd_seq_port_info_get_port( pinfo ); 2137 } 2138 2139 if ( inputData_.doInput == false ) { 2140 // Wait for old thread to stop, if still running 2141 if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) 2142 pthread_join( data->thread, NULL ); 2143 2144 // Start the input queue 2145 #ifndef AVOID_TIMESTAMPING 2146 snd_seq_start_queue( data->seq, data->queue_id, NULL ); 2147 snd_seq_drain_output( data->seq ); 2148 #endif 2149 // Start our MIDI input thread. 2150 pthread_attr_t attr; 2151 pthread_attr_init( &attr ); 2152 pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); 2153 pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); 2154 2155 inputData_.doInput = true; 2156 int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); 2157 pthread_attr_destroy( &attr ); 2158 if ( err ) { 2159 if ( data->subscription ) { 2160 snd_seq_unsubscribe_port( data->seq, data->subscription ); 2161 snd_seq_port_subscribe_free( data->subscription ); 2162 data->subscription = 0; 2163 } 2164 inputData_.doInput = false; 2165 errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; 2166 error( RtMidiError::THREAD_ERROR, errorString_ ); 2167 return; 2168 } 2169 } 2170 } 2171 2172 void MidiInAlsa :: closePort( void ) 2173 { 2174 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2175 2176 if ( connected_ ) { 2177 if ( data->subscription ) { 2178 snd_seq_unsubscribe_port( data->seq, data->subscription ); 2179 snd_seq_port_subscribe_free( data->subscription ); 2180 data->subscription = 0; 2181 } 2182 // Stop the input queue 2183 #ifndef AVOID_TIMESTAMPING 2184 snd_seq_stop_queue( data->seq, data->queue_id, NULL ); 2185 snd_seq_drain_output( data->seq ); 2186 #endif 2187 connected_ = false; 2188 } 2189 2190 // Stop thread to avoid triggering the callback, while the port is intended to be closed 2191 if ( inputData_.doInput ) { 2192 inputData_.doInput = false; 2193 int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); 2194 (void) res; 2195 if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) 2196 pthread_join( data->thread, NULL ); 2197 } 2198 } 2199 2200 void MidiInAlsa :: setClientName( const std::string &clientName ) 2201 { 2202 2203 AlsaMidiData *data = static_cast<AlsaMidiData *> ( apiData_ ); 2204 snd_seq_set_client_name( data->seq, clientName.c_str() ); 2205 2206 } 2207 2208 void MidiInAlsa :: setPortName( const std::string &portName ) 2209 { 2210 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2211 snd_seq_port_info_t *pinfo; 2212 snd_seq_port_info_alloca( &pinfo ); 2213 snd_seq_get_port_info( data->seq, data->vport, pinfo ); 2214 snd_seq_port_info_set_name( pinfo, portName.c_str() ); 2215 snd_seq_set_port_info( data->seq, data->vport, pinfo ); 2216 } 2217 2218 //*********************************************************************// 2219 // API: LINUX ALSA 2220 // Class Definitions: MidiOutAlsa 2221 //*********************************************************************// 2222 2223 MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi() 2224 { 2225 MidiOutAlsa::initialize( clientName ); 2226 } 2227 2228 MidiOutAlsa :: ~MidiOutAlsa() 2229 { 2230 // Close a connection if it exists. 2231 MidiOutAlsa::closePort(); 2232 2233 // Cleanup. 2234 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2235 if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); 2236 if ( data->coder ) snd_midi_event_free( data->coder ); 2237 if ( data->buffer ) free( data->buffer ); 2238 snd_seq_close( data->seq ); 2239 delete data; 2240 } 2241 2242 void MidiOutAlsa :: initialize( const std::string& clientName ) 2243 { 2244 // Set up the ALSA sequencer client. 2245 snd_seq_t *seq; 2246 int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); 2247 if ( result1 < 0 ) { 2248 errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; 2249 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2250 return; 2251 } 2252 2253 // Set client name. 2254 snd_seq_set_client_name( seq, clientName.c_str() ); 2255 2256 // Save our api-specific connection information. 2257 AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; 2258 data->seq = seq; 2259 data->portNum = -1; 2260 data->vport = -1; 2261 data->bufferSize = 32; 2262 data->coder = 0; 2263 data->buffer = 0; 2264 int result = snd_midi_event_new( data->bufferSize, &data->coder ); 2265 if ( result < 0 ) { 2266 delete data; 2267 errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; 2268 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2269 return; 2270 } 2271 data->buffer = (unsigned char *) malloc( data->bufferSize ); 2272 if ( data->buffer == NULL ) { 2273 delete data; 2274 errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; 2275 error( RtMidiError::MEMORY_ERROR, errorString_ ); 2276 return; 2277 } 2278 snd_midi_event_init( data->coder ); 2279 apiData_ = (void *) data; 2280 } 2281 2282 unsigned int MidiOutAlsa :: getPortCount() 2283 { 2284 snd_seq_port_info_t *pinfo; 2285 snd_seq_port_info_alloca( &pinfo ); 2286 2287 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2288 return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); 2289 } 2290 2291 std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) 2292 { 2293 snd_seq_client_info_t *cinfo; 2294 snd_seq_port_info_t *pinfo; 2295 snd_seq_client_info_alloca( &cinfo ); 2296 snd_seq_port_info_alloca( &pinfo ); 2297 2298 std::string stringName; 2299 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2300 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { 2301 int cnum = snd_seq_port_info_get_client( pinfo ); 2302 snd_seq_get_any_client_info( data->seq, cnum, cinfo ); 2303 std::ostringstream os; 2304 os << snd_seq_client_info_get_name( cinfo ); 2305 os << ":"; 2306 os << snd_seq_port_info_get_name( pinfo ); 2307 os << " "; // These lines added to make sure devices are listed 2308 os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names 2309 os << ":"; 2310 os << snd_seq_port_info_get_port( pinfo ); 2311 stringName = os.str(); 2312 return stringName; 2313 } 2314 2315 // If we get here, we didn't find a match. 2316 errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; 2317 error( RtMidiError::WARNING, errorString_ ); 2318 return stringName; 2319 } 2320 2321 void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portName ) 2322 { 2323 if ( connected_ ) { 2324 errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; 2325 error( RtMidiError::WARNING, errorString_ ); 2326 return; 2327 } 2328 2329 unsigned int nSrc = this->getPortCount(); 2330 if ( nSrc < 1 ) { 2331 errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; 2332 error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2333 return; 2334 } 2335 2336 snd_seq_port_info_t *pinfo; 2337 snd_seq_port_info_alloca( &pinfo ); 2338 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2339 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { 2340 std::ostringstream ost; 2341 ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2342 errorString_ = ost.str(); 2343 error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2344 return; 2345 } 2346 2347 snd_seq_addr_t sender, receiver; 2348 receiver.client = snd_seq_port_info_get_client( pinfo ); 2349 receiver.port = snd_seq_port_info_get_port( pinfo ); 2350 sender.client = snd_seq_client_id( data->seq ); 2351 2352 if ( data->vport < 0 ) { 2353 data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), 2354 SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, 2355 SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); 2356 if ( data->vport < 0 ) { 2357 errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; 2358 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2359 return; 2360 } 2361 } 2362 2363 sender.port = data->vport; 2364 2365 // Make subscription 2366 if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { 2367 snd_seq_port_subscribe_free( data->subscription ); 2368 errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; 2369 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2370 return; 2371 } 2372 snd_seq_port_subscribe_set_sender( data->subscription, &sender ); 2373 snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); 2374 snd_seq_port_subscribe_set_time_update( data->subscription, 1 ); 2375 snd_seq_port_subscribe_set_time_real( data->subscription, 1 ); 2376 if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { 2377 snd_seq_port_subscribe_free( data->subscription ); 2378 errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; 2379 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2380 return; 2381 } 2382 2383 connected_ = true; 2384 } 2385 2386 void MidiOutAlsa :: closePort( void ) 2387 { 2388 if ( connected_ ) { 2389 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2390 snd_seq_unsubscribe_port( data->seq, data->subscription ); 2391 snd_seq_port_subscribe_free( data->subscription ); 2392 data->subscription = 0; 2393 connected_ = false; 2394 } 2395 } 2396 2397 void MidiOutAlsa :: setClientName( const std::string &clientName ) 2398 { 2399 2400 AlsaMidiData *data = static_cast<AlsaMidiData *> ( apiData_ ); 2401 snd_seq_set_client_name( data->seq, clientName.c_str() ); 2402 2403 } 2404 2405 void MidiOutAlsa :: setPortName( const std::string &portName ) 2406 { 2407 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2408 snd_seq_port_info_t *pinfo; 2409 snd_seq_port_info_alloca( &pinfo ); 2410 snd_seq_get_port_info( data->seq, data->vport, pinfo ); 2411 snd_seq_port_info_set_name( pinfo, portName.c_str() ); 2412 snd_seq_set_port_info( data->seq, data->vport, pinfo ); 2413 } 2414 2415 void MidiOutAlsa :: openVirtualPort( const std::string &portName ) 2416 { 2417 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2418 if ( data->vport < 0 ) { 2419 data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), 2420 SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, 2421 SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); 2422 2423 if ( data->vport < 0 ) { 2424 errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; 2425 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2426 } 2427 } 2428 } 2429 2430 void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) 2431 { 2432 long result; 2433 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); 2434 unsigned int nBytes = static_cast<unsigned int> (size); 2435 if ( nBytes > data->bufferSize ) { 2436 data->bufferSize = nBytes; 2437 result = snd_midi_event_resize_buffer( data->coder, nBytes ); 2438 if ( result != 0 ) { 2439 errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; 2440 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2441 return; 2442 } 2443 free (data->buffer); 2444 data->buffer = (unsigned char *) malloc( data->bufferSize ); 2445 if ( data->buffer == NULL ) { 2446 errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; 2447 error( RtMidiError::MEMORY_ERROR, errorString_ ); 2448 return; 2449 } 2450 } 2451 2452 for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message[i]; 2453 2454 unsigned int offset = 0; 2455 while (offset < nBytes) { 2456 snd_seq_event_t ev; 2457 snd_seq_ev_clear( &ev ); 2458 snd_seq_ev_set_source( &ev, data->vport ); 2459 snd_seq_ev_set_subs( &ev ); 2460 snd_seq_ev_set_direct( &ev ); 2461 result = snd_midi_event_encode( data->coder, data->buffer + offset, 2462 (long)(nBytes - offset), &ev ); 2463 if ( result < 0 ) { 2464 errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; 2465 error( RtMidiError::WARNING, errorString_ ); 2466 return; 2467 } 2468 2469 if ( ev.type == SND_SEQ_EVENT_NONE ) { 2470 errorString_ = "MidiOutAlsa::sendMessage: incomplete message!"; 2471 error( RtMidiError::WARNING, errorString_ ); 2472 return; 2473 } 2474 2475 offset += result; 2476 2477 // Send the event. 2478 result = snd_seq_event_output( data->seq, &ev ); 2479 if ( result < 0 ) { 2480 errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; 2481 error( RtMidiError::WARNING, errorString_ ); 2482 return; 2483 } 2484 } 2485 snd_seq_drain_output( data->seq ); 2486 } 2487 2488 #endif // __LINUX_ALSA__ 2489 2490 2491 //*********************************************************************// 2492 // API: Windows Multimedia Library (MM) 2493 //*********************************************************************// 2494 2495 // API information deciphered from: 2496 // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp 2497 2498 // Thanks to Jean-Baptiste Berruchon for the sysex code. 2499 2500 #if defined(__WINDOWS_MM__) 2501 2502 // The Windows MM API is based on the use of a callback function for 2503 // MIDI input. We convert the system specific time stamps to delta 2504 // time values. 2505 2506 // Windows MM MIDI header files. 2507 #include <windows.h> 2508 #include <mmsystem.h> 2509 2510 // Convert a null-terminated wide string or ANSI-encoded string to UTF-8. 2511 static std::string ConvertToUTF8(const TCHAR *str) 2512 { 2513 std::string u8str; 2514 const WCHAR *wstr = L""; 2515 #if defined( UNICODE ) || defined( _UNICODE ) 2516 wstr = str; 2517 #else 2518 // Convert from ANSI encoding to wide string 2519 int wlength = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); 2520 std::wstring wstrtemp; 2521 if ( wlength ) 2522 { 2523 wstrtemp.assign( wlength - 1, 0 ); 2524 MultiByteToWideChar( CP_ACP, 0, str, -1, &wstrtemp[0], wlength ); 2525 wstr = &wstrtemp[0]; 2526 } 2527 #endif 2528 // Convert from wide string to UTF-8 2529 int length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL ); 2530 if ( length ) 2531 { 2532 u8str.assign( length - 1, 0 ); 2533 length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL ); 2534 } 2535 return u8str; 2536 } 2537 2538 // A structure to hold variables related to the CoreMIDI API 2539 // implementation. 2540 struct WinMidiData { 2541 HMIDIIN inHandle; // Handle to Midi Input Device 2542 HMIDIOUT outHandle; // Handle to Midi Output Device 2543 DWORD lastTime; 2544 MidiInApi::MidiMessage message; 2545 std::vector<LPMIDIHDR> sysexBuffer; 2546 CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo 2547 }; 2548 2549 //*********************************************************************// 2550 // API: Windows MM 2551 // Class Definitions: MidiInWinMM 2552 //*********************************************************************// 2553 2554 static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, 2555 UINT inputStatus, 2556 DWORD_PTR instancePtr, 2557 DWORD_PTR midiMessage, 2558 DWORD timestamp ) 2559 { 2560 if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; 2561 2562 //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr); 2563 MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; 2564 WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData); 2565 2566 // Calculate time stamp. 2567 if ( data->firstMessage == true ) { 2568 apiData->message.timeStamp = 0.0; 2569 data->firstMessage = false; 2570 } 2571 else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; 2572 2573 if ( inputStatus == MIM_DATA ) { // Channel or system message 2574 2575 // Make sure the first byte is a status byte. 2576 unsigned char status = (unsigned char) (midiMessage & 0x000000FF); 2577 if ( !(status & 0x80) ) return; 2578 2579 // Determine the number of bytes in the MIDI message. 2580 unsigned short nBytes = 1; 2581 if ( status < 0xC0 ) nBytes = 3; 2582 else if ( status < 0xE0 ) nBytes = 2; 2583 else if ( status < 0xF0 ) nBytes = 3; 2584 else if ( status == 0xF1 ) { 2585 if ( data->ignoreFlags & 0x02 ) return; 2586 else nBytes = 2; 2587 } 2588 else if ( status == 0xF2 ) nBytes = 3; 2589 else if ( status == 0xF3 ) nBytes = 2; 2590 else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { 2591 // A MIDI timing tick message and we're ignoring it. 2592 return; 2593 } 2594 else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { 2595 // A MIDI active sensing message and we're ignoring it. 2596 return; 2597 } 2598 2599 // Copy bytes to our MIDI message. 2600 unsigned char *ptr = (unsigned char *) &midiMessage; 2601 for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ ); 2602 } 2603 else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) 2604 MIDIHDR *sysex = ( MIDIHDR *) midiMessage; 2605 if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { 2606 // Sysex message and we're not ignoring it 2607 for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) 2608 apiData->message.bytes.push_back( sysex->lpData[i] ); 2609 } 2610 2611 // The WinMM API requires that the sysex buffer be requeued after 2612 // input of each sysex message. Even if we are ignoring sysex 2613 // messages, we still need to requeue the buffer in case the user 2614 // decides to not ignore sysex messages in the future. However, 2615 // it seems that WinMM calls this function with an empty sysex 2616 // buffer when an application closes and in this case, we should 2617 // avoid requeueing it, else the computer suddenly reboots after 2618 // one or two minutes. 2619 if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { 2620 //if ( sysex->dwBytesRecorded > 0 ) { 2621 EnterCriticalSection( &(apiData->_mutex) ); 2622 MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); 2623 LeaveCriticalSection( &(apiData->_mutex) ); 2624 if ( result != MMSYSERR_NOERROR ) 2625 std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; 2626 2627 if ( data->ignoreFlags & 0x01 ) return; 2628 } 2629 else return; 2630 } 2631 2632 // Save the time of the last non-filtered message 2633 apiData->lastTime = timestamp; 2634 2635 if ( data->usingCallback ) { 2636 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 2637 callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); 2638 } 2639 else { 2640 // As long as we haven't reached our queue size limit, push the message. 2641 if ( !data->queue.push( apiData->message ) ) 2642 std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n"; 2643 } 2644 2645 // Clear the vector for the next input message. 2646 apiData->message.bytes.clear(); 2647 } 2648 2649 MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ) 2650 : MidiInApi( queueSizeLimit ) 2651 { 2652 MidiInWinMM::initialize( clientName ); 2653 } 2654 2655 MidiInWinMM :: ~MidiInWinMM() 2656 { 2657 // Close a connection if it exists. 2658 MidiInWinMM::closePort(); 2659 2660 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 2661 DeleteCriticalSection( &(data->_mutex) ); 2662 2663 // Cleanup. 2664 delete data; 2665 } 2666 2667 void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) 2668 { 2669 // We'll issue a warning here if no devices are available but not 2670 // throw an error since the user can plugin something later. 2671 unsigned int nDevices = midiInGetNumDevs(); 2672 if ( nDevices == 0 ) { 2673 errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; 2674 error( RtMidiError::WARNING, errorString_ ); 2675 } 2676 2677 // Save our api-specific connection information. 2678 WinMidiData *data = (WinMidiData *) new WinMidiData; 2679 apiData_ = (void *) data; 2680 inputData_.apiData = (void *) data; 2681 data->message.bytes.clear(); // needs to be empty for first input message 2682 2683 if ( !InitializeCriticalSectionAndSpinCount( &(data->_mutex), 0x00000400 ) ) { 2684 errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; 2685 error( RtMidiError::WARNING, errorString_ ); 2686 } 2687 } 2688 2689 void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) 2690 { 2691 if ( connected_ ) { 2692 errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; 2693 error( RtMidiError::WARNING, errorString_ ); 2694 return; 2695 } 2696 2697 unsigned int nDevices = midiInGetNumDevs(); 2698 if (nDevices == 0) { 2699 errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; 2700 error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2701 return; 2702 } 2703 2704 if ( portNumber >= nDevices ) { 2705 std::ostringstream ost; 2706 ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2707 errorString_ = ost.str(); 2708 error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2709 return; 2710 } 2711 2712 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 2713 MMRESULT result = midiInOpen( &data->inHandle, 2714 portNumber, 2715 (DWORD_PTR)&midiInputCallback, 2716 (DWORD_PTR)&inputData_, 2717 CALLBACK_FUNCTION ); 2718 if ( result != MMSYSERR_NOERROR ) { 2719 errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; 2720 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2721 return; 2722 } 2723 2724 // Allocate and init the sysex buffers. 2725 data->sysexBuffer.resize( inputData_.bufferCount ); 2726 for ( unsigned int i=0; i < inputData_.bufferCount; ++i ) { 2727 data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; 2728 data->sysexBuffer[i]->lpData = new char[ inputData_.bufferSize ]; 2729 data->sysexBuffer[i]->dwBufferLength = inputData_.bufferSize; 2730 data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator 2731 data->sysexBuffer[i]->dwFlags = 0; 2732 2733 result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); 2734 if ( result != MMSYSERR_NOERROR ) { 2735 midiInClose( data->inHandle ); 2736 data->inHandle = 0; 2737 errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; 2738 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2739 return; 2740 } 2741 2742 // Register the buffer. 2743 result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); 2744 if ( result != MMSYSERR_NOERROR ) { 2745 midiInClose( data->inHandle ); 2746 data->inHandle = 0; 2747 errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; 2748 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2749 return; 2750 } 2751 } 2752 2753 result = midiInStart( data->inHandle ); 2754 if ( result != MMSYSERR_NOERROR ) { 2755 midiInClose( data->inHandle ); 2756 data->inHandle = 0; 2757 errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; 2758 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2759 return; 2760 } 2761 2762 connected_ = true; 2763 } 2764 2765 void MidiInWinMM :: openVirtualPort( const std::string &/*portName*/ ) 2766 { 2767 // This function cannot be implemented for the Windows MM MIDI API. 2768 errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; 2769 error( RtMidiError::WARNING, errorString_ ); 2770 } 2771 2772 void MidiInWinMM :: closePort( void ) 2773 { 2774 if ( connected_ ) { 2775 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 2776 EnterCriticalSection( &(data->_mutex) ); 2777 midiInReset( data->inHandle ); 2778 midiInStop( data->inHandle ); 2779 2780 for ( size_t i=0; i < data->sysexBuffer.size(); ++i ) { 2781 int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); 2782 delete [] data->sysexBuffer[i]->lpData; 2783 delete [] data->sysexBuffer[i]; 2784 if ( result != MMSYSERR_NOERROR ) { 2785 midiInClose( data->inHandle ); 2786 data->inHandle = 0; 2787 errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; 2788 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2789 return; 2790 } 2791 } 2792 2793 midiInClose( data->inHandle ); 2794 data->inHandle = 0; 2795 connected_ = false; 2796 LeaveCriticalSection( &(data->_mutex) ); 2797 } 2798 } 2799 2800 void MidiInWinMM :: setClientName ( const std::string& ) 2801 { 2802 2803 errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; 2804 error( RtMidiError::WARNING, errorString_ ); 2805 2806 } 2807 2808 void MidiInWinMM :: setPortName ( const std::string& ) 2809 { 2810 2811 errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; 2812 error( RtMidiError::WARNING, errorString_ ); 2813 2814 } 2815 2816 unsigned int MidiInWinMM :: getPortCount() 2817 { 2818 return midiInGetNumDevs(); 2819 } 2820 2821 std::string MidiInWinMM :: getPortName( unsigned int portNumber ) 2822 { 2823 std::string stringName; 2824 unsigned int nDevices = midiInGetNumDevs(); 2825 if ( portNumber >= nDevices ) { 2826 std::ostringstream ost; 2827 ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2828 errorString_ = ost.str(); 2829 error( RtMidiError::WARNING, errorString_ ); 2830 return stringName; 2831 } 2832 2833 MIDIINCAPS deviceCaps; 2834 midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); 2835 stringName = ConvertToUTF8( deviceCaps.szPname ); 2836 2837 // Next lines added to add the portNumber to the name so that 2838 // the device's names are sure to be listed with individual names 2839 // even when they have the same brand name 2840 #ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES 2841 std::ostringstream os; 2842 os << " "; 2843 os << portNumber; 2844 stringName += os.str(); 2845 #endif 2846 2847 return stringName; 2848 } 2849 2850 //*********************************************************************// 2851 // API: Windows MM 2852 // Class Definitions: MidiOutWinMM 2853 //*********************************************************************// 2854 2855 MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi() 2856 { 2857 MidiOutWinMM::initialize( clientName ); 2858 } 2859 2860 MidiOutWinMM :: ~MidiOutWinMM() 2861 { 2862 // Close a connection if it exists. 2863 MidiOutWinMM::closePort(); 2864 2865 // Cleanup. 2866 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 2867 delete data; 2868 } 2869 2870 void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) 2871 { 2872 // We'll issue a warning here if no devices are available but not 2873 // throw an error since the user can plug something in later. 2874 unsigned int nDevices = midiOutGetNumDevs(); 2875 if ( nDevices == 0 ) { 2876 errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; 2877 error( RtMidiError::WARNING, errorString_ ); 2878 } 2879 2880 // Save our api-specific connection information. 2881 WinMidiData *data = (WinMidiData *) new WinMidiData; 2882 apiData_ = (void *) data; 2883 } 2884 2885 unsigned int MidiOutWinMM :: getPortCount() 2886 { 2887 return midiOutGetNumDevs(); 2888 } 2889 2890 std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) 2891 { 2892 std::string stringName; 2893 unsigned int nDevices = midiOutGetNumDevs(); 2894 if ( portNumber >= nDevices ) { 2895 std::ostringstream ost; 2896 ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2897 errorString_ = ost.str(); 2898 error( RtMidiError::WARNING, errorString_ ); 2899 return stringName; 2900 } 2901 2902 MIDIOUTCAPS deviceCaps; 2903 midiOutGetDevCaps( portNumber, &deviceCaps, sizeof( MIDIOUTCAPS ) ); 2904 stringName = ConvertToUTF8( deviceCaps.szPname ); 2905 2906 // Next lines added to add the portNumber to the name so that 2907 // the device's names are sure to be listed with individual names 2908 // even when they have the same brand name 2909 std::ostringstream os; 2910 #ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES 2911 os << " "; 2912 os << portNumber; 2913 stringName += os.str(); 2914 #endif 2915 2916 return stringName; 2917 } 2918 2919 void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) 2920 { 2921 if ( connected_ ) { 2922 errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; 2923 error( RtMidiError::WARNING, errorString_ ); 2924 return; 2925 } 2926 2927 unsigned int nDevices = midiOutGetNumDevs(); 2928 if ( nDevices < 1 ) { 2929 errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; 2930 error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2931 return; 2932 } 2933 2934 if ( portNumber >= nDevices ) { 2935 std::ostringstream ost; 2936 ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2937 errorString_ = ost.str(); 2938 error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2939 return; 2940 } 2941 2942 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 2943 MMRESULT result = midiOutOpen( &data->outHandle, 2944 portNumber, 2945 (DWORD)NULL, 2946 (DWORD)NULL, 2947 CALLBACK_NULL ); 2948 if ( result != MMSYSERR_NOERROR ) { 2949 errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; 2950 error( RtMidiError::DRIVER_ERROR, errorString_ ); 2951 return; 2952 } 2953 2954 connected_ = true; 2955 } 2956 2957 void MidiOutWinMM :: closePort( void ) 2958 { 2959 if ( connected_ ) { 2960 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 2961 // Disabled because midiOutReset triggers 0x7b (if any note was ON) and 0x79 "Reset All 2962 // Controllers" (to all 16 channels) CC messages which is undesirable (see issue #222) 2963 // midiOutReset( data->outHandle ); 2964 2965 midiOutClose( data->outHandle ); 2966 data->outHandle = 0; 2967 connected_ = false; 2968 } 2969 } 2970 2971 void MidiOutWinMM :: setClientName ( const std::string& ) 2972 { 2973 2974 errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; 2975 error( RtMidiError::WARNING, errorString_ ); 2976 2977 } 2978 2979 void MidiOutWinMM :: setPortName ( const std::string& ) 2980 { 2981 2982 errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; 2983 error( RtMidiError::WARNING, errorString_ ); 2984 2985 } 2986 2987 void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ ) 2988 { 2989 // This function cannot be implemented for the Windows MM MIDI API. 2990 errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; 2991 error( RtMidiError::WARNING, errorString_ ); 2992 } 2993 2994 void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) 2995 { 2996 if ( !connected_ ) return; 2997 2998 unsigned int nBytes = static_cast<unsigned int>(size); 2999 if ( nBytes == 0 ) { 3000 errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; 3001 error( RtMidiError::WARNING, errorString_ ); 3002 return; 3003 } 3004 3005 MMRESULT result; 3006 WinMidiData *data = static_cast<WinMidiData *> (apiData_); 3007 if ( message[0] == 0xF0 ) { // Sysex message 3008 3009 // Allocate buffer for sysex data. 3010 char *buffer = (char *) malloc( nBytes ); 3011 if ( buffer == NULL ) { 3012 errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; 3013 error( RtMidiError::MEMORY_ERROR, errorString_ ); 3014 return; 3015 } 3016 3017 // Copy data to buffer. 3018 for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message[i]; 3019 3020 // Create and prepare MIDIHDR structure. 3021 MIDIHDR sysex; 3022 sysex.lpData = (LPSTR) buffer; 3023 sysex.dwBufferLength = nBytes; 3024 sysex.dwFlags = 0; 3025 result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof( MIDIHDR ) ); 3026 if ( result != MMSYSERR_NOERROR ) { 3027 free( buffer ); 3028 errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; 3029 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3030 return; 3031 } 3032 3033 // Send the message. 3034 result = midiOutLongMsg( data->outHandle, &sysex, sizeof( MIDIHDR ) ); 3035 if ( result != MMSYSERR_NOERROR ) { 3036 free( buffer ); 3037 errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; 3038 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3039 return; 3040 } 3041 3042 // Unprepare the buffer and MIDIHDR. 3043 while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof ( MIDIHDR ) ) ) Sleep( 1 ); 3044 free( buffer ); 3045 } 3046 else { // Channel or system message. 3047 3048 // Make sure the message size isn't too big. 3049 if ( nBytes > 3 ) { 3050 errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; 3051 error( RtMidiError::WARNING, errorString_ ); 3052 return; 3053 } 3054 3055 // Pack MIDI bytes into double word. 3056 DWORD packet; 3057 unsigned char *ptr = (unsigned char *) &packet; 3058 for ( unsigned int i=0; i<nBytes; ++i ) { 3059 *ptr = message[i]; 3060 ++ptr; 3061 } 3062 3063 // Send the message immediately. 3064 result = midiOutShortMsg( data->outHandle, packet ); 3065 if ( result != MMSYSERR_NOERROR ) { 3066 errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; 3067 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3068 } 3069 } 3070 } 3071 3072 #endif // __WINDOWS_MM__ 3073 3074 3075 //*********************************************************************// 3076 // API: UNIX JACK 3077 // 3078 // Written primarily by Alexander Svetalkin, with updates for delta 3079 // time by Gary Scavone, April 2011. 3080 // 3081 // *********************************************************************// 3082 3083 #if defined(__UNIX_JACK__) 3084 3085 // JACK header files 3086 #include <jack/jack.h> 3087 #include <jack/midiport.h> 3088 #include <jack/ringbuffer.h> 3089 #include <pthread.h> 3090 #include <sched.h> 3091 #ifdef HAVE_SEMAPHORE 3092 #include <semaphore.h> 3093 #endif 3094 3095 #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer 3096 3097 struct JackMidiData { 3098 jack_client_t *client; 3099 jack_port_t *port; 3100 jack_ringbuffer_t *buff; 3101 int buffMaxWrite; // actual writable size, usually 1 less than ringbuffer 3102 jack_time_t lastTime; 3103 #ifdef HAVE_SEMAPHORE 3104 sem_t sem_cleanup; 3105 sem_t sem_needpost; 3106 #endif 3107 MidiInApi :: RtMidiInData *rtMidiIn; 3108 }; 3109 3110 //*********************************************************************// 3111 // API: JACK 3112 // Class Definitions: MidiInJack 3113 //*********************************************************************// 3114 3115 static int jackProcessIn( jack_nframes_t nframes, void *arg ) 3116 { 3117 JackMidiData *jData = (JackMidiData *) arg; 3118 MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; 3119 jack_midi_event_t event; 3120 jack_time_t time; 3121 3122 // Is port created? 3123 if ( jData->port == NULL ) return 0; 3124 3125 void *buff = jack_port_get_buffer( jData->port, nframes ); 3126 bool& continueSysex = rtData->continueSysex; 3127 unsigned char& ignoreFlags = rtData->ignoreFlags; 3128 3129 // We have midi events in buffer 3130 int evCount = jack_midi_get_event_count( buff ); 3131 for (int j = 0; j < evCount; j++) { 3132 MidiInApi::MidiMessage& message = rtData->message; 3133 jack_midi_event_get( &event, buff, j ); 3134 3135 // Compute the delta time. 3136 time = jack_get_time(); 3137 if ( rtData->firstMessage == true ) { 3138 message.timeStamp = 0.0; 3139 rtData->firstMessage = false; 3140 } else 3141 message.timeStamp = ( time - jData->lastTime ) * 0.000001; 3142 3143 jData->lastTime = time; 3144 3145 if ( !continueSysex ) 3146 message.bytes.clear(); 3147 3148 if ( !( ( continueSysex || event.buffer[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) { 3149 // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx, 3150 // copy the event buffer into the MIDI message struct. 3151 for ( unsigned int i = 0; i < event.size; i++ ) 3152 message.bytes.push_back( event.buffer[i] ); 3153 } 3154 3155 switch ( event.buffer[0] ) { 3156 case 0xF0: 3157 // Start of a SysEx message 3158 continueSysex = event.buffer[event.size - 1] != 0xF7; 3159 if ( ignoreFlags & 0x01 ) continue; 3160 break; 3161 case 0xF1: 3162 case 0xF8: 3163 // MIDI Time Code or Timing Clock message 3164 if ( ignoreFlags & 0x02 ) continue; 3165 break; 3166 case 0xFE: 3167 // Active Sensing message 3168 if ( ignoreFlags & 0x04 ) continue; 3169 break; 3170 default: 3171 if ( continueSysex ) { 3172 // Continuation of a SysEx message 3173 continueSysex = event.buffer[event.size - 1] != 0xF7; 3174 if ( ignoreFlags & 0x01 ) continue; 3175 } 3176 // All other MIDI messages 3177 } 3178 3179 if ( !continueSysex ) { 3180 // If not a continuation of a SysEx message, 3181 // invoke the user callback function or queue the message. 3182 if ( rtData->usingCallback ) { 3183 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; 3184 callback( message.timeStamp, &message.bytes, rtData->userData ); 3185 } 3186 else { 3187 // As long as we haven't reached our queue size limit, push the message. 3188 if ( !rtData->queue.push( message ) ) 3189 std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; 3190 } 3191 } 3192 } 3193 3194 return 0; 3195 } 3196 3197 MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ) 3198 : MidiInApi( queueSizeLimit ) 3199 { 3200 MidiInJack::initialize( clientName ); 3201 } 3202 3203 void MidiInJack :: initialize( const std::string& clientName ) 3204 { 3205 JackMidiData *data = new JackMidiData; 3206 apiData_ = (void *) data; 3207 3208 data->rtMidiIn = &inputData_; 3209 data->port = NULL; 3210 data->client = NULL; 3211 this->clientName = clientName; 3212 3213 connect(); 3214 } 3215 3216 void MidiInJack :: connect() 3217 { 3218 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3219 if ( data->client ) 3220 return; 3221 3222 // Initialize JACK client 3223 if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { 3224 errorString_ = "MidiInJack::initialize: JACK server not running?"; 3225 error( RtMidiError::WARNING, errorString_ ); 3226 return; 3227 } 3228 3229 jack_set_process_callback( data->client, jackProcessIn, data ); 3230 jack_activate( data->client ); 3231 } 3232 3233 MidiInJack :: ~MidiInJack() 3234 { 3235 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3236 MidiInJack::closePort(); 3237 3238 if ( data->client ) 3239 jack_client_close( data->client ); 3240 delete data; 3241 } 3242 3243 void MidiInJack :: openPort( unsigned int portNumber, const std::string &portName ) 3244 { 3245 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3246 3247 connect(); 3248 3249 // Creating new port 3250 if ( data->port == NULL ) 3251 data->port = jack_port_register( data->client, portName.c_str(), 3252 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); 3253 3254 if ( data->port == NULL ) { 3255 errorString_ = "MidiInJack::openPort: JACK error creating port"; 3256 if (portName.size() >= (size_t)jack_port_name_size()) 3257 errorString_ += " (port name too long?)"; 3258 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3259 return; 3260 } 3261 3262 // Connecting to the output 3263 std::string name = getPortName( portNumber ); 3264 jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); 3265 3266 connected_ = true; 3267 } 3268 3269 void MidiInJack :: openVirtualPort( const std::string &portName ) 3270 { 3271 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3272 3273 connect(); 3274 if ( data->port == NULL ) 3275 data->port = jack_port_register( data->client, portName.c_str(), 3276 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); 3277 3278 if ( data->port == NULL ) { 3279 errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; 3280 if (portName.size() >= (size_t)jack_port_name_size()) 3281 errorString_ += " (port name too long?)"; 3282 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3283 } 3284 } 3285 3286 unsigned int MidiInJack :: getPortCount() 3287 { 3288 int count = 0; 3289 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3290 connect(); 3291 if ( !data->client ) 3292 return 0; 3293 3294 // List of available ports 3295 const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); 3296 3297 if ( ports == NULL ) return 0; 3298 while ( ports[count] != NULL ) 3299 count++; 3300 3301 free( ports ); 3302 3303 return count; 3304 } 3305 3306 std::string MidiInJack :: getPortName( unsigned int portNumber ) 3307 { 3308 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3309 std::string retStr( "" ); 3310 3311 connect(); 3312 3313 // List of available ports 3314 const char **ports = jack_get_ports( data->client, NULL, 3315 JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); 3316 3317 // Check port validity 3318 if ( ports == NULL ) { 3319 errorString_ = "MidiInJack::getPortName: no ports available!"; 3320 error( RtMidiError::WARNING, errorString_ ); 3321 return retStr; 3322 } 3323 3324 unsigned int i; 3325 for ( i=0; i<portNumber && ports[i]; i++ ) {} 3326 if ( i < portNumber || !ports[portNumber] ) { 3327 std::ostringstream ost; 3328 ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 3329 errorString_ = ost.str(); 3330 error( RtMidiError::WARNING, errorString_ ); 3331 } 3332 else retStr.assign( ports[portNumber] ); 3333 3334 jack_free( ports ); 3335 return retStr; 3336 } 3337 3338 void MidiInJack :: closePort() 3339 { 3340 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3341 3342 if ( data->port == NULL ) return; 3343 jack_port_unregister( data->client, data->port ); 3344 data->port = NULL; 3345 3346 connected_ = false; 3347 } 3348 3349 void MidiInJack:: setClientName( const std::string& ) 3350 { 3351 3352 errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!"; 3353 error( RtMidiError::WARNING, errorString_ ); 3354 3355 } 3356 3357 void MidiInJack :: setPortName( const std::string &portName ) 3358 { 3359 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3360 #ifdef JACK_HAS_PORT_RENAME 3361 jack_port_rename( data->client, data->port, portName.c_str() ); 3362 #else 3363 jack_port_set_name( data->port, portName.c_str() ); 3364 #endif 3365 } 3366 3367 //*********************************************************************// 3368 // API: JACK 3369 // Class Definitions: MidiOutJack 3370 //*********************************************************************// 3371 3372 // Jack process callback 3373 static int jackProcessOut( jack_nframes_t nframes, void *arg ) 3374 { 3375 JackMidiData *data = (JackMidiData *) arg; 3376 jack_midi_data_t *midiData; 3377 int space; 3378 3379 // Is port created? 3380 if ( data->port == NULL ) return 0; 3381 3382 void *buff = jack_port_get_buffer( data->port, nframes ); 3383 jack_midi_clear_buffer( buff ); 3384 3385 while ( jack_ringbuffer_peek( data->buff, (char *) &space, sizeof( space ) ) == sizeof(space) && 3386 jack_ringbuffer_read_space( data->buff ) >= sizeof(space) + space ) { 3387 jack_ringbuffer_read_advance( data->buff, sizeof(space) ); 3388 3389 midiData = jack_midi_event_reserve( buff, 0, space ); 3390 if ( midiData ) 3391 jack_ringbuffer_read( data->buff, (char *) midiData, (size_t) space ); 3392 else 3393 jack_ringbuffer_read_advance( data->buff, (size_t) space ); 3394 } 3395 3396 #ifdef HAVE_SEMAPHORE 3397 if ( !sem_trywait( &data->sem_needpost ) ) 3398 sem_post( &data->sem_cleanup ); 3399 #endif 3400 3401 return 0; 3402 } 3403 3404 MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi() 3405 { 3406 MidiOutJack::initialize( clientName ); 3407 } 3408 3409 void MidiOutJack :: initialize( const std::string& clientName ) 3410 { 3411 JackMidiData *data = new JackMidiData; 3412 apiData_ = (void *) data; 3413 3414 data->port = NULL; 3415 data->client = NULL; 3416 #ifdef HAVE_SEMAPHORE 3417 sem_init( &data->sem_cleanup, 0, 0 ); 3418 sem_init( &data->sem_needpost, 0, 0 ); 3419 #endif 3420 this->clientName = clientName; 3421 3422 connect(); 3423 } 3424 3425 void MidiOutJack :: connect() 3426 { 3427 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3428 if ( data->client ) 3429 return; 3430 3431 // Initialize output ringbuffers 3432 data->buff = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); 3433 data->buffMaxWrite = (int) jack_ringbuffer_write_space( data->buff ); 3434 3435 // Initialize JACK client 3436 if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) { 3437 errorString_ = "MidiOutJack::initialize: JACK server not running?"; 3438 error( RtMidiError::WARNING, errorString_ ); 3439 return; 3440 } 3441 3442 jack_set_process_callback( data->client, jackProcessOut, data ); 3443 jack_activate( data->client ); 3444 } 3445 3446 MidiOutJack :: ~MidiOutJack() 3447 { 3448 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3449 MidiOutJack::closePort(); 3450 3451 // Cleanup 3452 jack_ringbuffer_free( data->buff ); 3453 if ( data->client ) { 3454 jack_client_close( data->client ); 3455 } 3456 3457 #ifdef HAVE_SEMAPHORE 3458 sem_destroy( &data->sem_cleanup ); 3459 sem_destroy( &data->sem_needpost ); 3460 #endif 3461 3462 delete data; 3463 } 3464 3465 void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portName ) 3466 { 3467 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3468 3469 connect(); 3470 3471 // Creating new port 3472 if ( data->port == NULL ) 3473 data->port = jack_port_register( data->client, portName.c_str(), 3474 JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); 3475 3476 if ( data->port == NULL ) { 3477 errorString_ = "MidiOutJack::openPort: JACK error creating port"; 3478 if (portName.size() >= (size_t)jack_port_name_size()) 3479 errorString_ += " (port name too long?)"; 3480 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3481 return; 3482 } 3483 3484 // Connecting to the output 3485 std::string name = getPortName( portNumber ); 3486 jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); 3487 3488 connected_ = true; 3489 } 3490 3491 void MidiOutJack :: openVirtualPort( const std::string &portName ) 3492 { 3493 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3494 3495 connect(); 3496 if ( data->port == NULL ) 3497 data->port = jack_port_register( data->client, portName.c_str(), 3498 JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); 3499 3500 if ( data->port == NULL ) { 3501 errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; 3502 if (portName.size() >= (size_t)jack_port_name_size()) 3503 errorString_ += " (port name too long?)"; 3504 error( RtMidiError::DRIVER_ERROR, errorString_ ); 3505 } 3506 } 3507 3508 unsigned int MidiOutJack :: getPortCount() 3509 { 3510 int count = 0; 3511 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3512 connect(); 3513 if ( !data->client ) 3514 return 0; 3515 3516 // List of available ports 3517 const char **ports = jack_get_ports( data->client, NULL, 3518 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); 3519 3520 if ( ports == NULL ) return 0; 3521 while ( ports[count] != NULL ) 3522 count++; 3523 3524 free( ports ); 3525 3526 return count; 3527 } 3528 3529 std::string MidiOutJack :: getPortName( unsigned int portNumber ) 3530 { 3531 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3532 std::string retStr(""); 3533 3534 connect(); 3535 3536 // List of available ports 3537 const char **ports = jack_get_ports( data->client, NULL, 3538 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); 3539 3540 // Check port validity 3541 if ( ports == NULL ) { 3542 errorString_ = "MidiOutJack::getPortName: no ports available!"; 3543 error( RtMidiError::WARNING, errorString_ ); 3544 return retStr; 3545 } 3546 3547 if ( ports[portNumber] == NULL ) { 3548 std::ostringstream ost; 3549 ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 3550 errorString_ = ost.str(); 3551 error( RtMidiError::WARNING, errorString_ ); 3552 } 3553 else retStr.assign( ports[portNumber] ); 3554 3555 free( ports ); 3556 return retStr; 3557 } 3558 3559 void MidiOutJack :: closePort() 3560 { 3561 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3562 3563 if ( data->port == NULL ) return; 3564 3565 #ifdef HAVE_SEMAPHORE 3566 struct timespec ts; 3567 if ( clock_gettime( CLOCK_REALTIME, &ts ) != -1 ) { 3568 ts.tv_sec += 1; // wait max one second 3569 sem_post( &data->sem_needpost ); 3570 sem_timedwait( &data->sem_cleanup, &ts ); 3571 } 3572 #endif 3573 3574 jack_port_unregister( data->client, data->port ); 3575 data->port = NULL; 3576 3577 connected_ = false; 3578 } 3579 3580 void MidiOutJack:: setClientName( const std::string& ) 3581 { 3582 3583 errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!"; 3584 error( RtMidiError::WARNING, errorString_ ); 3585 3586 } 3587 3588 void MidiOutJack :: setPortName( const std::string &portName ) 3589 { 3590 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3591 #ifdef JACK_HAS_PORT_RENAME 3592 jack_port_rename( data->client, data->port, portName.c_str() ); 3593 #else 3594 jack_port_set_name( data->port, portName.c_str() ); 3595 #endif 3596 } 3597 3598 void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) 3599 { 3600 int nBytes = static_cast<int>(size); 3601 JackMidiData *data = static_cast<JackMidiData *> (apiData_); 3602 3603 if ( size + sizeof(nBytes) > (size_t) data->buffMaxWrite ) 3604 return; 3605 3606 while ( jack_ringbuffer_write_space(data->buff) < sizeof(nBytes) + size ) 3607 sched_yield(); 3608 3609 // Write full message to buffer 3610 jack_ringbuffer_write( data->buff, ( char * ) &nBytes, sizeof( nBytes ) ); 3611 jack_ringbuffer_write( data->buff, ( const char * ) message, nBytes ); 3612 } 3613 3614 #endif // __UNIX_JACK__ 3615 3616 //*********************************************************************// 3617 // API: Web MIDI 3618 // 3619 // Written primarily by Atsushi Eno, February 2020. 3620 // 3621 // *********************************************************************// 3622 3623 #if defined(__WEB_MIDI_API__) 3624 3625 #include <emscripten.h> 3626 3627 //*********************************************************************// 3628 // API: WEB MIDI 3629 // Class Definitions: WebMidiAccessShim 3630 //*********************************************************************// 3631 3632 class WebMidiAccessShim 3633 { 3634 public: 3635 WebMidiAccessShim(); 3636 ~WebMidiAccessShim(); 3637 std::string getPortName( unsigned int portNumber, bool isInput ); 3638 }; 3639 3640 std::unique_ptr<WebMidiAccessShim> shim{nullptr}; 3641 3642 void ensureShim() 3643 { 3644 if ( shim.get() != nullptr ) 3645 return; 3646 shim.reset( new WebMidiAccessShim() ); 3647 } 3648 3649 bool checkWebMidiAvailability() 3650 { 3651 ensureShim(); 3652 3653 return MAIN_THREAD_EM_ASM_INT( { 3654 if ( typeof window._rtmidi_internals_waiting === "undefined" ) { 3655 console.log ( "Attempted to use Web MIDI API without trying to open it." ); 3656 return false; 3657 } 3658 if ( window._rtmidi_internals_waiting ) { 3659 console.log ( "Attempted to use Web MIDI API while it is being queried." ); 3660 return false; 3661 } 3662 if ( _rtmidi_internals_midi_access == null ) { 3663 console.log ( "Attempted to use Web MIDI API while it already turned out to be unavailable." ); 3664 return false; 3665 } 3666 return true; 3667 } ); 3668 } 3669 3670 WebMidiAccessShim::WebMidiAccessShim() 3671 { 3672 MAIN_THREAD_ASYNC_EM_ASM( { 3673 if( typeof window._rtmidi_internals_midi_access !== "undefined" ) 3674 return; 3675 if( typeof window._rtmidi_internals_waiting !== "undefined" ) { 3676 console.log( "MIDI Access was requested while another request is in progress." ); 3677 return; 3678 } 3679 3680 // define functions 3681 window._rtmidi_internals_get_port_by_number = function( portNumber, isInput ) { 3682 var midi = window._rtmidi_internals_midi_access; 3683 var devices = isInput ? midi.inputs : midi.outputs; 3684 var i = 0; 3685 for (var device of devices.values()) { 3686 if ( i == portNumber ) 3687 return device; 3688 i++; 3689 } 3690 console.log( "MIDI " + (isInput ? "input" : "output") + " device of portNumber " + portNumber + " is not found."); 3691 return null; 3692 }; 3693 3694 window._rtmidi_internals_waiting = true; 3695 window.navigator.requestMIDIAccess( {"sysex": true} ).then( (midiAccess) => { 3696 window._rtmidi_internals_midi_access = midiAccess; 3697 window._rtmidi_internals_latest_message_timestamp = 0.0; 3698 window._rtmidi_internals_waiting = false; 3699 if( midiAccess == null ) { 3700 console.log ( "Could not get access to MIDI API" ); 3701 } 3702 } ); 3703 } ); 3704 } 3705 3706 WebMidiAccessShim::~WebMidiAccessShim() 3707 { 3708 } 3709 3710 std::string WebMidiAccessShim::getPortName( unsigned int portNumber, bool isInput ) 3711 { 3712 if( !checkWebMidiAvailability() ) 3713 return ""; 3714 char *ret = (char*) MAIN_THREAD_EM_ASM_INT( { 3715 var port = window._rtmidi_internals_get_port_by_number($0, $1); 3716 if( port == null) 3717 return null; 3718 var length = lengthBytesUTF8(port.name) + 1; 3719 var ret = _malloc(length); 3720 stringToUTF8(port.name, ret, length); 3721 return ret; 3722 }, portNumber, isInput); 3723 if (ret == nullptr) 3724 return ""; 3725 std::string s = ret; 3726 free(ret); 3727 return s; 3728 } 3729 3730 //*********************************************************************// 3731 // API: WEB MIDI 3732 // Class Definitions: MidiInWeb 3733 //*********************************************************************// 3734 3735 MidiInWeb::MidiInWeb( const std::string &clientName, unsigned int queueSizeLimit ) 3736 : MidiInApi( queueSizeLimit ) 3737 { 3738 initialize( clientName ); 3739 } 3740 3741 MidiInWeb::~MidiInWeb( void ) 3742 { 3743 closePort(); 3744 } 3745 3746 extern "C" void EMSCRIPTEN_KEEPALIVE rtmidi_onMidiMessageProc( MidiInApi::RtMidiInData* data, uint8_t* inputBytes, int32_t length, double domHighResTimeStamp ) 3747 { 3748 auto &message = data->message; 3749 message.bytes.resize(message.bytes.size() + length); 3750 memcpy(message.bytes.data(), inputBytes, length); 3751 // FIXME: handle timestamp 3752 if ( data->usingCallback ) { 3753 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 3754 callback( message.timeStamp, &message.bytes, data->userData ); 3755 } 3756 } 3757 3758 void MidiInWeb::openPort( unsigned int portNumber, const std::string &portName ) 3759 { 3760 if( !checkWebMidiAvailability() ) 3761 return; 3762 if (open_port_number >= 0) 3763 return; 3764 3765 MAIN_THREAD_EM_ASM( { 3766 // In Web MIDI API world, there is no step to open a port, but we have to register the input callback instead. 3767 var input = window._rtmidi_internals_get_port_by_number($0, true); 3768 input.onmidimessage = function(e) { 3769 // In RtMidi world, timestamps are delta time from previous message, while in Web MIDI world 3770 // timestamps are relative to window creation time (i.e. kind of absolute time with window "epoch" time). 3771 var rtmidiTimestamp = window._rtmidi_internals_latest_message_timestamp == 0.0 ? 0.0 : e.timeStamp - window._rtmidi_internals_latest_message_timestamp; 3772 window._rtmidi_internals_latest_message_timestamp = e.timeStamp; 3773 Module.ccall( 'rtmidi_onMidiMessageProc', 'void', ['number', 'array', 'number', 'number'], [$1, e.data, e.data.length, rtmidiTimestamp] ); 3774 }; 3775 }, portNumber, &inputData_ ); 3776 open_port_number = portNumber; 3777 } 3778 3779 void MidiInWeb::openVirtualPort( const std::string &portName ) 3780 { 3781 3782 errorString_ = "MidiInWeb::openVirtualPort: this function is not implemented for the Web MIDI API!"; 3783 error( RtMidiError::WARNING, errorString_ ); 3784 3785 } 3786 3787 void MidiInWeb::closePort( void ) 3788 { 3789 if( open_port_number < 0 ) 3790 return; 3791 3792 MAIN_THREAD_EM_ASM( { 3793 var input = _rtmidi_internals_get_port_by_number($0, true); 3794 if( input == null ) { 3795 console.log( "Port #" + $0 + " could not be found."); 3796 return; 3797 } 3798 // unregister event handler 3799 input.onmidimessage = null; 3800 }, open_port_number ); 3801 open_port_number = -1; 3802 } 3803 3804 void MidiInWeb::setClientName( const std::string &clientName ) 3805 { 3806 client_name = clientName; 3807 } 3808 3809 void MidiInWeb::setPortName( const std::string &portName ) 3810 { 3811 3812 errorString_ = "MidiInWeb::setPortName: this function is not implemented for the Web MIDI API!"; 3813 error( RtMidiError::WARNING, errorString_ ); 3814 3815 } 3816 3817 unsigned int MidiInWeb::getPortCount( void ) 3818 { 3819 if( !checkWebMidiAvailability() ) 3820 return 0; 3821 return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.inputs.size; } ); 3822 } 3823 3824 std::string MidiInWeb::getPortName( unsigned int portNumber ) 3825 { 3826 if( !checkWebMidiAvailability() ) 3827 return ""; 3828 return shim->getPortName( portNumber, true ); 3829 } 3830 3831 void MidiInWeb::initialize( const std::string& clientName ) 3832 { 3833 ensureShim(); 3834 setClientName( clientName ); 3835 } 3836 3837 //*********************************************************************// 3838 // API: WEB MIDI 3839 // Class Definitions: MidiOutWeb 3840 //*********************************************************************// 3841 3842 MidiOutWeb::MidiOutWeb( const std::string &clientName ) 3843 { 3844 initialize( clientName ); 3845 } 3846 3847 MidiOutWeb::~MidiOutWeb( void ) 3848 { 3849 closePort(); 3850 } 3851 3852 void MidiOutWeb::openPort( unsigned int portNumber, const std::string &portName ) 3853 { 3854 if( !checkWebMidiAvailability() ) 3855 return; 3856 if (open_port_number >= 0) 3857 return; 3858 // In Web MIDI API world, there is no step to open a port. 3859 3860 open_port_number = portNumber; 3861 } 3862 3863 void MidiOutWeb::openVirtualPort( const std::string &portName ) 3864 { 3865 3866 errorString_ = "MidiOutWeb::openVirtualPort: this function is not implemented for the Web MIDI API!"; 3867 error( RtMidiError::WARNING, errorString_ ); 3868 3869 } 3870 3871 void MidiOutWeb::closePort( void ) 3872 { 3873 // there is really nothing to do for output at JS side. 3874 open_port_number = -1; 3875 } 3876 3877 void MidiOutWeb::setClientName( const std::string &clientName ) 3878 { 3879 client_name = clientName; 3880 } 3881 3882 void MidiOutWeb::setPortName( const std::string &portName ) 3883 { 3884 3885 errorString_ = "MidiOutWeb::setPortName: this function is not implemented for the Web MIDI API!"; 3886 error( RtMidiError::WARNING, errorString_ ); 3887 3888 } 3889 3890 unsigned int MidiOutWeb::getPortCount( void ) 3891 { 3892 if( !checkWebMidiAvailability() ) 3893 return 0; 3894 return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.outputs.size; } ); 3895 } 3896 3897 std::string MidiOutWeb::getPortName( unsigned int portNumber ) 3898 { 3899 if( !checkWebMidiAvailability() ) 3900 return ""; 3901 return shim->getPortName( portNumber, false ); 3902 } 3903 3904 void MidiOutWeb::sendMessage( const unsigned char *message, size_t size ) 3905 { 3906 if( open_port_number < 0 ) 3907 return; 3908 3909 MAIN_THREAD_EM_ASM( { 3910 var output = _rtmidi_internals_get_port_by_number( $0, false ); 3911 if( output == null ) { 3912 console.log( "Port #" + $0 + " could not be found."); 3913 return; 3914 } 3915 var buf = new ArrayBuffer ($2); 3916 var msg = new Uint8Array( buf ); 3917 msg.set( new Uint8Array( Module.HEAPU8.buffer.slice( $1, $1 + $2 ) ) ); 3918 output.send( msg ); 3919 }, open_port_number, message, size ); 3920 } 3921 3922 void MidiOutWeb::initialize( const std::string& clientName ) 3923 { 3924 if ( shim.get() != nullptr ) 3925 return; 3926 shim.reset( new WebMidiAccessShim() ); 3927 setClientName( clientName ); 3928 } 3929 3930 #endif // __WEB_MIDI_API__