├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── RtMidi.cpp ├── RtMidi.h ├── build-osx.sh ├── index.cpp ├── midi-to-keyboard.sln ├── midi-to-keyboard.vcxproj └── midi-to-keyboard.vcxproj.filters /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | end_of_line = lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Unix build dir 35 | /build 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Luca Harris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PICO-8 MIDI to Keyboard 2 | 3 | A program which converts MIDI inputs to keyboard inputs, allowing MIDI devices to be used with PICO-8. 4 | 5 | [Download from the releases tab](https://github.com/lucatronica/pico8-midi-to-keyboard/releases/latest). 6 | 7 | ## Running 8 | 9 | On Windows, run: 10 | 11 | ``` 12 | .\midi-to-keyboard-windows.exe 13 | ``` 14 | 15 | #### Specifying which MIDI controller to use 16 | 17 | To specify which MIDI controller to use, figure out its name and then enter its as the first argument. 18 | 19 | Partial matches also work. The search is case sensitive. For example, if your controller is called "microKey-25", you can use it with the command: 20 | 21 | ``` 22 | .\midi-to-keyboard-windows.exe microKEY 23 | ``` 24 | 25 | ## MIDI to key conversion 26 | 27 | The following mapping is used: 28 | 29 | MIDI | ASCII 30 | ---- | ----- 31 | 0-31 | 32-63 32 | 32-62 | 96-125 33 | 34 | An inverse mapping can be created like this: 35 | 36 | ```lua 37 | key_to_midi={} 38 | for i=32,63 do 39 | key_to_midi[chr(i)]=i-32 40 | end 41 | for i=96,125 do 42 | key_to_midi[chr(i)]=32+(i-96) 43 | end 44 | ``` 45 | 46 | ## Building 47 | 48 | ### Windows 49 | 50 | Open the `midi-to-keyboard` project in Visual Studio and build. 51 | 52 | ### Mac 53 | 54 | Run `./build-osx.sh`. 55 | -------------------------------------------------------------------------------- /RtMidi.cpp: -------------------------------------------------------------------------------- 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-2019 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 42 | 43 | #if defined(__MACOSX_CORE__) 44 | #if TARGET_OS_IPHONE 45 | #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime 46 | #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos 47 | #endif 48 | #endif 49 | 50 | // Default for Windows is to add an identifier to the port names; this 51 | // flag can be defined (e.g. in your project file) to disable this behaviour. 52 | //#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES 53 | 54 | // **************************************************************** // 55 | // 56 | // MidiInApi and MidiOutApi subclass prototypes. 57 | // 58 | // **************************************************************** // 59 | 60 | #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) 61 | #define __RTMIDI_DUMMY__ 62 | #endif 63 | 64 | #if defined(__MACOSX_CORE__) 65 | 66 | class MidiInCore: public MidiInApi 67 | { 68 | public: 69 | MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ); 70 | ~MidiInCore( void ); 71 | RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; 72 | void openPort( unsigned int portNumber, const std::string &portName ); 73 | void openVirtualPort( const std::string &portName ); 74 | void closePort( void ); 75 | void setClientName( const std::string &clientName ); 76 | void setPortName( const std::string &portName ); 77 | unsigned int getPortCount( void ); 78 | std::string getPortName( unsigned int portNumber ); 79 | 80 | protected: 81 | void initialize( const std::string& clientName ); 82 | }; 83 | 84 | class MidiOutCore: public MidiOutApi 85 | { 86 | public: 87 | MidiOutCore( const std::string &clientName ); 88 | ~MidiOutCore( void ); 89 | RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; 90 | void openPort( unsigned int portNumber, const std::string &portName ); 91 | void openVirtualPort( const std::string &portName ); 92 | void closePort( void ); 93 | void setClientName( const std::string &clientName ); 94 | void setPortName( const std::string &portName ); 95 | unsigned int getPortCount( void ); 96 | std::string getPortName( unsigned int portNumber ); 97 | void sendMessage( const unsigned char *message, size_t size ); 98 | 99 | protected: 100 | void initialize( const std::string& clientName ); 101 | }; 102 | 103 | #endif 104 | 105 | #if defined(__UNIX_JACK__) 106 | 107 | class MidiInJack: public MidiInApi 108 | { 109 | public: 110 | MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ); 111 | ~MidiInJack( void ); 112 | RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; 113 | void openPort( unsigned int portNumber, const std::string &portName ); 114 | void openVirtualPort( const std::string &portName ); 115 | void closePort( void ); 116 | void setClientName( const std::string &clientName ); 117 | void setPortName( const std::string &portName); 118 | unsigned int getPortCount( void ); 119 | std::string getPortName( unsigned int portNumber ); 120 | 121 | protected: 122 | std::string clientName; 123 | 124 | void connect( void ); 125 | void initialize( const std::string& clientName ); 126 | }; 127 | 128 | class MidiOutJack: public MidiOutApi 129 | { 130 | public: 131 | MidiOutJack( const std::string &clientName ); 132 | ~MidiOutJack( void ); 133 | RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; 134 | void openPort( unsigned int portNumber, const std::string &portName ); 135 | void openVirtualPort( const std::string &portName ); 136 | void closePort( void ); 137 | void setClientName( const std::string &clientName ); 138 | void setPortName( const std::string &portName); 139 | unsigned int getPortCount( void ); 140 | std::string getPortName( unsigned int portNumber ); 141 | void sendMessage( const unsigned char *message, size_t size ); 142 | 143 | protected: 144 | std::string clientName; 145 | 146 | void connect( void ); 147 | void initialize( const std::string& clientName ); 148 | }; 149 | 150 | #endif 151 | 152 | #if defined(__LINUX_ALSA__) 153 | 154 | class MidiInAlsa: public MidiInApi 155 | { 156 | public: 157 | MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ); 158 | ~MidiInAlsa( void ); 159 | RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; 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 | 168 | protected: 169 | void initialize( const std::string& clientName ); 170 | }; 171 | 172 | class MidiOutAlsa: public MidiOutApi 173 | { 174 | public: 175 | MidiOutAlsa( const std::string &clientName ); 176 | ~MidiOutAlsa( void ); 177 | RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; 178 | void openPort( unsigned int portNumber, const std::string &portName ); 179 | void openVirtualPort( const std::string &portName ); 180 | void closePort( void ); 181 | void setClientName( const std::string &clientName ); 182 | void setPortName( const std::string &portName ); 183 | unsigned int getPortCount( void ); 184 | std::string getPortName( unsigned int portNumber ); 185 | void sendMessage( const unsigned char *message, size_t size ); 186 | 187 | protected: 188 | void initialize( const std::string& clientName ); 189 | }; 190 | 191 | #endif 192 | 193 | #if defined(__WINDOWS_MM__) 194 | 195 | class MidiInWinMM: public MidiInApi 196 | { 197 | public: 198 | MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ); 199 | ~MidiInWinMM( void ); 200 | RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; 201 | void openPort( unsigned int portNumber, const std::string &portName ); 202 | void openVirtualPort( const std::string &portName ); 203 | void closePort( void ); 204 | void setClientName( const std::string &clientName ); 205 | void setPortName( const std::string &portName ); 206 | unsigned int getPortCount( void ); 207 | std::string getPortName( unsigned int portNumber ); 208 | 209 | protected: 210 | void initialize( const std::string& clientName ); 211 | }; 212 | 213 | class MidiOutWinMM: public MidiOutApi 214 | { 215 | public: 216 | MidiOutWinMM( const std::string &clientName ); 217 | ~MidiOutWinMM( void ); 218 | RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; 219 | void openPort( unsigned int portNumber, const std::string &portName ); 220 | void openVirtualPort( const std::string &portName ); 221 | void closePort( void ); 222 | void setClientName( const std::string &clientName ); 223 | void setPortName( const std::string &portName ); 224 | unsigned int getPortCount( void ); 225 | std::string getPortName( unsigned int portNumber ); 226 | void sendMessage( const unsigned char *message, size_t size ); 227 | 228 | protected: 229 | void initialize( const std::string& clientName ); 230 | }; 231 | 232 | #endif 233 | 234 | #if defined(__RTMIDI_DUMMY__) 235 | 236 | class MidiInDummy: public MidiInApi 237 | { 238 | public: 239 | MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } 240 | RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } 241 | void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} 242 | void openVirtualPort( const std::string &/*portName*/ ) {} 243 | void closePort( void ) {} 244 | void setClientName( const std::string &/*clientName*/ ) {}; 245 | void setPortName( const std::string &/*portName*/ ) {}; 246 | unsigned int getPortCount( void ) { return 0; } 247 | std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } 248 | 249 | protected: 250 | void initialize( const std::string& /*clientName*/ ) {} 251 | }; 252 | 253 | class MidiOutDummy: public MidiOutApi 254 | { 255 | public: 256 | MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } 257 | RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } 258 | void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} 259 | void openVirtualPort( const std::string &/*portName*/ ) {} 260 | void closePort( void ) {} 261 | void setClientName( const std::string &/*clientName*/ ) {}; 262 | void setPortName( const std::string &/*portName*/ ) {}; 263 | unsigned int getPortCount( void ) { return 0; } 264 | std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } 265 | void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {} 266 | 267 | protected: 268 | void initialize( const std::string& /*clientName*/ ) {} 269 | }; 270 | 271 | #endif 272 | 273 | //*********************************************************************// 274 | // RtMidi Definitions 275 | //*********************************************************************// 276 | 277 | RtMidi :: RtMidi() 278 | : rtapi_(0) 279 | { 280 | } 281 | 282 | RtMidi :: ~RtMidi() 283 | { 284 | delete rtapi_; 285 | rtapi_ = 0; 286 | } 287 | 288 | std::string RtMidi :: getVersion( void ) throw() 289 | { 290 | return std::string( RTMIDI_VERSION ); 291 | } 292 | 293 | // Define API names and display names. 294 | // Must be in same order as API enum. 295 | extern "C" { 296 | const char* rtmidi_api_names[][2] = { 297 | { "unspecified" , "Unknown" }, 298 | { "core" , "CoreMidi" }, 299 | { "alsa" , "ALSA" }, 300 | { "jack" , "Jack" }, 301 | { "winmm" , "Windows MultiMedia" }, 302 | { "dummy" , "Dummy" }, 303 | }; 304 | const unsigned int rtmidi_num_api_names = 305 | sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]); 306 | 307 | // The order here will control the order of RtMidi's API search in 308 | // the constructor. 309 | extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { 310 | #if defined(__MACOSX_CORE__) 311 | RtMidi::MACOSX_CORE, 312 | #endif 313 | #if defined(__LINUX_ALSA__) 314 | RtMidi::LINUX_ALSA, 315 | #endif 316 | #if defined(__UNIX_JACK__) 317 | RtMidi::UNIX_JACK, 318 | #endif 319 | #if defined(__WINDOWS_MM__) 320 | RtMidi::WINDOWS_MM, 321 | #endif 322 | #if defined(__RTMIDI_DUMMY__) 323 | RtMidi::RTMIDI_DUMMY, 324 | #endif 325 | RtMidi::UNSPECIFIED, 326 | }; 327 | extern "C" const unsigned int rtmidi_num_compiled_apis = 328 | sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1; 329 | } 330 | 331 | // This is a compile-time check that rtmidi_num_api_names == RtMidi::NUM_APIS. 332 | // If the build breaks here, check that they match. 333 | template class StaticAssert { private: StaticAssert() {} }; 334 | template<> class StaticAssert{ public: StaticAssert() {} }; 335 | class StaticAssertions { StaticAssertions() { 336 | StaticAssert(); 337 | }}; 338 | 339 | void RtMidi :: getCompiledApi( std::vector &apis ) throw() 340 | { 341 | apis = std::vector(rtmidi_compiled_apis, 342 | rtmidi_compiled_apis + rtmidi_num_compiled_apis); 343 | } 344 | 345 | std::string RtMidi :: getApiName( RtMidi::Api api ) 346 | { 347 | if (api < 0 || api >= RtMidi::NUM_APIS) 348 | return ""; 349 | return rtmidi_api_names[api][0]; 350 | } 351 | 352 | std::string RtMidi :: getApiDisplayName( RtMidi::Api api ) 353 | { 354 | if (api < 0 || api >= RtMidi::NUM_APIS) 355 | return "Unknown"; 356 | return rtmidi_api_names[api][1]; 357 | } 358 | 359 | RtMidi::Api RtMidi :: getCompiledApiByName( const std::string &name ) 360 | { 361 | unsigned int i=0; 362 | for (i = 0; i < rtmidi_num_compiled_apis; ++i) 363 | if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0]) 364 | return rtmidi_compiled_apis[i]; 365 | return RtMidi::UNSPECIFIED; 366 | } 367 | 368 | void RtMidi :: setClientName( const std::string &clientName ) 369 | { 370 | rtapi_->setClientName( clientName ); 371 | } 372 | 373 | void RtMidi :: setPortName( const std::string &portName ) 374 | { 375 | rtapi_->setPortName( portName ); 376 | } 377 | 378 | 379 | //*********************************************************************// 380 | // RtMidiIn Definitions 381 | //*********************************************************************// 382 | 383 | void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) 384 | { 385 | delete rtapi_; 386 | rtapi_ = 0; 387 | 388 | #if defined(__UNIX_JACK__) 389 | if ( api == UNIX_JACK ) 390 | rtapi_ = new MidiInJack( clientName, queueSizeLimit ); 391 | #endif 392 | #if defined(__LINUX_ALSA__) 393 | if ( api == LINUX_ALSA ) 394 | rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); 395 | #endif 396 | #if defined(__WINDOWS_MM__) 397 | if ( api == WINDOWS_MM ) 398 | rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); 399 | #endif 400 | #if defined(__MACOSX_CORE__) 401 | if ( api == MACOSX_CORE ) 402 | rtapi_ = new MidiInCore( clientName, queueSizeLimit ); 403 | #endif 404 | #if defined(__RTMIDI_DUMMY__) 405 | if ( api == RTMIDI_DUMMY ) 406 | rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); 407 | #endif 408 | } 409 | 410 | RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) 411 | : RtMidi() 412 | { 413 | if ( api != UNSPECIFIED ) { 414 | // Attempt to open the specified API. 415 | openMidiApi( api, clientName, queueSizeLimit ); 416 | if ( rtapi_ ) return; 417 | 418 | // No compiled support for specified API value. Issue a warning 419 | // and continue as if no API was specified. 420 | std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; 421 | } 422 | 423 | // Iterate through the compiled APIs and return as soon as we find 424 | // one with at least one port or we reach the end of the list. 425 | std::vector< RtMidi::Api > apis; 426 | getCompiledApi( apis ); 427 | for ( unsigned int i=0; igetPortCount() ) break; 430 | } 431 | 432 | if ( rtapi_ ) return; 433 | 434 | // It should not be possible to get here because the preprocessor 435 | // definition __RTMIDI_DUMMY__ is automatically defined if no 436 | // API-specific definitions are passed to the compiler. But just in 437 | // case something weird happens, we'll throw an error. 438 | std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; 439 | throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); 440 | } 441 | 442 | RtMidiIn :: ~RtMidiIn() throw() 443 | { 444 | } 445 | 446 | 447 | //*********************************************************************// 448 | // RtMidiOut Definitions 449 | //*********************************************************************// 450 | 451 | void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) 452 | { 453 | delete rtapi_; 454 | rtapi_ = 0; 455 | 456 | #if defined(__UNIX_JACK__) 457 | if ( api == UNIX_JACK ) 458 | rtapi_ = new MidiOutJack( clientName ); 459 | #endif 460 | #if defined(__LINUX_ALSA__) 461 | if ( api == LINUX_ALSA ) 462 | rtapi_ = new MidiOutAlsa( clientName ); 463 | #endif 464 | #if defined(__WINDOWS_MM__) 465 | if ( api == WINDOWS_MM ) 466 | rtapi_ = new MidiOutWinMM( clientName ); 467 | #endif 468 | #if defined(__MACOSX_CORE__) 469 | if ( api == MACOSX_CORE ) 470 | rtapi_ = new MidiOutCore( clientName ); 471 | #endif 472 | #if defined(__RTMIDI_DUMMY__) 473 | if ( api == RTMIDI_DUMMY ) 474 | rtapi_ = new MidiOutDummy( clientName ); 475 | #endif 476 | } 477 | 478 | RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName) 479 | { 480 | if ( api != UNSPECIFIED ) { 481 | // Attempt to open the specified API. 482 | openMidiApi( api, clientName ); 483 | if ( rtapi_ ) return; 484 | 485 | // No compiled support for specified API value. Issue a warning 486 | // and continue as if no API was specified. 487 | std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; 488 | } 489 | 490 | // Iterate through the compiled APIs and return as soon as we find 491 | // one with at least one port or we reach the end of the list. 492 | std::vector< RtMidi::Api > apis; 493 | getCompiledApi( apis ); 494 | for ( unsigned int i=0; igetPortCount() ) break; 497 | } 498 | 499 | if ( rtapi_ ) return; 500 | 501 | // It should not be possible to get here because the preprocessor 502 | // definition __RTMIDI_DUMMY__ is automatically defined if no 503 | // API-specific definitions are passed to the compiler. But just in 504 | // case something weird happens, we'll thrown an error. 505 | std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; 506 | throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); 507 | } 508 | 509 | RtMidiOut :: ~RtMidiOut() throw() 510 | { 511 | } 512 | 513 | //*********************************************************************// 514 | // Common MidiApi Definitions 515 | //*********************************************************************// 516 | 517 | MidiApi :: MidiApi( void ) 518 | : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0) 519 | { 520 | } 521 | 522 | MidiApi :: ~MidiApi( void ) 523 | { 524 | } 525 | 526 | void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 ) 527 | { 528 | errorCallback_ = errorCallback; 529 | errorCallbackUserData_ = userData; 530 | } 531 | 532 | void MidiApi :: error( RtMidiError::Type type, std::string errorString ) 533 | { 534 | if ( errorCallback_ ) { 535 | 536 | if ( firstErrorOccurred_ ) 537 | return; 538 | 539 | firstErrorOccurred_ = true; 540 | const std::string errorMessage = errorString; 541 | 542 | errorCallback_( type, errorMessage, errorCallbackUserData_ ); 543 | firstErrorOccurred_ = false; 544 | return; 545 | } 546 | 547 | if ( type == RtMidiError::WARNING ) { 548 | std::cerr << '\n' << errorString << "\n\n"; 549 | } 550 | else if ( type == RtMidiError::DEBUG_WARNING ) { 551 | #if defined(__RTMIDI_DEBUG__) 552 | std::cerr << '\n' << errorString << "\n\n"; 553 | #endif 554 | } 555 | else { 556 | std::cerr << '\n' << errorString << "\n\n"; 557 | throw RtMidiError( errorString, type ); 558 | } 559 | } 560 | 561 | //*********************************************************************// 562 | // Common MidiInApi Definitions 563 | //*********************************************************************// 564 | 565 | MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) 566 | : MidiApi() 567 | { 568 | // Allocate the MIDI queue. 569 | inputData_.queue.ringSize = queueSizeLimit; 570 | if ( inputData_.queue.ringSize > 0 ) 571 | inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; 572 | } 573 | 574 | MidiInApi :: ~MidiInApi( void ) 575 | { 576 | // Delete the MIDI queue. 577 | if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; 578 | } 579 | 580 | void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) 581 | { 582 | if ( inputData_.usingCallback ) { 583 | errorString_ = "MidiInApi::setCallback: a callback function is already set!"; 584 | error( RtMidiError::WARNING, errorString_ ); 585 | return; 586 | } 587 | 588 | if ( !callback ) { 589 | errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; 590 | error( RtMidiError::WARNING, errorString_ ); 591 | return; 592 | } 593 | 594 | inputData_.userCallback = callback; 595 | inputData_.userData = userData; 596 | inputData_.usingCallback = true; 597 | } 598 | 599 | void MidiInApi :: cancelCallback() 600 | { 601 | if ( !inputData_.usingCallback ) { 602 | errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; 603 | error( RtMidiError::WARNING, errorString_ ); 604 | return; 605 | } 606 | 607 | inputData_.userCallback = 0; 608 | inputData_.userData = 0; 609 | inputData_.usingCallback = false; 610 | } 611 | 612 | void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) 613 | { 614 | inputData_.ignoreFlags = 0; 615 | if ( midiSysex ) inputData_.ignoreFlags = 0x01; 616 | if ( midiTime ) inputData_.ignoreFlags |= 0x02; 617 | if ( midiSense ) inputData_.ignoreFlags |= 0x04; 618 | } 619 | 620 | double MidiInApi :: getMessage( std::vector *message ) 621 | { 622 | message->clear(); 623 | 624 | if ( inputData_.usingCallback ) { 625 | errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; 626 | error( RtMidiError::WARNING, errorString_ ); 627 | return 0.0; 628 | } 629 | 630 | double timeStamp; 631 | if ( !inputData_.queue.pop( message, &timeStamp ) ) 632 | return 0.0; 633 | 634 | return timeStamp; 635 | } 636 | 637 | unsigned int MidiInApi::MidiQueue::size( unsigned int *__back, 638 | unsigned int *__front ) 639 | { 640 | // Access back/front members exactly once and make stack copies for 641 | // size calculation 642 | unsigned int _back = back, _front = front, _size; 643 | if ( _back >= _front ) 644 | _size = _back - _front; 645 | else 646 | _size = ringSize - _front + _back; 647 | 648 | // Return copies of back/front so no new and unsynchronized accesses 649 | // to member variables are needed. 650 | if ( __back ) *__back = _back; 651 | if ( __front ) *__front = _front; 652 | return _size; 653 | } 654 | 655 | // As long as we haven't reached our queue size limit, push the message. 656 | bool MidiInApi::MidiQueue::push( const MidiInApi::MidiMessage& msg ) 657 | { 658 | // Local stack copies of front/back 659 | unsigned int _back, _front, _size; 660 | 661 | // Get back/front indexes exactly once and calculate current size 662 | _size = size( &_back, &_front ); 663 | 664 | if ( _size < ringSize-1 ) 665 | { 666 | ring[_back] = msg; 667 | back = (back+1)%ringSize; 668 | return true; 669 | } 670 | 671 | return false; 672 | } 673 | 674 | bool MidiInApi::MidiQueue::pop( std::vector *msg, double* timeStamp ) 675 | { 676 | // Local stack copies of front/back 677 | unsigned int _back, _front, _size; 678 | 679 | // Get back/front indexes exactly once and calculate current size 680 | _size = size( &_back, &_front ); 681 | 682 | if ( _size == 0 ) 683 | return false; 684 | 685 | // Copy queued message to the vector pointer argument and then "pop" it. 686 | msg->assign( ring[_front].bytes.begin(), ring[_front].bytes.end() ); 687 | *timeStamp = ring[_front].timeStamp; 688 | 689 | // Update front 690 | front = (front+1)%ringSize; 691 | return true; 692 | } 693 | 694 | //*********************************************************************// 695 | // Common MidiOutApi Definitions 696 | //*********************************************************************// 697 | 698 | MidiOutApi :: MidiOutApi( void ) 699 | : MidiApi() 700 | { 701 | } 702 | 703 | MidiOutApi :: ~MidiOutApi( void ) 704 | { 705 | } 706 | 707 | // *************************************************** // 708 | // 709 | // OS/API-specific methods. 710 | // 711 | // *************************************************** // 712 | 713 | #if defined(__MACOSX_CORE__) 714 | 715 | // The CoreMIDI API is based on the use of a callback function for 716 | // MIDI input. We convert the system specific time stamps to delta 717 | // time values. 718 | 719 | // OS-X CoreMIDI header files. 720 | #include 721 | #include 722 | #include 723 | 724 | // A structure to hold variables related to the CoreMIDI API 725 | // implementation. 726 | struct CoreMidiData { 727 | MIDIClientRef client; 728 | MIDIPortRef port; 729 | MIDIEndpointRef endpoint; 730 | MIDIEndpointRef destinationId; 731 | unsigned long long lastTime; 732 | MIDISysexSendRequest sysexreq; 733 | }; 734 | 735 | //*********************************************************************// 736 | // API: OS-X 737 | // Class Definitions: MidiInCore 738 | //*********************************************************************// 739 | 740 | static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) 741 | { 742 | MidiInApi::RtMidiInData *data = static_cast (procRef); 743 | CoreMidiData *apiData = static_cast (data->apiData); 744 | 745 | unsigned char status; 746 | unsigned short nBytes, iByte, size; 747 | unsigned long long time; 748 | 749 | bool& continueSysex = data->continueSysex; 750 | MidiInApi::MidiMessage& message = data->message; 751 | 752 | const MIDIPacket *packet = &list->packet[0]; 753 | for ( unsigned int i=0; inumPackets; ++i ) { 754 | 755 | // My interpretation of the CoreMIDI documentation: all message 756 | // types, except sysex, are complete within a packet and there may 757 | // be several of them in a single packet. Sysex messages can be 758 | // broken across multiple packets and PacketLists but are bundled 759 | // alone within each packet (these packets do not contain other 760 | // message types). If sysex messages are split across multiple 761 | // MIDIPacketLists, they must be handled by multiple calls to this 762 | // function. 763 | 764 | nBytes = packet->length; 765 | if ( nBytes == 0 ) { 766 | packet = MIDIPacketNext( packet ); 767 | continue; 768 | } 769 | 770 | // Calculate time stamp. 771 | if ( data->firstMessage ) { 772 | message.timeStamp = 0.0; 773 | data->firstMessage = false; 774 | } 775 | else { 776 | time = packet->timeStamp; 777 | if ( time == 0 ) { // this happens when receiving asynchronous sysex messages 778 | time = AudioGetCurrentHostTime(); 779 | } 780 | time -= apiData->lastTime; 781 | time = AudioConvertHostTimeToNanos( time ); 782 | if ( !continueSysex ) 783 | message.timeStamp = time * 0.000000001; 784 | } 785 | 786 | // Track whether any non-filtered messages were found in this 787 | // packet for timestamp calculation 788 | bool foundNonFiltered = false; 789 | 790 | iByte = 0; 791 | if ( continueSysex ) { 792 | // We have a continuing, segmented sysex message. 793 | if ( !( data->ignoreFlags & 0x01 ) ) { 794 | // If we're not ignoring sysex messages, copy the entire packet. 795 | for ( unsigned int j=0; jdata[j] ); 797 | } 798 | continueSysex = packet->data[nBytes-1] != 0xF7; 799 | 800 | if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { 801 | // If not a continuing sysex message, invoke the user callback function or queue the message. 802 | if ( data->usingCallback ) { 803 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 804 | callback( message.timeStamp, &message.bytes, data->userData ); 805 | } 806 | else { 807 | // As long as we haven't reached our queue size limit, push the message. 808 | if ( !data->queue.push( message ) ) 809 | std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; 810 | } 811 | message.bytes.clear(); 812 | } 813 | } 814 | else { 815 | while ( iByte < nBytes ) { 816 | size = 0; 817 | // We are expecting that the next byte in the packet is a status byte. 818 | status = packet->data[iByte]; 819 | if ( !(status & 0x80) ) break; 820 | // Determine the number of bytes in the MIDI message. 821 | if ( status < 0xC0 ) size = 3; 822 | else if ( status < 0xE0 ) size = 2; 823 | else if ( status < 0xF0 ) size = 3; 824 | else if ( status == 0xF0 ) { 825 | // A MIDI sysex 826 | if ( data->ignoreFlags & 0x01 ) { 827 | size = 0; 828 | iByte = nBytes; 829 | } 830 | else size = nBytes - iByte; 831 | continueSysex = packet->data[nBytes-1] != 0xF7; 832 | } 833 | else if ( status == 0xF1 ) { 834 | // A MIDI time code message 835 | if ( data->ignoreFlags & 0x02 ) { 836 | size = 0; 837 | iByte += 2; 838 | } 839 | else size = 2; 840 | } 841 | else if ( status == 0xF2 ) size = 3; 842 | else if ( status == 0xF3 ) size = 2; 843 | else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { 844 | // A MIDI timing tick message and we're ignoring it. 845 | size = 0; 846 | iByte += 1; 847 | } 848 | else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { 849 | // A MIDI active sensing message and we're ignoring it. 850 | size = 0; 851 | iByte += 1; 852 | } 853 | else size = 1; 854 | 855 | // Copy the MIDI data to our vector. 856 | if ( size ) { 857 | foundNonFiltered = true; 858 | message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); 859 | if ( !continueSysex ) { 860 | // If not a continuing sysex message, invoke the user callback function or queue the message. 861 | if ( data->usingCallback ) { 862 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 863 | callback( message.timeStamp, &message.bytes, data->userData ); 864 | } 865 | else { 866 | // As long as we haven't reached our queue size limit, push the message. 867 | if ( !data->queue.push( message ) ) 868 | std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; 869 | } 870 | message.bytes.clear(); 871 | } 872 | iByte += size; 873 | } 874 | } 875 | } 876 | 877 | // Save the time of the last non-filtered message 878 | if ( foundNonFiltered ) { 879 | apiData->lastTime = packet->timeStamp; 880 | if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages 881 | apiData->lastTime = AudioGetCurrentHostTime(); 882 | } 883 | } 884 | 885 | packet = MIDIPacketNext(packet); 886 | } 887 | } 888 | 889 | MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ) 890 | : MidiInApi( queueSizeLimit ) 891 | { 892 | MidiInCore::initialize( clientName ); 893 | } 894 | 895 | MidiInCore :: ~MidiInCore( void ) 896 | { 897 | // Close a connection if it exists. 898 | MidiInCore::closePort(); 899 | 900 | // Cleanup. 901 | CoreMidiData *data = static_cast (apiData_); 902 | MIDIClientDispose( data->client ); 903 | if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); 904 | delete data; 905 | } 906 | 907 | void MidiInCore :: initialize( const std::string& clientName ) 908 | { 909 | // Set up our client. 910 | MIDIClientRef client; 911 | CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); 912 | OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); 913 | if ( result != noErr ) { 914 | std::ostringstream ost; 915 | ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; 916 | errorString_ = ost.str(); 917 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 918 | return; 919 | } 920 | 921 | // Save our api-specific connection information. 922 | CoreMidiData *data = (CoreMidiData *) new CoreMidiData; 923 | data->client = client; 924 | data->endpoint = 0; 925 | apiData_ = (void *) data; 926 | inputData_.apiData = (void *) data; 927 | CFRelease( name ); 928 | } 929 | 930 | void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName ) 931 | { 932 | if ( connected_ ) { 933 | errorString_ = "MidiInCore::openPort: a valid connection already exists!"; 934 | error( RtMidiError::WARNING, errorString_ ); 935 | return; 936 | } 937 | 938 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 939 | unsigned int nSrc = MIDIGetNumberOfSources(); 940 | if ( nSrc < 1 ) { 941 | errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; 942 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 943 | return; 944 | } 945 | 946 | if ( portNumber >= nSrc ) { 947 | std::ostringstream ost; 948 | ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 949 | errorString_ = ost.str(); 950 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 951 | return; 952 | } 953 | 954 | MIDIPortRef port; 955 | CoreMidiData *data = static_cast (apiData_); 956 | CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 957 | OSStatus result = MIDIInputPortCreate( data->client, 958 | portNameRef, 959 | midiInputCallback, (void *)&inputData_, &port ); 960 | CFRelease( portNameRef ); 961 | 962 | if ( result != noErr ) { 963 | MIDIClientDispose( data->client ); 964 | errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; 965 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 966 | return; 967 | } 968 | 969 | // Get the desired input source identifier. 970 | MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); 971 | if ( endpoint == 0 ) { 972 | MIDIPortDispose( port ); 973 | MIDIClientDispose( data->client ); 974 | errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; 975 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 976 | return; 977 | } 978 | 979 | // Make the connection. 980 | result = MIDIPortConnectSource( port, endpoint, NULL ); 981 | if ( result != noErr ) { 982 | MIDIPortDispose( port ); 983 | MIDIClientDispose( data->client ); 984 | errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; 985 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 986 | return; 987 | } 988 | 989 | // Save our api-specific port information. 990 | data->port = port; 991 | 992 | connected_ = true; 993 | } 994 | 995 | void MidiInCore :: openVirtualPort( const std::string &portName ) 996 | { 997 | CoreMidiData *data = static_cast (apiData_); 998 | 999 | // Create a virtual MIDI input destination. 1000 | MIDIEndpointRef endpoint; 1001 | CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1002 | OSStatus result = MIDIDestinationCreate( data->client, 1003 | portNameRef, 1004 | midiInputCallback, (void *)&inputData_, &endpoint ); 1005 | CFRelease( portNameRef ); 1006 | 1007 | if ( result != noErr ) { 1008 | errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; 1009 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1010 | return; 1011 | } 1012 | 1013 | // Save our api-specific connection information. 1014 | data->endpoint = endpoint; 1015 | } 1016 | 1017 | void MidiInCore :: closePort( void ) 1018 | { 1019 | CoreMidiData *data = static_cast (apiData_); 1020 | 1021 | if ( data->endpoint ) { 1022 | MIDIEndpointDispose( data->endpoint ); 1023 | data->endpoint = 0; 1024 | } 1025 | 1026 | if ( data->port ) { 1027 | MIDIPortDispose( data->port ); 1028 | data->port = 0; 1029 | } 1030 | 1031 | connected_ = false; 1032 | } 1033 | 1034 | void MidiInCore :: setClientName ( const std::string& ) 1035 | { 1036 | 1037 | errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; 1038 | error( RtMidiError::WARNING, errorString_ ); 1039 | 1040 | } 1041 | 1042 | void MidiInCore :: setPortName ( const std::string& ) 1043 | { 1044 | 1045 | errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; 1046 | error( RtMidiError::WARNING, errorString_ ); 1047 | 1048 | } 1049 | 1050 | unsigned int MidiInCore :: getPortCount() 1051 | { 1052 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1053 | return MIDIGetNumberOfSources(); 1054 | } 1055 | 1056 | // This function was submitted by Douglas Casey Tucker and apparently 1057 | // derived largely from PortMidi. 1058 | CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) 1059 | { 1060 | CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); 1061 | CFStringRef str; 1062 | 1063 | // Begin with the endpoint's name. 1064 | str = NULL; 1065 | MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); 1066 | if ( str != NULL ) { 1067 | CFStringAppend( result, str ); 1068 | CFRelease( str ); 1069 | } 1070 | 1071 | MIDIEntityRef entity = 0; 1072 | MIDIEndpointGetEntity( endpoint, &entity ); 1073 | if ( entity == 0 ) 1074 | // probably virtual 1075 | return result; 1076 | 1077 | if ( CFStringGetLength( result ) == 0 ) { 1078 | // endpoint name has zero length -- try the entity 1079 | str = NULL; 1080 | MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); 1081 | if ( str != NULL ) { 1082 | CFStringAppend( result, str ); 1083 | CFRelease( str ); 1084 | } 1085 | } 1086 | // now consider the device's name 1087 | MIDIDeviceRef device = 0; 1088 | MIDIEntityGetDevice( entity, &device ); 1089 | if ( device == 0 ) 1090 | return result; 1091 | 1092 | str = NULL; 1093 | MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); 1094 | if ( CFStringGetLength( result ) == 0 ) { 1095 | CFRelease( result ); 1096 | return str; 1097 | } 1098 | if ( str != NULL ) { 1099 | // if an external device has only one entity, throw away 1100 | // the endpoint name and just use the device name 1101 | if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { 1102 | CFRelease( result ); 1103 | return str; 1104 | } else { 1105 | if ( CFStringGetLength( str ) == 0 ) { 1106 | CFRelease( str ); 1107 | return result; 1108 | } 1109 | // does the entity name already start with the device name? 1110 | // (some drivers do this though they shouldn't) 1111 | // if so, do not prepend 1112 | if ( CFStringCompareWithOptions( result, /* endpoint name */ 1113 | str /* device name */, 1114 | CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { 1115 | // prepend the device name to the entity name 1116 | if ( CFStringGetLength( result ) > 0 ) 1117 | CFStringInsert( result, 0, CFSTR(" ") ); 1118 | 1119 | CFStringInsert( result, 0, str ); 1120 | } 1121 | CFRelease( str ); 1122 | } 1123 | } 1124 | return result; 1125 | } 1126 | 1127 | // This function was submitted by Douglas Casey Tucker and apparently 1128 | // derived largely from PortMidi. 1129 | static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) 1130 | { 1131 | CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); 1132 | CFStringRef str; 1133 | OSStatus err; 1134 | int i; 1135 | 1136 | // Does the endpoint have connections? 1137 | CFDataRef connections = NULL; 1138 | int nConnected = 0; 1139 | bool anyStrings = false; 1140 | err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); 1141 | if ( connections != NULL ) { 1142 | // It has connections, follow them 1143 | // Concatenate the names of all connected devices 1144 | nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); 1145 | if ( nConnected ) { 1146 | const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); 1147 | for ( i=0; i= MIDIGetNumberOfSources() ) { 1193 | std::ostringstream ost; 1194 | ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 1195 | errorString_ = ost.str(); 1196 | error( RtMidiError::WARNING, errorString_ ); 1197 | return stringName; 1198 | } 1199 | 1200 | portRef = MIDIGetSource( portNumber ); 1201 | nameRef = ConnectedEndpointName( portRef ); 1202 | CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); 1203 | CFRelease( nameRef ); 1204 | 1205 | return stringName = name; 1206 | } 1207 | 1208 | //*********************************************************************// 1209 | // API: OS-X 1210 | // Class Definitions: MidiOutCore 1211 | //*********************************************************************// 1212 | 1213 | MidiOutCore :: MidiOutCore( const std::string &clientName ) 1214 | : MidiOutApi() 1215 | { 1216 | MidiOutCore::initialize( clientName ); 1217 | } 1218 | 1219 | MidiOutCore :: ~MidiOutCore( void ) 1220 | { 1221 | // Close a connection if it exists. 1222 | MidiOutCore::closePort(); 1223 | 1224 | // Cleanup. 1225 | CoreMidiData *data = static_cast (apiData_); 1226 | MIDIClientDispose( data->client ); 1227 | if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); 1228 | delete data; 1229 | } 1230 | 1231 | void MidiOutCore :: initialize( const std::string& clientName ) 1232 | { 1233 | // Set up our client. 1234 | MIDIClientRef client; 1235 | CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); 1236 | OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); 1237 | if ( result != noErr ) { 1238 | std::ostringstream ost; 1239 | ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; 1240 | errorString_ = ost.str(); 1241 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1242 | return; 1243 | } 1244 | 1245 | // Save our api-specific connection information. 1246 | CoreMidiData *data = (CoreMidiData *) new CoreMidiData; 1247 | data->client = client; 1248 | data->endpoint = 0; 1249 | apiData_ = (void *) data; 1250 | CFRelease( name ); 1251 | } 1252 | 1253 | unsigned int MidiOutCore :: getPortCount() 1254 | { 1255 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1256 | return MIDIGetNumberOfDestinations(); 1257 | } 1258 | 1259 | std::string MidiOutCore :: getPortName( unsigned int portNumber ) 1260 | { 1261 | CFStringRef nameRef; 1262 | MIDIEndpointRef portRef; 1263 | char name[128]; 1264 | 1265 | std::string stringName; 1266 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1267 | if ( portNumber >= MIDIGetNumberOfDestinations() ) { 1268 | std::ostringstream ost; 1269 | ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 1270 | errorString_ = ost.str(); 1271 | error( RtMidiError::WARNING, errorString_ ); 1272 | return stringName; 1273 | } 1274 | 1275 | portRef = MIDIGetDestination( portNumber ); 1276 | nameRef = ConnectedEndpointName(portRef); 1277 | CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); 1278 | CFRelease( nameRef ); 1279 | 1280 | return stringName = name; 1281 | } 1282 | 1283 | void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName ) 1284 | { 1285 | if ( connected_ ) { 1286 | errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; 1287 | error( RtMidiError::WARNING, errorString_ ); 1288 | return; 1289 | } 1290 | 1291 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 1292 | unsigned int nDest = MIDIGetNumberOfDestinations(); 1293 | if (nDest < 1) { 1294 | errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; 1295 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 1296 | return; 1297 | } 1298 | 1299 | if ( portNumber >= nDest ) { 1300 | std::ostringstream ost; 1301 | ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 1302 | errorString_ = ost.str(); 1303 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 1304 | return; 1305 | } 1306 | 1307 | MIDIPortRef port; 1308 | CoreMidiData *data = static_cast (apiData_); 1309 | CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1310 | OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); 1311 | CFRelease( portNameRef ); 1312 | if ( result != noErr ) { 1313 | MIDIClientDispose( data->client ); 1314 | errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; 1315 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1316 | return; 1317 | } 1318 | 1319 | // Get the desired output port identifier. 1320 | MIDIEndpointRef destination = MIDIGetDestination( portNumber ); 1321 | if ( destination == 0 ) { 1322 | MIDIPortDispose( port ); 1323 | MIDIClientDispose( data->client ); 1324 | errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; 1325 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1326 | return; 1327 | } 1328 | 1329 | // Save our api-specific connection information. 1330 | data->port = port; 1331 | data->destinationId = destination; 1332 | connected_ = true; 1333 | } 1334 | 1335 | void MidiOutCore :: closePort( void ) 1336 | { 1337 | CoreMidiData *data = static_cast (apiData_); 1338 | 1339 | if ( data->endpoint ) { 1340 | MIDIEndpointDispose( data->endpoint ); 1341 | data->endpoint = 0; 1342 | } 1343 | 1344 | if ( data->port ) { 1345 | MIDIPortDispose( data->port ); 1346 | data->port = 0; 1347 | } 1348 | 1349 | connected_ = false; 1350 | } 1351 | 1352 | void MidiOutCore :: setClientName ( const std::string& ) 1353 | { 1354 | 1355 | errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; 1356 | error( RtMidiError::WARNING, errorString_ ); 1357 | 1358 | } 1359 | 1360 | void MidiOutCore :: setPortName ( const std::string& ) 1361 | { 1362 | 1363 | errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; 1364 | error( RtMidiError::WARNING, errorString_ ); 1365 | 1366 | } 1367 | 1368 | void MidiOutCore :: openVirtualPort( const std::string &portName ) 1369 | { 1370 | CoreMidiData *data = static_cast (apiData_); 1371 | 1372 | if ( data->endpoint ) { 1373 | errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; 1374 | error( RtMidiError::WARNING, errorString_ ); 1375 | return; 1376 | } 1377 | 1378 | // Create a virtual MIDI output source. 1379 | MIDIEndpointRef endpoint; 1380 | CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); 1381 | OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint ); 1382 | CFRelease( portNameRef ); 1383 | 1384 | if ( result != noErr ) { 1385 | errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; 1386 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1387 | return; 1388 | } 1389 | 1390 | // Save our api-specific connection information. 1391 | data->endpoint = endpoint; 1392 | } 1393 | 1394 | void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) 1395 | { 1396 | // We use the MIDISendSysex() function to asynchronously send sysex 1397 | // messages. Otherwise, we use a single CoreMidi MIDIPacket. 1398 | unsigned int nBytes = static_cast (size); 1399 | if ( nBytes == 0 ) { 1400 | errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; 1401 | error( RtMidiError::WARNING, errorString_ ); 1402 | return; 1403 | } 1404 | 1405 | MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); 1406 | CoreMidiData *data = static_cast (apiData_); 1407 | OSStatus result; 1408 | 1409 | if ( message[0] != 0xF0 && nBytes > 3 ) { 1410 | errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; 1411 | error( RtMidiError::WARNING, errorString_ ); 1412 | return; 1413 | } 1414 | 1415 | Byte buffer[nBytes+(sizeof( MIDIPacketList ))]; 1416 | ByteCount listSize = sizeof( buffer ); 1417 | MIDIPacketList *packetList = (MIDIPacketList*)buffer; 1418 | MIDIPacket *packet = MIDIPacketListInit( packetList ); 1419 | 1420 | ByteCount remainingBytes = nBytes; 1421 | while ( remainingBytes && packet ) { 1422 | ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket 1423 | const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes]; 1424 | packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr ); 1425 | remainingBytes -= bytesForPacket; 1426 | } 1427 | 1428 | if ( !packet ) { 1429 | errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; 1430 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1431 | return; 1432 | } 1433 | 1434 | // Send to any destinations that may have connected to us. 1435 | if ( data->endpoint ) { 1436 | result = MIDIReceived( data->endpoint, packetList ); 1437 | if ( result != noErr ) { 1438 | errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; 1439 | error( RtMidiError::WARNING, errorString_ ); 1440 | } 1441 | } 1442 | 1443 | // And send to an explicit destination port if we're connected. 1444 | if ( connected_ ) { 1445 | result = MIDISend( data->port, data->destinationId, packetList ); 1446 | if ( result != noErr ) { 1447 | errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; 1448 | error( RtMidiError::WARNING, errorString_ ); 1449 | } 1450 | } 1451 | } 1452 | 1453 | #endif // __MACOSX_CORE__ 1454 | 1455 | 1456 | //*********************************************************************// 1457 | // API: LINUX ALSA SEQUENCER 1458 | //*********************************************************************// 1459 | 1460 | // API information found at: 1461 | // - http://www.alsa-project.org/documentation.php#Library 1462 | 1463 | #if defined(__LINUX_ALSA__) 1464 | 1465 | // The ALSA Sequencer API is based on the use of a callback function for 1466 | // MIDI input. 1467 | // 1468 | // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer 1469 | // time stamps and other assorted fixes!!! 1470 | 1471 | // If you don't need timestamping for incoming MIDI events, define the 1472 | // preprocessor definition AVOID_TIMESTAMPING to save resources 1473 | // associated with the ALSA sequencer queues. 1474 | 1475 | #include 1476 | #include 1477 | 1478 | // ALSA header file. 1479 | #include 1480 | 1481 | // A structure to hold variables related to the ALSA API 1482 | // implementation. 1483 | struct AlsaMidiData { 1484 | snd_seq_t *seq; 1485 | unsigned int portNum; 1486 | int vport; 1487 | snd_seq_port_subscribe_t *subscription; 1488 | snd_midi_event_t *coder; 1489 | unsigned int bufferSize; 1490 | unsigned char *buffer; 1491 | pthread_t thread; 1492 | pthread_t dummy_thread_id; 1493 | snd_seq_real_time_t lastTime; 1494 | int queue_id; // an input queue is needed to get timestamped events 1495 | int trigger_fds[2]; 1496 | }; 1497 | 1498 | #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) 1499 | 1500 | //*********************************************************************// 1501 | // API: LINUX ALSA 1502 | // Class Definitions: MidiInAlsa 1503 | //*********************************************************************// 1504 | 1505 | static void *alsaMidiHandler( void *ptr ) 1506 | { 1507 | MidiInApi::RtMidiInData *data = static_cast (ptr); 1508 | AlsaMidiData *apiData = static_cast (data->apiData); 1509 | 1510 | long nBytes; 1511 | double time; 1512 | bool continueSysex = false; 1513 | bool doDecode = false; 1514 | MidiInApi::MidiMessage message; 1515 | int poll_fd_count; 1516 | struct pollfd *poll_fds; 1517 | 1518 | snd_seq_event_t *ev; 1519 | int result; 1520 | apiData->bufferSize = 32; 1521 | result = snd_midi_event_new( 0, &apiData->coder ); 1522 | if ( result < 0 ) { 1523 | data->doInput = false; 1524 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; 1525 | return 0; 1526 | } 1527 | unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); 1528 | if ( buffer == NULL ) { 1529 | data->doInput = false; 1530 | snd_midi_event_free( apiData->coder ); 1531 | apiData->coder = 0; 1532 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; 1533 | return 0; 1534 | } 1535 | snd_midi_event_init( apiData->coder ); 1536 | snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages 1537 | 1538 | poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; 1539 | poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); 1540 | snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); 1541 | poll_fds[0].fd = apiData->trigger_fds[0]; 1542 | poll_fds[0].events = POLLIN; 1543 | 1544 | while ( data->doInput ) { 1545 | 1546 | if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { 1547 | // No data pending 1548 | if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { 1549 | if ( poll_fds[0].revents & POLLIN ) { 1550 | bool dummy; 1551 | int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); 1552 | (void) res; 1553 | } 1554 | } 1555 | continue; 1556 | } 1557 | 1558 | // If here, there should be data. 1559 | result = snd_seq_event_input( apiData->seq, &ev ); 1560 | if ( result == -ENOSPC ) { 1561 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; 1562 | continue; 1563 | } 1564 | else if ( result <= 0 ) { 1565 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; 1566 | perror("System reports"); 1567 | continue; 1568 | } 1569 | 1570 | // This is a bit weird, but we now have to decode an ALSA MIDI 1571 | // event (back) into MIDI bytes. We'll ignore non-MIDI types. 1572 | if ( !continueSysex ) message.bytes.clear(); 1573 | 1574 | doDecode = false; 1575 | switch ( ev->type ) { 1576 | 1577 | case SND_SEQ_EVENT_PORT_SUBSCRIBED: 1578 | #if defined(__RTMIDI_DEBUG__) 1579 | std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; 1580 | #endif 1581 | break; 1582 | 1583 | case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: 1584 | #if defined(__RTMIDI_DEBUG__) 1585 | std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; 1586 | std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" 1587 | << (int) ev->data.connect.sender.port 1588 | << ", dest = " << (int) ev->data.connect.dest.client << ":" 1589 | << (int) ev->data.connect.dest.port 1590 | << std::endl; 1591 | #endif 1592 | break; 1593 | 1594 | case SND_SEQ_EVENT_QFRAME: // MIDI time code 1595 | if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1596 | break; 1597 | 1598 | case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick 1599 | if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1600 | break; 1601 | 1602 | case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick 1603 | if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1604 | break; 1605 | 1606 | case SND_SEQ_EVENT_SENSING: // Active sensing 1607 | if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; 1608 | break; 1609 | 1610 | case SND_SEQ_EVENT_SYSEX: 1611 | if ( (data->ignoreFlags & 0x01) ) break; 1612 | if ( ev->data.ext.len > apiData->bufferSize ) { 1613 | apiData->bufferSize = ev->data.ext.len; 1614 | free( buffer ); 1615 | buffer = (unsigned char *) malloc( apiData->bufferSize ); 1616 | if ( buffer == NULL ) { 1617 | data->doInput = false; 1618 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; 1619 | break; 1620 | } 1621 | } 1622 | doDecode = true; 1623 | break; 1624 | 1625 | default: 1626 | doDecode = true; 1627 | } 1628 | 1629 | if ( doDecode ) { 1630 | 1631 | nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); 1632 | if ( nBytes > 0 ) { 1633 | // The ALSA sequencer has a maximum buffer size for MIDI sysex 1634 | // events of 256 bytes. If a device sends sysex messages larger 1635 | // than this, they are segmented into 256 byte chunks. So, 1636 | // we'll watch for this and concatenate sysex chunks into a 1637 | // single sysex message if necessary. 1638 | if ( !continueSysex ) 1639 | message.bytes.assign( buffer, &buffer[nBytes] ); 1640 | else 1641 | message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); 1642 | 1643 | continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); 1644 | if ( !continueSysex ) { 1645 | 1646 | // Calculate the time stamp: 1647 | message.timeStamp = 0.0; 1648 | 1649 | // Method 1: Use the system time. 1650 | //(void)gettimeofday(&tv, (struct timezone *)NULL); 1651 | //time = (tv.tv_sec * 1000000) + tv.tv_usec; 1652 | 1653 | // Method 2: Use the ALSA sequencer event time data. 1654 | // (thanks to Pedro Lopez-Cabanillas!). 1655 | 1656 | // Using method from: 1657 | // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html 1658 | 1659 | // Perform the carry for the later subtraction by updating y. 1660 | // Temp var y is timespec because computation requires signed types, 1661 | // while snd_seq_real_time_t has unsigned types. 1662 | snd_seq_real_time_t &x( ev->time.time ); 1663 | struct timespec y; 1664 | y.tv_nsec = apiData->lastTime.tv_nsec; 1665 | y.tv_sec = apiData->lastTime.tv_sec; 1666 | if ( x.tv_nsec < y.tv_nsec ) { 1667 | int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1; 1668 | y.tv_nsec -= 1000000000 * nsec; 1669 | y.tv_sec += nsec; 1670 | } 1671 | if ( x.tv_nsec - y.tv_nsec > 1000000000 ) { 1672 | int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000; 1673 | y.tv_nsec += 1000000000 * nsec; 1674 | y.tv_sec -= nsec; 1675 | } 1676 | 1677 | // Compute the time difference. 1678 | time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9; 1679 | 1680 | apiData->lastTime = ev->time.time; 1681 | 1682 | if ( data->firstMessage == true ) 1683 | data->firstMessage = false; 1684 | else 1685 | message.timeStamp = time; 1686 | } 1687 | else { 1688 | #if defined(__RTMIDI_DEBUG__) 1689 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; 1690 | #endif 1691 | } 1692 | } 1693 | } 1694 | 1695 | snd_seq_free_event( ev ); 1696 | if ( message.bytes.size() == 0 || continueSysex ) continue; 1697 | 1698 | if ( data->usingCallback ) { 1699 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 1700 | callback( message.timeStamp, &message.bytes, data->userData ); 1701 | } 1702 | else { 1703 | // As long as we haven't reached our queue size limit, push the message. 1704 | if ( !data->queue.push( message ) ) 1705 | std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; 1706 | } 1707 | } 1708 | 1709 | if ( buffer ) free( buffer ); 1710 | snd_midi_event_free( apiData->coder ); 1711 | apiData->coder = 0; 1712 | apiData->thread = apiData->dummy_thread_id; 1713 | return 0; 1714 | } 1715 | 1716 | MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ) 1717 | : MidiInApi( queueSizeLimit ) 1718 | { 1719 | MidiInAlsa::initialize( clientName ); 1720 | } 1721 | 1722 | MidiInAlsa :: ~MidiInAlsa() 1723 | { 1724 | // Close a connection if it exists. 1725 | MidiInAlsa::closePort(); 1726 | 1727 | // Shutdown the input thread. 1728 | AlsaMidiData *data = static_cast (apiData_); 1729 | if ( inputData_.doInput ) { 1730 | inputData_.doInput = false; 1731 | int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); 1732 | (void) res; 1733 | if ( !pthread_equal(data->thread, data->dummy_thread_id) ) 1734 | pthread_join( data->thread, NULL ); 1735 | } 1736 | 1737 | // Cleanup. 1738 | close ( data->trigger_fds[0] ); 1739 | close ( data->trigger_fds[1] ); 1740 | if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); 1741 | #ifndef AVOID_TIMESTAMPING 1742 | snd_seq_free_queue( data->seq, data->queue_id ); 1743 | #endif 1744 | snd_seq_close( data->seq ); 1745 | delete data; 1746 | } 1747 | 1748 | void MidiInAlsa :: initialize( const std::string& clientName ) 1749 | { 1750 | // Set up the ALSA sequencer client. 1751 | snd_seq_t *seq; 1752 | int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK ); 1753 | if ( result < 0 ) { 1754 | errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; 1755 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1756 | return; 1757 | } 1758 | 1759 | // Set client name. 1760 | snd_seq_set_client_name( seq, clientName.c_str() ); 1761 | 1762 | // Save our api-specific connection information. 1763 | AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; 1764 | data->seq = seq; 1765 | data->portNum = -1; 1766 | data->vport = -1; 1767 | data->subscription = 0; 1768 | data->dummy_thread_id = pthread_self(); 1769 | data->thread = data->dummy_thread_id; 1770 | data->trigger_fds[0] = -1; 1771 | data->trigger_fds[1] = -1; 1772 | apiData_ = (void *) data; 1773 | inputData_.apiData = (void *) data; 1774 | 1775 | if ( pipe(data->trigger_fds) == -1 ) { 1776 | errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; 1777 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1778 | return; 1779 | } 1780 | 1781 | // Create the input queue 1782 | #ifndef AVOID_TIMESTAMPING 1783 | data->queue_id = snd_seq_alloc_named_queue( seq, "RtMidi Queue" ); 1784 | // Set arbitrary tempo (mm=100) and resolution (240) 1785 | snd_seq_queue_tempo_t *qtempo; 1786 | snd_seq_queue_tempo_alloca( &qtempo ); 1787 | snd_seq_queue_tempo_set_tempo( qtempo, 600000 ); 1788 | snd_seq_queue_tempo_set_ppq( qtempo, 240 ); 1789 | snd_seq_set_queue_tempo( data->seq, data->queue_id, qtempo ); 1790 | snd_seq_drain_output( data->seq ); 1791 | #endif 1792 | } 1793 | 1794 | // This function is used to count or get the pinfo structure for a given port number. 1795 | unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) 1796 | { 1797 | snd_seq_client_info_t *cinfo; 1798 | int client; 1799 | int count = 0; 1800 | snd_seq_client_info_alloca( &cinfo ); 1801 | 1802 | snd_seq_client_info_set_client( cinfo, -1 ); 1803 | while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { 1804 | client = snd_seq_client_info_get_client( cinfo ); 1805 | if ( client == 0 ) continue; 1806 | // Reset query info 1807 | snd_seq_port_info_set_client( pinfo, client ); 1808 | snd_seq_port_info_set_port( pinfo, -1 ); 1809 | while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { 1810 | unsigned int atyp = snd_seq_port_info_get_type( pinfo ); 1811 | if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && 1812 | ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) && 1813 | ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue; 1814 | 1815 | unsigned int caps = snd_seq_port_info_get_capability( pinfo ); 1816 | if ( ( caps & type ) != type ) continue; 1817 | if ( count == portNumber ) return 1; 1818 | ++count; 1819 | } 1820 | } 1821 | 1822 | // If a negative portNumber was used, return the port count. 1823 | if ( portNumber < 0 ) return count; 1824 | return 0; 1825 | } 1826 | 1827 | unsigned int MidiInAlsa :: getPortCount() 1828 | { 1829 | snd_seq_port_info_t *pinfo; 1830 | snd_seq_port_info_alloca( &pinfo ); 1831 | 1832 | AlsaMidiData *data = static_cast (apiData_); 1833 | return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); 1834 | } 1835 | 1836 | std::string MidiInAlsa :: getPortName( unsigned int portNumber ) 1837 | { 1838 | snd_seq_client_info_t *cinfo; 1839 | snd_seq_port_info_t *pinfo; 1840 | snd_seq_client_info_alloca( &cinfo ); 1841 | snd_seq_port_info_alloca( &pinfo ); 1842 | 1843 | std::string stringName; 1844 | AlsaMidiData *data = static_cast (apiData_); 1845 | if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { 1846 | int cnum = snd_seq_port_info_get_client( pinfo ); 1847 | snd_seq_get_any_client_info( data->seq, cnum, cinfo ); 1848 | std::ostringstream os; 1849 | os << snd_seq_client_info_get_name( cinfo ); 1850 | os << ":"; 1851 | os << snd_seq_port_info_get_name( pinfo ); 1852 | os << " "; // These lines added to make sure devices are listed 1853 | os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names 1854 | os << ":"; 1855 | os << snd_seq_port_info_get_port( pinfo ); 1856 | stringName = os.str(); 1857 | return stringName; 1858 | } 1859 | 1860 | // If we get here, we didn't find a match. 1861 | errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; 1862 | error( RtMidiError::WARNING, errorString_ ); 1863 | return stringName; 1864 | } 1865 | 1866 | void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portName ) 1867 | { 1868 | if ( connected_ ) { 1869 | errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; 1870 | error( RtMidiError::WARNING, errorString_ ); 1871 | return; 1872 | } 1873 | 1874 | unsigned int nSrc = this->getPortCount(); 1875 | if ( nSrc < 1 ) { 1876 | errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; 1877 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 1878 | return; 1879 | } 1880 | 1881 | snd_seq_port_info_t *src_pinfo; 1882 | snd_seq_port_info_alloca( &src_pinfo ); 1883 | AlsaMidiData *data = static_cast (apiData_); 1884 | if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { 1885 | std::ostringstream ost; 1886 | ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 1887 | errorString_ = ost.str(); 1888 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 1889 | return; 1890 | } 1891 | 1892 | snd_seq_addr_t sender, receiver; 1893 | sender.client = snd_seq_port_info_get_client( src_pinfo ); 1894 | sender.port = snd_seq_port_info_get_port( src_pinfo ); 1895 | receiver.client = snd_seq_client_id( data->seq ); 1896 | 1897 | snd_seq_port_info_t *pinfo; 1898 | snd_seq_port_info_alloca( &pinfo ); 1899 | if ( data->vport < 0 ) { 1900 | snd_seq_port_info_set_client( pinfo, 0 ); 1901 | snd_seq_port_info_set_port( pinfo, 0 ); 1902 | snd_seq_port_info_set_capability( pinfo, 1903 | SND_SEQ_PORT_CAP_WRITE | 1904 | SND_SEQ_PORT_CAP_SUBS_WRITE ); 1905 | snd_seq_port_info_set_type( pinfo, 1906 | SND_SEQ_PORT_TYPE_MIDI_GENERIC | 1907 | SND_SEQ_PORT_TYPE_APPLICATION ); 1908 | snd_seq_port_info_set_midi_channels(pinfo, 16); 1909 | #ifndef AVOID_TIMESTAMPING 1910 | snd_seq_port_info_set_timestamping( pinfo, 1 ); 1911 | snd_seq_port_info_set_timestamp_real( pinfo, 1 ); 1912 | snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); 1913 | #endif 1914 | snd_seq_port_info_set_name( pinfo, portName.c_str() ); 1915 | data->vport = snd_seq_create_port( data->seq, pinfo ); 1916 | 1917 | if ( data->vport < 0 ) { 1918 | errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; 1919 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1920 | return; 1921 | } 1922 | data->vport = snd_seq_port_info_get_port( pinfo ); 1923 | } 1924 | 1925 | receiver.port = data->vport; 1926 | 1927 | if ( !data->subscription ) { 1928 | // Make subscription 1929 | if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { 1930 | errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; 1931 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1932 | return; 1933 | } 1934 | snd_seq_port_subscribe_set_sender( data->subscription, &sender ); 1935 | snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); 1936 | if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { 1937 | snd_seq_port_subscribe_free( data->subscription ); 1938 | data->subscription = 0; 1939 | errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; 1940 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1941 | return; 1942 | } 1943 | } 1944 | 1945 | if ( inputData_.doInput == false ) { 1946 | // Start the input queue 1947 | #ifndef AVOID_TIMESTAMPING 1948 | snd_seq_start_queue( data->seq, data->queue_id, NULL ); 1949 | snd_seq_drain_output( data->seq ); 1950 | #endif 1951 | // Start our MIDI input thread. 1952 | pthread_attr_t attr; 1953 | pthread_attr_init( &attr ); 1954 | pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); 1955 | pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); 1956 | 1957 | inputData_.doInput = true; 1958 | int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); 1959 | pthread_attr_destroy( &attr ); 1960 | if ( err ) { 1961 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 1962 | snd_seq_port_subscribe_free( data->subscription ); 1963 | data->subscription = 0; 1964 | inputData_.doInput = false; 1965 | errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; 1966 | error( RtMidiError::THREAD_ERROR, errorString_ ); 1967 | return; 1968 | } 1969 | } 1970 | 1971 | connected_ = true; 1972 | } 1973 | 1974 | void MidiInAlsa :: openVirtualPort( const std::string &portName ) 1975 | { 1976 | AlsaMidiData *data = static_cast (apiData_); 1977 | if ( data->vport < 0 ) { 1978 | snd_seq_port_info_t *pinfo; 1979 | snd_seq_port_info_alloca( &pinfo ); 1980 | snd_seq_port_info_set_capability( pinfo, 1981 | SND_SEQ_PORT_CAP_WRITE | 1982 | SND_SEQ_PORT_CAP_SUBS_WRITE ); 1983 | snd_seq_port_info_set_type( pinfo, 1984 | SND_SEQ_PORT_TYPE_MIDI_GENERIC | 1985 | SND_SEQ_PORT_TYPE_APPLICATION ); 1986 | snd_seq_port_info_set_midi_channels( pinfo, 16 ); 1987 | #ifndef AVOID_TIMESTAMPING 1988 | snd_seq_port_info_set_timestamping( pinfo, 1 ); 1989 | snd_seq_port_info_set_timestamp_real( pinfo, 1 ); 1990 | snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); 1991 | #endif 1992 | snd_seq_port_info_set_name( pinfo, portName.c_str() ); 1993 | data->vport = snd_seq_create_port( data->seq, pinfo ); 1994 | 1995 | if ( data->vport < 0 ) { 1996 | errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; 1997 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1998 | return; 1999 | } 2000 | data->vport = snd_seq_port_info_get_port( pinfo ); 2001 | } 2002 | 2003 | if ( inputData_.doInput == false ) { 2004 | // Wait for old thread to stop, if still running 2005 | if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) 2006 | pthread_join( data->thread, NULL ); 2007 | 2008 | // Start the input queue 2009 | #ifndef AVOID_TIMESTAMPING 2010 | snd_seq_start_queue( data->seq, data->queue_id, NULL ); 2011 | snd_seq_drain_output( data->seq ); 2012 | #endif 2013 | // Start our MIDI input thread. 2014 | pthread_attr_t attr; 2015 | pthread_attr_init( &attr ); 2016 | pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); 2017 | pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); 2018 | 2019 | inputData_.doInput = true; 2020 | int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); 2021 | pthread_attr_destroy( &attr ); 2022 | if ( err ) { 2023 | if ( data->subscription ) { 2024 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 2025 | snd_seq_port_subscribe_free( data->subscription ); 2026 | data->subscription = 0; 2027 | } 2028 | inputData_.doInput = false; 2029 | errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; 2030 | error( RtMidiError::THREAD_ERROR, errorString_ ); 2031 | return; 2032 | } 2033 | } 2034 | } 2035 | 2036 | void MidiInAlsa :: closePort( void ) 2037 | { 2038 | AlsaMidiData *data = static_cast (apiData_); 2039 | 2040 | if ( connected_ ) { 2041 | if ( data->subscription ) { 2042 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 2043 | snd_seq_port_subscribe_free( data->subscription ); 2044 | data->subscription = 0; 2045 | } 2046 | // Stop the input queue 2047 | #ifndef AVOID_TIMESTAMPING 2048 | snd_seq_stop_queue( data->seq, data->queue_id, NULL ); 2049 | snd_seq_drain_output( data->seq ); 2050 | #endif 2051 | connected_ = false; 2052 | } 2053 | 2054 | // Stop thread to avoid triggering the callback, while the port is intended to be closed 2055 | if ( inputData_.doInput ) { 2056 | inputData_.doInput = false; 2057 | int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); 2058 | (void) res; 2059 | if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) 2060 | pthread_join( data->thread, NULL ); 2061 | } 2062 | } 2063 | 2064 | void MidiInAlsa :: setClientName( const std::string &clientName ) 2065 | { 2066 | 2067 | AlsaMidiData *data = static_cast ( apiData_ ); 2068 | snd_seq_set_client_name( data->seq, clientName.c_str() ); 2069 | 2070 | } 2071 | 2072 | void MidiInAlsa :: setPortName( const std::string &portName ) 2073 | { 2074 | AlsaMidiData *data = static_cast (apiData_); 2075 | snd_seq_port_info_t *pinfo; 2076 | snd_seq_port_info_alloca( &pinfo ); 2077 | snd_seq_get_port_info( data->seq, data->vport, pinfo ); 2078 | snd_seq_port_info_set_name( pinfo, portName.c_str() ); 2079 | snd_seq_set_port_info( data->seq, data->vport, pinfo ); 2080 | } 2081 | 2082 | //*********************************************************************// 2083 | // API: LINUX ALSA 2084 | // Class Definitions: MidiOutAlsa 2085 | //*********************************************************************// 2086 | 2087 | MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi() 2088 | { 2089 | MidiOutAlsa::initialize( clientName ); 2090 | } 2091 | 2092 | MidiOutAlsa :: ~MidiOutAlsa() 2093 | { 2094 | // Close a connection if it exists. 2095 | MidiOutAlsa::closePort(); 2096 | 2097 | // Cleanup. 2098 | AlsaMidiData *data = static_cast (apiData_); 2099 | if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); 2100 | if ( data->coder ) snd_midi_event_free( data->coder ); 2101 | if ( data->buffer ) free( data->buffer ); 2102 | snd_seq_close( data->seq ); 2103 | delete data; 2104 | } 2105 | 2106 | void MidiOutAlsa :: initialize( const std::string& clientName ) 2107 | { 2108 | // Set up the ALSA sequencer client. 2109 | snd_seq_t *seq; 2110 | int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); 2111 | if ( result1 < 0 ) { 2112 | errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; 2113 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2114 | return; 2115 | } 2116 | 2117 | // Set client name. 2118 | snd_seq_set_client_name( seq, clientName.c_str() ); 2119 | 2120 | // Save our api-specific connection information. 2121 | AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; 2122 | data->seq = seq; 2123 | data->portNum = -1; 2124 | data->vport = -1; 2125 | data->bufferSize = 32; 2126 | data->coder = 0; 2127 | data->buffer = 0; 2128 | int result = snd_midi_event_new( data->bufferSize, &data->coder ); 2129 | if ( result < 0 ) { 2130 | delete data; 2131 | errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; 2132 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2133 | return; 2134 | } 2135 | data->buffer = (unsigned char *) malloc( data->bufferSize ); 2136 | if ( data->buffer == NULL ) { 2137 | delete data; 2138 | errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; 2139 | error( RtMidiError::MEMORY_ERROR, errorString_ ); 2140 | return; 2141 | } 2142 | snd_midi_event_init( data->coder ); 2143 | apiData_ = (void *) data; 2144 | } 2145 | 2146 | unsigned int MidiOutAlsa :: getPortCount() 2147 | { 2148 | snd_seq_port_info_t *pinfo; 2149 | snd_seq_port_info_alloca( &pinfo ); 2150 | 2151 | AlsaMidiData *data = static_cast (apiData_); 2152 | return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); 2153 | } 2154 | 2155 | std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) 2156 | { 2157 | snd_seq_client_info_t *cinfo; 2158 | snd_seq_port_info_t *pinfo; 2159 | snd_seq_client_info_alloca( &cinfo ); 2160 | snd_seq_port_info_alloca( &pinfo ); 2161 | 2162 | std::string stringName; 2163 | AlsaMidiData *data = static_cast (apiData_); 2164 | if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { 2165 | int cnum = snd_seq_port_info_get_client( pinfo ); 2166 | snd_seq_get_any_client_info( data->seq, cnum, cinfo ); 2167 | std::ostringstream os; 2168 | os << snd_seq_client_info_get_name( cinfo ); 2169 | os << ":"; 2170 | os << snd_seq_port_info_get_name( pinfo ); 2171 | os << " "; // These lines added to make sure devices are listed 2172 | os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names 2173 | os << ":"; 2174 | os << snd_seq_port_info_get_port( pinfo ); 2175 | stringName = os.str(); 2176 | return stringName; 2177 | } 2178 | 2179 | // If we get here, we didn't find a match. 2180 | errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; 2181 | error( RtMidiError::WARNING, errorString_ ); 2182 | return stringName; 2183 | } 2184 | 2185 | void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portName ) 2186 | { 2187 | if ( connected_ ) { 2188 | errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; 2189 | error( RtMidiError::WARNING, errorString_ ); 2190 | return; 2191 | } 2192 | 2193 | unsigned int nSrc = this->getPortCount(); 2194 | if ( nSrc < 1 ) { 2195 | errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; 2196 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2197 | return; 2198 | } 2199 | 2200 | snd_seq_port_info_t *pinfo; 2201 | snd_seq_port_info_alloca( &pinfo ); 2202 | AlsaMidiData *data = static_cast (apiData_); 2203 | if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { 2204 | std::ostringstream ost; 2205 | ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2206 | errorString_ = ost.str(); 2207 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2208 | return; 2209 | } 2210 | 2211 | snd_seq_addr_t sender, receiver; 2212 | receiver.client = snd_seq_port_info_get_client( pinfo ); 2213 | receiver.port = snd_seq_port_info_get_port( pinfo ); 2214 | sender.client = snd_seq_client_id( data->seq ); 2215 | 2216 | if ( data->vport < 0 ) { 2217 | data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), 2218 | SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, 2219 | SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); 2220 | if ( data->vport < 0 ) { 2221 | errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; 2222 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2223 | return; 2224 | } 2225 | } 2226 | 2227 | sender.port = data->vport; 2228 | 2229 | // Make subscription 2230 | if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { 2231 | snd_seq_port_subscribe_free( data->subscription ); 2232 | errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; 2233 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2234 | return; 2235 | } 2236 | snd_seq_port_subscribe_set_sender( data->subscription, &sender ); 2237 | snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); 2238 | snd_seq_port_subscribe_set_time_update( data->subscription, 1 ); 2239 | snd_seq_port_subscribe_set_time_real( data->subscription, 1 ); 2240 | if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { 2241 | snd_seq_port_subscribe_free( data->subscription ); 2242 | errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; 2243 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2244 | return; 2245 | } 2246 | 2247 | connected_ = true; 2248 | } 2249 | 2250 | void MidiOutAlsa :: closePort( void ) 2251 | { 2252 | if ( connected_ ) { 2253 | AlsaMidiData *data = static_cast (apiData_); 2254 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 2255 | snd_seq_port_subscribe_free( data->subscription ); 2256 | data->subscription = 0; 2257 | connected_ = false; 2258 | } 2259 | } 2260 | 2261 | void MidiOutAlsa :: setClientName( const std::string &clientName ) 2262 | { 2263 | 2264 | AlsaMidiData *data = static_cast ( apiData_ ); 2265 | snd_seq_set_client_name( data->seq, clientName.c_str() ); 2266 | 2267 | } 2268 | 2269 | void MidiOutAlsa :: setPortName( const std::string &portName ) 2270 | { 2271 | AlsaMidiData *data = static_cast (apiData_); 2272 | snd_seq_port_info_t *pinfo; 2273 | snd_seq_port_info_alloca( &pinfo ); 2274 | snd_seq_get_port_info( data->seq, data->vport, pinfo ); 2275 | snd_seq_port_info_set_name( pinfo, portName.c_str() ); 2276 | snd_seq_set_port_info( data->seq, data->vport, pinfo ); 2277 | } 2278 | 2279 | void MidiOutAlsa :: openVirtualPort( const std::string &portName ) 2280 | { 2281 | AlsaMidiData *data = static_cast (apiData_); 2282 | if ( data->vport < 0 ) { 2283 | data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), 2284 | SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, 2285 | SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); 2286 | 2287 | if ( data->vport < 0 ) { 2288 | errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; 2289 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2290 | } 2291 | } 2292 | } 2293 | 2294 | void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) 2295 | { 2296 | int result; 2297 | AlsaMidiData *data = static_cast (apiData_); 2298 | unsigned int nBytes = static_cast (size); 2299 | if ( nBytes > data->bufferSize ) { 2300 | data->bufferSize = nBytes; 2301 | result = snd_midi_event_resize_buffer( data->coder, nBytes ); 2302 | if ( result != 0 ) { 2303 | errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; 2304 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2305 | return; 2306 | } 2307 | free (data->buffer); 2308 | data->buffer = (unsigned char *) malloc( data->bufferSize ); 2309 | if ( data->buffer == NULL ) { 2310 | errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; 2311 | error( RtMidiError::MEMORY_ERROR, errorString_ ); 2312 | return; 2313 | } 2314 | } 2315 | 2316 | snd_seq_event_t ev; 2317 | snd_seq_ev_clear( &ev ); 2318 | snd_seq_ev_set_source( &ev, data->vport ); 2319 | snd_seq_ev_set_subs( &ev ); 2320 | snd_seq_ev_set_direct( &ev ); 2321 | for ( unsigned int i=0; ibuffer[i] = message[i]; 2322 | result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); 2323 | if ( result < (int)nBytes ) { 2324 | errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; 2325 | error( RtMidiError::WARNING, errorString_ ); 2326 | return; 2327 | } 2328 | 2329 | // Send the event. 2330 | result = snd_seq_event_output( data->seq, &ev ); 2331 | if ( result < 0 ) { 2332 | errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; 2333 | error( RtMidiError::WARNING, errorString_ ); 2334 | return; 2335 | } 2336 | snd_seq_drain_output( data->seq ); 2337 | } 2338 | 2339 | #endif // __LINUX_ALSA__ 2340 | 2341 | 2342 | //*********************************************************************// 2343 | // API: Windows Multimedia Library (MM) 2344 | //*********************************************************************// 2345 | 2346 | // API information deciphered from: 2347 | // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp 2348 | 2349 | // Thanks to Jean-Baptiste Berruchon for the sysex code. 2350 | 2351 | #if defined(__WINDOWS_MM__) 2352 | 2353 | // The Windows MM API is based on the use of a callback function for 2354 | // MIDI input. We convert the system specific time stamps to delta 2355 | // time values. 2356 | 2357 | // Windows MM MIDI header files. 2358 | #include 2359 | #include 2360 | 2361 | // Convert a null-terminated wide string or ANSI-encoded string to UTF-8. 2362 | static std::string ConvertToUTF8(const TCHAR *str) 2363 | { 2364 | std::string u8str; 2365 | const WCHAR *wstr = L""; 2366 | #if defined( UNICODE ) || defined( _UNICODE ) 2367 | wstr = str; 2368 | #else 2369 | // Convert from ANSI encoding to wide string 2370 | int wlength = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); 2371 | std::wstring wstrtemp; 2372 | if ( wlength ) 2373 | { 2374 | wstrtemp.assign( wlength - 1, 0 ); 2375 | MultiByteToWideChar( CP_ACP, 0, str, -1, &wstrtemp[0], wlength ); 2376 | wstr = &wstrtemp[0]; 2377 | } 2378 | #endif 2379 | // Convert from wide string to UTF-8 2380 | int length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL ); 2381 | if ( length ) 2382 | { 2383 | u8str.assign( length - 1, 0 ); 2384 | length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL ); 2385 | } 2386 | return u8str; 2387 | } 2388 | 2389 | #define RT_SYSEX_BUFFER_SIZE 1024 2390 | #define RT_SYSEX_BUFFER_COUNT 4 2391 | 2392 | // A structure to hold variables related to the CoreMIDI API 2393 | // implementation. 2394 | struct WinMidiData { 2395 | HMIDIIN inHandle; // Handle to Midi Input Device 2396 | HMIDIOUT outHandle; // Handle to Midi Output Device 2397 | DWORD lastTime; 2398 | MidiInApi::MidiMessage message; 2399 | LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; 2400 | CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo 2401 | }; 2402 | 2403 | //*********************************************************************// 2404 | // API: Windows MM 2405 | // Class Definitions: MidiInWinMM 2406 | //*********************************************************************// 2407 | 2408 | static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, 2409 | UINT inputStatus, 2410 | DWORD_PTR instancePtr, 2411 | DWORD_PTR midiMessage, 2412 | DWORD timestamp ) 2413 | { 2414 | if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; 2415 | 2416 | //MidiInApi::RtMidiInData *data = static_cast (instancePtr); 2417 | MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; 2418 | WinMidiData *apiData = static_cast (data->apiData); 2419 | 2420 | // Calculate time stamp. 2421 | if ( data->firstMessage == true ) { 2422 | apiData->message.timeStamp = 0.0; 2423 | data->firstMessage = false; 2424 | } 2425 | else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; 2426 | 2427 | if ( inputStatus == MIM_DATA ) { // Channel or system message 2428 | 2429 | // Make sure the first byte is a status byte. 2430 | unsigned char status = (unsigned char) (midiMessage & 0x000000FF); 2431 | if ( !(status & 0x80) ) return; 2432 | 2433 | // Determine the number of bytes in the MIDI message. 2434 | unsigned short nBytes = 1; 2435 | if ( status < 0xC0 ) nBytes = 3; 2436 | else if ( status < 0xE0 ) nBytes = 2; 2437 | else if ( status < 0xF0 ) nBytes = 3; 2438 | else if ( status == 0xF1 ) { 2439 | if ( data->ignoreFlags & 0x02 ) return; 2440 | else nBytes = 2; 2441 | } 2442 | else if ( status == 0xF2 ) nBytes = 3; 2443 | else if ( status == 0xF3 ) nBytes = 2; 2444 | else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { 2445 | // A MIDI timing tick message and we're ignoring it. 2446 | return; 2447 | } 2448 | else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { 2449 | // A MIDI active sensing message and we're ignoring it. 2450 | return; 2451 | } 2452 | 2453 | // Copy bytes to our MIDI message. 2454 | unsigned char *ptr = (unsigned char *) &midiMessage; 2455 | for ( int i=0; imessage.bytes.push_back( *ptr++ ); 2456 | } 2457 | else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) 2458 | MIDIHDR *sysex = ( MIDIHDR *) midiMessage; 2459 | if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { 2460 | // Sysex message and we're not ignoring it 2461 | for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) 2462 | apiData->message.bytes.push_back( sysex->lpData[i] ); 2463 | } 2464 | 2465 | // The WinMM API requires that the sysex buffer be requeued after 2466 | // input of each sysex message. Even if we are ignoring sysex 2467 | // messages, we still need to requeue the buffer in case the user 2468 | // decides to not ignore sysex messages in the future. However, 2469 | // it seems that WinMM calls this function with an empty sysex 2470 | // buffer when an application closes and in this case, we should 2471 | // avoid requeueing it, else the computer suddenly reboots after 2472 | // one or two minutes. 2473 | if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { 2474 | //if ( sysex->dwBytesRecorded > 0 ) { 2475 | EnterCriticalSection( &(apiData->_mutex) ); 2476 | MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); 2477 | LeaveCriticalSection( &(apiData->_mutex) ); 2478 | if ( result != MMSYSERR_NOERROR ) 2479 | std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; 2480 | 2481 | if ( data->ignoreFlags & 0x01 ) return; 2482 | } 2483 | else return; 2484 | } 2485 | 2486 | // Save the time of the last non-filtered message 2487 | apiData->lastTime = timestamp; 2488 | 2489 | if ( data->usingCallback ) { 2490 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 2491 | callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); 2492 | } 2493 | else { 2494 | // As long as we haven't reached our queue size limit, push the message. 2495 | if ( !data->queue.push( apiData->message ) ) 2496 | std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n"; 2497 | } 2498 | 2499 | // Clear the vector for the next input message. 2500 | apiData->message.bytes.clear(); 2501 | } 2502 | 2503 | MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ) 2504 | : MidiInApi( queueSizeLimit ) 2505 | { 2506 | MidiInWinMM::initialize( clientName ); 2507 | } 2508 | 2509 | MidiInWinMM :: ~MidiInWinMM() 2510 | { 2511 | // Close a connection if it exists. 2512 | MidiInWinMM::closePort(); 2513 | 2514 | WinMidiData *data = static_cast (apiData_); 2515 | DeleteCriticalSection( &(data->_mutex) ); 2516 | 2517 | // Cleanup. 2518 | delete data; 2519 | } 2520 | 2521 | void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) 2522 | { 2523 | // We'll issue a warning here if no devices are available but not 2524 | // throw an error since the user can plugin something later. 2525 | unsigned int nDevices = midiInGetNumDevs(); 2526 | if ( nDevices == 0 ) { 2527 | errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; 2528 | error( RtMidiError::WARNING, errorString_ ); 2529 | } 2530 | 2531 | // Save our api-specific connection information. 2532 | WinMidiData *data = (WinMidiData *) new WinMidiData; 2533 | apiData_ = (void *) data; 2534 | inputData_.apiData = (void *) data; 2535 | data->message.bytes.clear(); // needs to be empty for first input message 2536 | 2537 | if ( !InitializeCriticalSectionAndSpinCount( &(data->_mutex), 0x00000400 ) ) { 2538 | errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; 2539 | error( RtMidiError::WARNING, errorString_ ); 2540 | } 2541 | } 2542 | 2543 | void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) 2544 | { 2545 | if ( connected_ ) { 2546 | errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; 2547 | error( RtMidiError::WARNING, errorString_ ); 2548 | return; 2549 | } 2550 | 2551 | unsigned int nDevices = midiInGetNumDevs(); 2552 | if (nDevices == 0) { 2553 | errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; 2554 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2555 | return; 2556 | } 2557 | 2558 | if ( portNumber >= nDevices ) { 2559 | std::ostringstream ost; 2560 | ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2561 | errorString_ = ost.str(); 2562 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2563 | return; 2564 | } 2565 | 2566 | WinMidiData *data = static_cast (apiData_); 2567 | MMRESULT result = midiInOpen( &data->inHandle, 2568 | portNumber, 2569 | (DWORD_PTR)&midiInputCallback, 2570 | (DWORD_PTR)&inputData_, 2571 | CALLBACK_FUNCTION ); 2572 | if ( result != MMSYSERR_NOERROR ) { 2573 | errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; 2574 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2575 | return; 2576 | } 2577 | 2578 | // Allocate and init the sysex buffers. 2579 | for ( int i=0; isysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; 2581 | data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ]; 2582 | data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; 2583 | data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator 2584 | data->sysexBuffer[i]->dwFlags = 0; 2585 | 2586 | result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); 2587 | if ( result != MMSYSERR_NOERROR ) { 2588 | midiInClose( data->inHandle ); 2589 | data->inHandle = 0; 2590 | errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; 2591 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2592 | return; 2593 | } 2594 | 2595 | // Register the buffer. 2596 | result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); 2597 | if ( result != MMSYSERR_NOERROR ) { 2598 | midiInClose( data->inHandle ); 2599 | data->inHandle = 0; 2600 | errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; 2601 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2602 | return; 2603 | } 2604 | } 2605 | 2606 | result = midiInStart( data->inHandle ); 2607 | if ( result != MMSYSERR_NOERROR ) { 2608 | midiInClose( data->inHandle ); 2609 | data->inHandle = 0; 2610 | errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; 2611 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2612 | return; 2613 | } 2614 | 2615 | connected_ = true; 2616 | } 2617 | 2618 | void MidiInWinMM :: openVirtualPort( const std::string &/*portName*/ ) 2619 | { 2620 | // This function cannot be implemented for the Windows MM MIDI API. 2621 | errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; 2622 | error( RtMidiError::WARNING, errorString_ ); 2623 | } 2624 | 2625 | void MidiInWinMM :: closePort( void ) 2626 | { 2627 | if ( connected_ ) { 2628 | WinMidiData *data = static_cast (apiData_); 2629 | EnterCriticalSection( &(data->_mutex) ); 2630 | midiInReset( data->inHandle ); 2631 | midiInStop( data->inHandle ); 2632 | 2633 | for ( int i=0; iinHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); 2635 | delete [] data->sysexBuffer[i]->lpData; 2636 | delete [] data->sysexBuffer[i]; 2637 | if ( result != MMSYSERR_NOERROR ) { 2638 | midiInClose( data->inHandle ); 2639 | data->inHandle = 0; 2640 | errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; 2641 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2642 | return; 2643 | } 2644 | } 2645 | 2646 | midiInClose( data->inHandle ); 2647 | data->inHandle = 0; 2648 | connected_ = false; 2649 | LeaveCriticalSection( &(data->_mutex) ); 2650 | } 2651 | } 2652 | 2653 | void MidiInWinMM :: setClientName ( const std::string& ) 2654 | { 2655 | 2656 | errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; 2657 | error( RtMidiError::WARNING, errorString_ ); 2658 | 2659 | } 2660 | 2661 | void MidiInWinMM :: setPortName ( const std::string& ) 2662 | { 2663 | 2664 | errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; 2665 | error( RtMidiError::WARNING, errorString_ ); 2666 | 2667 | } 2668 | 2669 | unsigned int MidiInWinMM :: getPortCount() 2670 | { 2671 | return midiInGetNumDevs(); 2672 | } 2673 | 2674 | std::string MidiInWinMM :: getPortName( unsigned int portNumber ) 2675 | { 2676 | std::string stringName; 2677 | unsigned int nDevices = midiInGetNumDevs(); 2678 | if ( portNumber >= nDevices ) { 2679 | std::ostringstream ost; 2680 | ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2681 | errorString_ = ost.str(); 2682 | error( RtMidiError::WARNING, errorString_ ); 2683 | return stringName; 2684 | } 2685 | 2686 | MIDIINCAPS deviceCaps; 2687 | midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); 2688 | stringName = ConvertToUTF8( deviceCaps.szPname ); 2689 | 2690 | // Next lines added to add the portNumber to the name so that 2691 | // the device's names are sure to be listed with individual names 2692 | // even when they have the same brand name 2693 | #ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES 2694 | std::ostringstream os; 2695 | os << " "; 2696 | os << portNumber; 2697 | stringName += os.str(); 2698 | #endif 2699 | 2700 | return stringName; 2701 | } 2702 | 2703 | //*********************************************************************// 2704 | // API: Windows MM 2705 | // Class Definitions: MidiOutWinMM 2706 | //*********************************************************************// 2707 | 2708 | MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi() 2709 | { 2710 | MidiOutWinMM::initialize( clientName ); 2711 | } 2712 | 2713 | MidiOutWinMM :: ~MidiOutWinMM() 2714 | { 2715 | // Close a connection if it exists. 2716 | MidiOutWinMM::closePort(); 2717 | 2718 | // Cleanup. 2719 | WinMidiData *data = static_cast (apiData_); 2720 | delete data; 2721 | } 2722 | 2723 | void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) 2724 | { 2725 | // We'll issue a warning here if no devices are available but not 2726 | // throw an error since the user can plug something in later. 2727 | unsigned int nDevices = midiOutGetNumDevs(); 2728 | if ( nDevices == 0 ) { 2729 | errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; 2730 | error( RtMidiError::WARNING, errorString_ ); 2731 | } 2732 | 2733 | // Save our api-specific connection information. 2734 | WinMidiData *data = (WinMidiData *) new WinMidiData; 2735 | apiData_ = (void *) data; 2736 | } 2737 | 2738 | unsigned int MidiOutWinMM :: getPortCount() 2739 | { 2740 | return midiOutGetNumDevs(); 2741 | } 2742 | 2743 | std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) 2744 | { 2745 | std::string stringName; 2746 | unsigned int nDevices = midiOutGetNumDevs(); 2747 | if ( portNumber >= nDevices ) { 2748 | std::ostringstream ost; 2749 | ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2750 | errorString_ = ost.str(); 2751 | error( RtMidiError::WARNING, errorString_ ); 2752 | return stringName; 2753 | } 2754 | 2755 | MIDIOUTCAPS deviceCaps; 2756 | midiOutGetDevCaps( portNumber, &deviceCaps, sizeof( MIDIOUTCAPS ) ); 2757 | stringName = ConvertToUTF8( deviceCaps.szPname ); 2758 | 2759 | // Next lines added to add the portNumber to the name so that 2760 | // the device's names are sure to be listed with individual names 2761 | // even when they have the same brand name 2762 | std::ostringstream os; 2763 | #ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES 2764 | os << " "; 2765 | os << portNumber; 2766 | stringName += os.str(); 2767 | #endif 2768 | 2769 | return stringName; 2770 | } 2771 | 2772 | void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) 2773 | { 2774 | if ( connected_ ) { 2775 | errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; 2776 | error( RtMidiError::WARNING, errorString_ ); 2777 | return; 2778 | } 2779 | 2780 | unsigned int nDevices = midiOutGetNumDevs(); 2781 | if ( nDevices < 1 ) { 2782 | errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; 2783 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2784 | return; 2785 | } 2786 | 2787 | if ( portNumber >= nDevices ) { 2788 | std::ostringstream ost; 2789 | ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2790 | errorString_ = ost.str(); 2791 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2792 | return; 2793 | } 2794 | 2795 | WinMidiData *data = static_cast (apiData_); 2796 | MMRESULT result = midiOutOpen( &data->outHandle, 2797 | portNumber, 2798 | (DWORD)NULL, 2799 | (DWORD)NULL, 2800 | CALLBACK_NULL ); 2801 | if ( result != MMSYSERR_NOERROR ) { 2802 | errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; 2803 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2804 | return; 2805 | } 2806 | 2807 | connected_ = true; 2808 | } 2809 | 2810 | void MidiOutWinMM :: closePort( void ) 2811 | { 2812 | if ( connected_ ) { 2813 | WinMidiData *data = static_cast (apiData_); 2814 | midiOutReset( data->outHandle ); 2815 | midiOutClose( data->outHandle ); 2816 | data->outHandle = 0; 2817 | connected_ = false; 2818 | } 2819 | } 2820 | 2821 | void MidiOutWinMM :: setClientName ( const std::string& ) 2822 | { 2823 | 2824 | errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; 2825 | error( RtMidiError::WARNING, errorString_ ); 2826 | 2827 | } 2828 | 2829 | void MidiOutWinMM :: setPortName ( const std::string& ) 2830 | { 2831 | 2832 | errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; 2833 | error( RtMidiError::WARNING, errorString_ ); 2834 | 2835 | } 2836 | 2837 | void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ ) 2838 | { 2839 | // This function cannot be implemented for the Windows MM MIDI API. 2840 | errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; 2841 | error( RtMidiError::WARNING, errorString_ ); 2842 | } 2843 | 2844 | void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) 2845 | { 2846 | if ( !connected_ ) return; 2847 | 2848 | unsigned int nBytes = static_cast(size); 2849 | if ( nBytes == 0 ) { 2850 | errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; 2851 | error( RtMidiError::WARNING, errorString_ ); 2852 | return; 2853 | } 2854 | 2855 | MMRESULT result; 2856 | WinMidiData *data = static_cast (apiData_); 2857 | if ( message[0] == 0xF0 ) { // Sysex message 2858 | 2859 | // Allocate buffer for sysex data. 2860 | char *buffer = (char *) malloc( nBytes ); 2861 | if ( buffer == NULL ) { 2862 | errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; 2863 | error( RtMidiError::MEMORY_ERROR, errorString_ ); 2864 | return; 2865 | } 2866 | 2867 | // Copy data to buffer. 2868 | for ( unsigned int i=0; ioutHandle, &sysex, sizeof( MIDIHDR ) ); 2876 | if ( result != MMSYSERR_NOERROR ) { 2877 | free( buffer ); 2878 | errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; 2879 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2880 | return; 2881 | } 2882 | 2883 | // Send the message. 2884 | result = midiOutLongMsg( data->outHandle, &sysex, sizeof( MIDIHDR ) ); 2885 | if ( result != MMSYSERR_NOERROR ) { 2886 | free( buffer ); 2887 | errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; 2888 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2889 | return; 2890 | } 2891 | 2892 | // Unprepare the buffer and MIDIHDR. 2893 | while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof ( MIDIHDR ) ) ) Sleep( 1 ); 2894 | free( buffer ); 2895 | } 2896 | else { // Channel or system message. 2897 | 2898 | // Make sure the message size isn't too big. 2899 | if ( nBytes > 3 ) { 2900 | errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; 2901 | error( RtMidiError::WARNING, errorString_ ); 2902 | return; 2903 | } 2904 | 2905 | // Pack MIDI bytes into double word. 2906 | DWORD packet; 2907 | unsigned char *ptr = (unsigned char *) &packet; 2908 | for ( unsigned int i=0; ioutHandle, packet ); 2915 | if ( result != MMSYSERR_NOERROR ) { 2916 | errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; 2917 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2918 | } 2919 | } 2920 | } 2921 | 2922 | #endif // __WINDOWS_MM__ 2923 | 2924 | 2925 | //*********************************************************************// 2926 | // API: UNIX JACK 2927 | // 2928 | // Written primarily by Alexander Svetalkin, with updates for delta 2929 | // time by Gary Scavone, April 2011. 2930 | // 2931 | // *********************************************************************// 2932 | 2933 | #if defined(__UNIX_JACK__) 2934 | 2935 | // JACK header files 2936 | #include 2937 | #include 2938 | #include 2939 | #ifdef HAVE_SEMAPHORE 2940 | #include 2941 | #endif 2942 | 2943 | #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer 2944 | 2945 | struct JackMidiData { 2946 | jack_client_t *client; 2947 | jack_port_t *port; 2948 | jack_ringbuffer_t *buffSize; 2949 | jack_ringbuffer_t *buffMessage; 2950 | jack_time_t lastTime; 2951 | #ifdef HAVE_SEMAPHORE 2952 | sem_t sem_cleanup; 2953 | sem_t sem_needpost; 2954 | #endif 2955 | MidiInApi :: RtMidiInData *rtMidiIn; 2956 | }; 2957 | 2958 | //*********************************************************************// 2959 | // API: JACK 2960 | // Class Definitions: MidiInJack 2961 | //*********************************************************************// 2962 | 2963 | static int jackProcessIn( jack_nframes_t nframes, void *arg ) 2964 | { 2965 | JackMidiData *jData = (JackMidiData *) arg; 2966 | MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; 2967 | jack_midi_event_t event; 2968 | jack_time_t time; 2969 | 2970 | // Is port created? 2971 | if ( jData->port == NULL ) return 0; 2972 | 2973 | void *buff = jack_port_get_buffer( jData->port, nframes ); 2974 | bool& continueSysex = rtData->continueSysex; 2975 | unsigned char& ignoreFlags = rtData->ignoreFlags; 2976 | 2977 | // We have midi events in buffer 2978 | int evCount = jack_midi_get_event_count( buff ); 2979 | for (int j = 0; j < evCount; j++) { 2980 | MidiInApi::MidiMessage& message = rtData->message; 2981 | jack_midi_event_get( &event, buff, j ); 2982 | 2983 | // Compute the delta time. 2984 | time = jack_get_time(); 2985 | if ( rtData->firstMessage == true ) { 2986 | message.timeStamp = 0.0; 2987 | rtData->firstMessage = false; 2988 | } else 2989 | message.timeStamp = ( time - jData->lastTime ) * 0.000001; 2990 | 2991 | jData->lastTime = time; 2992 | 2993 | if ( !continueSysex ) 2994 | message.bytes.clear(); 2995 | 2996 | if ( !( ( continueSysex || event.buffer[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) { 2997 | // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx, 2998 | // copy the event buffer into the MIDI message struct. 2999 | for ( unsigned int i = 0; i < event.size; i++ ) 3000 | message.bytes.push_back( event.buffer[i] ); 3001 | } 3002 | 3003 | switch ( event.buffer[0] ) { 3004 | case 0xF0: 3005 | // Start of a SysEx message 3006 | continueSysex = event.buffer[event.size - 1] != 0xF7; 3007 | if ( ignoreFlags & 0x01 ) continue; 3008 | break; 3009 | case 0xF1: 3010 | case 0xF8: 3011 | // MIDI Time Code or Timing Clock message 3012 | if ( ignoreFlags & 0x02 ) continue; 3013 | break; 3014 | case 0xFE: 3015 | // Active Sensing message 3016 | if ( ignoreFlags & 0x04 ) continue; 3017 | break; 3018 | default: 3019 | if ( continueSysex ) { 3020 | // Continuation of a SysEx message 3021 | continueSysex = event.buffer[event.size - 1] != 0xF7; 3022 | if ( ignoreFlags & 0x01 ) continue; 3023 | } 3024 | // All other MIDI messages 3025 | } 3026 | 3027 | if ( !continueSysex ) { 3028 | // If not a continuation of a SysEx message, 3029 | // invoke the user callback function or queue the message. 3030 | if ( rtData->usingCallback ) { 3031 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; 3032 | callback( message.timeStamp, &message.bytes, rtData->userData ); 3033 | } 3034 | else { 3035 | // As long as we haven't reached our queue size limit, push the message. 3036 | if ( !rtData->queue.push( message ) ) 3037 | std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; 3038 | } 3039 | } 3040 | } 3041 | 3042 | return 0; 3043 | } 3044 | 3045 | MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ) 3046 | : MidiInApi( queueSizeLimit ) 3047 | { 3048 | MidiInJack::initialize( clientName ); 3049 | } 3050 | 3051 | void MidiInJack :: initialize( const std::string& clientName ) 3052 | { 3053 | JackMidiData *data = new JackMidiData; 3054 | apiData_ = (void *) data; 3055 | 3056 | data->rtMidiIn = &inputData_; 3057 | data->port = NULL; 3058 | data->client = NULL; 3059 | this->clientName = clientName; 3060 | 3061 | connect(); 3062 | } 3063 | 3064 | void MidiInJack :: connect() 3065 | { 3066 | JackMidiData *data = static_cast (apiData_); 3067 | if ( data->client ) 3068 | return; 3069 | 3070 | // Initialize JACK client 3071 | if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { 3072 | errorString_ = "MidiInJack::initialize: JACK server not running?"; 3073 | error( RtMidiError::WARNING, errorString_ ); 3074 | return; 3075 | } 3076 | 3077 | jack_set_process_callback( data->client, jackProcessIn, data ); 3078 | jack_activate( data->client ); 3079 | } 3080 | 3081 | MidiInJack :: ~MidiInJack() 3082 | { 3083 | JackMidiData *data = static_cast (apiData_); 3084 | MidiInJack::closePort(); 3085 | 3086 | if ( data->client ) 3087 | jack_client_close( data->client ); 3088 | delete data; 3089 | } 3090 | 3091 | void MidiInJack :: openPort( unsigned int portNumber, const std::string &portName ) 3092 | { 3093 | JackMidiData *data = static_cast (apiData_); 3094 | 3095 | connect(); 3096 | 3097 | // Creating new port 3098 | if ( data->port == NULL ) 3099 | data->port = jack_port_register( data->client, portName.c_str(), 3100 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); 3101 | 3102 | if ( data->port == NULL ) { 3103 | errorString_ = "MidiInJack::openPort: JACK error creating port"; 3104 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 3105 | return; 3106 | } 3107 | 3108 | // Connecting to the output 3109 | std::string name = getPortName( portNumber ); 3110 | jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); 3111 | 3112 | connected_ = true; 3113 | } 3114 | 3115 | void MidiInJack :: openVirtualPort( const std::string &portName ) 3116 | { 3117 | JackMidiData *data = static_cast (apiData_); 3118 | 3119 | connect(); 3120 | if ( data->port == NULL ) 3121 | data->port = jack_port_register( data->client, portName.c_str(), 3122 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); 3123 | 3124 | if ( data->port == NULL ) { 3125 | errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; 3126 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 3127 | } 3128 | } 3129 | 3130 | unsigned int MidiInJack :: getPortCount() 3131 | { 3132 | int count = 0; 3133 | JackMidiData *data = static_cast (apiData_); 3134 | connect(); 3135 | if ( !data->client ) 3136 | return 0; 3137 | 3138 | // List of available ports 3139 | const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); 3140 | 3141 | if ( ports == NULL ) return 0; 3142 | while ( ports[count] != NULL ) 3143 | count++; 3144 | 3145 | free( ports ); 3146 | 3147 | return count; 3148 | } 3149 | 3150 | std::string MidiInJack :: getPortName( unsigned int portNumber ) 3151 | { 3152 | JackMidiData *data = static_cast (apiData_); 3153 | std::string retStr( "" ); 3154 | 3155 | connect(); 3156 | 3157 | // List of available ports 3158 | const char **ports = jack_get_ports( data->client, NULL, 3159 | JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); 3160 | 3161 | // Check port validity 3162 | if ( ports == NULL ) { 3163 | errorString_ = "MidiInJack::getPortName: no ports available!"; 3164 | error( RtMidiError::WARNING, errorString_ ); 3165 | return retStr; 3166 | } 3167 | 3168 | unsigned int i; 3169 | for ( i=0; i (apiData_); 3185 | 3186 | if ( data->port == NULL ) return; 3187 | jack_port_unregister( data->client, data->port ); 3188 | data->port = NULL; 3189 | 3190 | connected_ = false; 3191 | } 3192 | 3193 | void MidiInJack:: setClientName( const std::string& ) 3194 | { 3195 | 3196 | errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!"; 3197 | error( RtMidiError::WARNING, errorString_ ); 3198 | 3199 | } 3200 | 3201 | void MidiInJack :: setPortName( const std::string &portName ) 3202 | { 3203 | JackMidiData *data = static_cast (apiData_); 3204 | #ifdef JACK_HAS_PORT_RENAME 3205 | jack_port_rename( data->client, data->port, portName.c_str() ); 3206 | #else 3207 | jack_port_set_name( data->port, portName.c_str() ); 3208 | #endif 3209 | } 3210 | 3211 | //*********************************************************************// 3212 | // API: JACK 3213 | // Class Definitions: MidiOutJack 3214 | //*********************************************************************// 3215 | 3216 | // Jack process callback 3217 | static int jackProcessOut( jack_nframes_t nframes, void *arg ) 3218 | { 3219 | JackMidiData *data = (JackMidiData *) arg; 3220 | jack_midi_data_t *midiData; 3221 | int space; 3222 | 3223 | // Is port created? 3224 | if ( data->port == NULL ) return 0; 3225 | 3226 | void *buff = jack_port_get_buffer( data->port, nframes ); 3227 | jack_midi_clear_buffer( buff ); 3228 | 3229 | while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { 3230 | jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof( space ) ); 3231 | midiData = jack_midi_event_reserve( buff, 0, space ); 3232 | 3233 | jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); 3234 | } 3235 | 3236 | #ifdef HAVE_SEMAPHORE 3237 | if ( !sem_trywait( &data->sem_needpost ) ) 3238 | sem_post( &data->sem_cleanup ); 3239 | #endif 3240 | 3241 | return 0; 3242 | } 3243 | 3244 | MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi() 3245 | { 3246 | MidiOutJack::initialize( clientName ); 3247 | } 3248 | 3249 | void MidiOutJack :: initialize( const std::string& clientName ) 3250 | { 3251 | JackMidiData *data = new JackMidiData; 3252 | apiData_ = (void *) data; 3253 | 3254 | data->port = NULL; 3255 | data->client = NULL; 3256 | #ifdef HAVE_SEMAPHORE 3257 | sem_init( &data->sem_cleanup, 0, 0 ); 3258 | sem_init( &data->sem_needpost, 0, 0 ); 3259 | #endif 3260 | this->clientName = clientName; 3261 | 3262 | connect(); 3263 | } 3264 | 3265 | void MidiOutJack :: connect() 3266 | { 3267 | JackMidiData *data = static_cast (apiData_); 3268 | if ( data->client ) 3269 | return; 3270 | 3271 | // Initialize output ringbuffers 3272 | data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); 3273 | data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); 3274 | 3275 | // Initialize JACK client 3276 | if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) { 3277 | errorString_ = "MidiOutJack::initialize: JACK server not running?"; 3278 | error( RtMidiError::WARNING, errorString_ ); 3279 | return; 3280 | } 3281 | 3282 | jack_set_process_callback( data->client, jackProcessOut, data ); 3283 | jack_activate( data->client ); 3284 | } 3285 | 3286 | MidiOutJack :: ~MidiOutJack() 3287 | { 3288 | JackMidiData *data = static_cast (apiData_); 3289 | MidiOutJack::closePort(); 3290 | 3291 | // Cleanup 3292 | jack_ringbuffer_free( data->buffSize ); 3293 | jack_ringbuffer_free( data->buffMessage ); 3294 | if ( data->client ) { 3295 | jack_client_close( data->client ); 3296 | } 3297 | 3298 | #ifdef HAVE_SEMAPHORE 3299 | sem_destroy( &data->sem_cleanup ); 3300 | sem_destroy( &data->sem_needpost ); 3301 | #endif 3302 | 3303 | delete data; 3304 | } 3305 | 3306 | void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portName ) 3307 | { 3308 | JackMidiData *data = static_cast (apiData_); 3309 | 3310 | connect(); 3311 | 3312 | // Creating new port 3313 | if ( data->port == NULL ) 3314 | data->port = jack_port_register( data->client, portName.c_str(), 3315 | JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); 3316 | 3317 | if ( data->port == NULL ) { 3318 | errorString_ = "MidiOutJack::openPort: JACK error creating port"; 3319 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 3320 | return; 3321 | } 3322 | 3323 | // Connecting to the output 3324 | std::string name = getPortName( portNumber ); 3325 | jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); 3326 | 3327 | connected_ = true; 3328 | } 3329 | 3330 | void MidiOutJack :: openVirtualPort( const std::string &portName ) 3331 | { 3332 | JackMidiData *data = static_cast (apiData_); 3333 | 3334 | connect(); 3335 | if ( data->port == NULL ) 3336 | data->port = jack_port_register( data->client, portName.c_str(), 3337 | JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); 3338 | 3339 | if ( data->port == NULL ) { 3340 | errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; 3341 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 3342 | } 3343 | } 3344 | 3345 | unsigned int MidiOutJack :: getPortCount() 3346 | { 3347 | int count = 0; 3348 | JackMidiData *data = static_cast (apiData_); 3349 | connect(); 3350 | if ( !data->client ) 3351 | return 0; 3352 | 3353 | // List of available ports 3354 | const char **ports = jack_get_ports( data->client, NULL, 3355 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); 3356 | 3357 | if ( ports == NULL ) return 0; 3358 | while ( ports[count] != NULL ) 3359 | count++; 3360 | 3361 | free( ports ); 3362 | 3363 | return count; 3364 | } 3365 | 3366 | std::string MidiOutJack :: getPortName( unsigned int portNumber ) 3367 | { 3368 | JackMidiData *data = static_cast (apiData_); 3369 | std::string retStr(""); 3370 | 3371 | connect(); 3372 | 3373 | // List of available ports 3374 | const char **ports = jack_get_ports( data->client, NULL, 3375 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); 3376 | 3377 | // Check port validity 3378 | if ( ports == NULL ) { 3379 | errorString_ = "MidiOutJack::getPortName: no ports available!"; 3380 | error( RtMidiError::WARNING, errorString_ ); 3381 | return retStr; 3382 | } 3383 | 3384 | if ( ports[portNumber] == NULL ) { 3385 | std::ostringstream ost; 3386 | ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 3387 | errorString_ = ost.str(); 3388 | error( RtMidiError::WARNING, errorString_ ); 3389 | } 3390 | else retStr.assign( ports[portNumber] ); 3391 | 3392 | free( ports ); 3393 | return retStr; 3394 | } 3395 | 3396 | void MidiOutJack :: closePort() 3397 | { 3398 | JackMidiData *data = static_cast (apiData_); 3399 | 3400 | if ( data->port == NULL ) return; 3401 | 3402 | #ifdef HAVE_SEMAPHORE 3403 | struct timespec ts; 3404 | if ( clock_gettime( CLOCK_REALTIME, &ts ) != -1 ) { 3405 | ts.tv_sec += 1; // wait max one second 3406 | sem_post( &data->sem_needpost ); 3407 | sem_timedwait( &data->sem_cleanup, &ts ); 3408 | } 3409 | #endif 3410 | 3411 | jack_port_unregister( data->client, data->port ); 3412 | data->port = NULL; 3413 | 3414 | connected_ = false; 3415 | } 3416 | 3417 | void MidiOutJack:: setClientName( const std::string& ) 3418 | { 3419 | 3420 | errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!"; 3421 | error( RtMidiError::WARNING, errorString_ ); 3422 | 3423 | } 3424 | 3425 | void MidiOutJack :: setPortName( const std::string &portName ) 3426 | { 3427 | JackMidiData *data = static_cast (apiData_); 3428 | #ifdef JACK_HAS_PORT_RENAME 3429 | jack_port_rename( data->client, data->port, portName.c_str() ); 3430 | #else 3431 | jack_port_set_name( data->port, portName.c_str() ); 3432 | #endif 3433 | } 3434 | 3435 | void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) 3436 | { 3437 | int nBytes = static_cast(size); 3438 | JackMidiData *data = static_cast (apiData_); 3439 | 3440 | // Write full message to buffer 3441 | jack_ringbuffer_write( data->buffMessage, ( const char * ) message, nBytes ); 3442 | jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); 3443 | } 3444 | 3445 | #endif // __UNIX_JACK__ 3446 | -------------------------------------------------------------------------------- /RtMidi.h: -------------------------------------------------------------------------------- 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-2019 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 | /*! 41 | \file RtMidi.h 42 | */ 43 | 44 | #ifndef RTMIDI_H 45 | #define RTMIDI_H 46 | 47 | #if defined _WIN32 || defined __CYGWIN__ 48 | #if defined(RTMIDI_EXPORT) 49 | #define RTMIDI_DLL_PUBLIC __declspec(dllexport) 50 | #else 51 | #define RTMIDI_DLL_PUBLIC 52 | #endif 53 | #else 54 | #if __GNUC__ >= 4 55 | #define RTMIDI_DLL_PUBLIC __attribute__( (visibility( "default" )) ) 56 | #else 57 | #define RTMIDI_DLL_PUBLIC 58 | #endif 59 | #endif 60 | 61 | #define RTMIDI_VERSION "4.0.0" 62 | 63 | #include 64 | #include 65 | #include 66 | #include 67 | 68 | /************************************************************************/ 69 | /*! \class RtMidiError 70 | \brief Exception handling class for RtMidi. 71 | 72 | The RtMidiError class is quite simple but it does allow errors to be 73 | "caught" by RtMidiError::Type. See the RtMidi documentation to know 74 | which methods can throw an RtMidiError. 75 | */ 76 | /************************************************************************/ 77 | 78 | class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception 79 | { 80 | public: 81 | //! Defined RtMidiError types. 82 | enum Type { 83 | WARNING, /*!< A non-critical error. */ 84 | DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ 85 | UNSPECIFIED, /*!< The default, unspecified error type. */ 86 | NO_DEVICES_FOUND, /*!< No devices found on system. */ 87 | INVALID_DEVICE, /*!< An invalid device ID was specified. */ 88 | MEMORY_ERROR, /*!< An error occured during memory allocation. */ 89 | INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ 90 | INVALID_USE, /*!< The function was called incorrectly. */ 91 | DRIVER_ERROR, /*!< A system driver error occured. */ 92 | SYSTEM_ERROR, /*!< A system error occured. */ 93 | THREAD_ERROR /*!< A thread error occured. */ 94 | }; 95 | 96 | //! The constructor. 97 | RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() 98 | : message_(message), type_(type) {} 99 | 100 | //! The destructor. 101 | virtual ~RtMidiError( void ) throw() {} 102 | 103 | //! Prints thrown error message to stderr. 104 | virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } 105 | 106 | //! Returns the thrown error message type. 107 | virtual const Type& getType( void ) const throw() { return type_; } 108 | 109 | //! Returns the thrown error message string. 110 | virtual const std::string& getMessage( void ) const throw() { return message_; } 111 | 112 | //! Returns the thrown error message as a c-style string. 113 | virtual const char* what( void ) const throw() { return message_.c_str(); } 114 | 115 | protected: 116 | std::string message_; 117 | Type type_; 118 | }; 119 | 120 | //! RtMidi error callback function prototype. 121 | /*! 122 | \param type Type of error. 123 | \param errorText Error description. 124 | 125 | Note that class behaviour is undefined after a critical error (not 126 | a warning) is reported. 127 | */ 128 | typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData ); 129 | 130 | class MidiApi; 131 | 132 | class RTMIDI_DLL_PUBLIC RtMidi 133 | { 134 | public: 135 | //! MIDI API specifier arguments. 136 | enum Api { 137 | UNSPECIFIED, /*!< Search for a working compiled API. */ 138 | MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */ 139 | LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ 140 | UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ 141 | WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ 142 | RTMIDI_DUMMY, /*!< A compilable but non-functional API. */ 143 | NUM_APIS /*!< Number of values in this enum. */ 144 | }; 145 | 146 | //! A static function to determine the current RtMidi version. 147 | static std::string getVersion( void ) throw(); 148 | 149 | //! A static function to determine the available compiled MIDI APIs. 150 | /*! 151 | The values returned in the std::vector can be compared against 152 | the enumerated list values. Note that there can be more than one 153 | API compiled for certain operating systems. 154 | */ 155 | static void getCompiledApi( std::vector &apis ) throw(); 156 | 157 | //! Return the name of a specified compiled MIDI API. 158 | /*! 159 | This obtains a short lower-case name used for identification purposes. 160 | This value is guaranteed to remain identical across library versions. 161 | If the API is unknown, this function will return the empty string. 162 | */ 163 | static std::string getApiName( RtMidi::Api api ); 164 | 165 | //! Return the display name of a specified compiled MIDI API. 166 | /*! 167 | This obtains a long name used for display purposes. 168 | If the API is unknown, this function will return the empty string. 169 | */ 170 | static std::string getApiDisplayName( RtMidi::Api api ); 171 | 172 | //! Return the compiled MIDI API having the given name. 173 | /*! 174 | A case insensitive comparison will check the specified name 175 | against the list of compiled APIs, and return the one which 176 | matches. On failure, the function returns UNSPECIFIED. 177 | */ 178 | static RtMidi::Api getCompiledApiByName( const std::string &name ); 179 | 180 | //! Pure virtual openPort() function. 181 | virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0; 182 | 183 | //! Pure virtual openVirtualPort() function. 184 | virtual void openVirtualPort( const std::string &portName = std::string( "RtMidi" ) ) = 0; 185 | 186 | //! Pure virtual getPortCount() function. 187 | virtual unsigned int getPortCount() = 0; 188 | 189 | //! Pure virtual getPortName() function. 190 | virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; 191 | 192 | //! Pure virtual closePort() function. 193 | virtual void closePort( void ) = 0; 194 | 195 | void setClientName( const std::string &clientName ); 196 | void setPortName( const std::string &portName ); 197 | 198 | //! Returns true if a port is open and false if not. 199 | /*! 200 | Note that this only applies to connections made with the openPort() 201 | function, not to virtual ports. 202 | */ 203 | virtual bool isPortOpen( void ) const = 0; 204 | 205 | //! Set an error callback function to be invoked when an error has occured. 206 | /*! 207 | The callback function will be called whenever an error has occured. It is best 208 | to set the error callback function before opening a port. 209 | */ 210 | virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; 211 | 212 | protected: 213 | RtMidi(); 214 | virtual ~RtMidi(); 215 | MidiApi *rtapi_; 216 | }; 217 | 218 | /**********************************************************************/ 219 | /*! \class RtMidiIn 220 | \brief A realtime MIDI input class. 221 | 222 | This class provides a common, platform-independent API for 223 | realtime MIDI input. It allows access to a single MIDI input 224 | port. Incoming MIDI messages are either saved to a queue for 225 | retrieval using the getMessage() function or immediately passed to 226 | a user-specified callback function. Create multiple instances of 227 | this class to connect to more than one MIDI device at the same 228 | time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also 229 | possible to open a virtual input port to which other MIDI software 230 | clients can connect. 231 | 232 | by Gary P. Scavone, 2003-2017. 233 | */ 234 | /**********************************************************************/ 235 | 236 | // **************************************************************** // 237 | // 238 | // RtMidiIn and RtMidiOut class declarations. 239 | // 240 | // RtMidiIn / RtMidiOut are "controllers" used to select an available 241 | // MIDI input or output interface. They present common APIs for the 242 | // user to call but all functionality is implemented by the classes 243 | // MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut 244 | // each create an instance of a MidiInApi or MidiOutApi subclass based 245 | // on the user's API choice. If no choice is made, they attempt to 246 | // make a "logical" API selection. 247 | // 248 | // **************************************************************** // 249 | 250 | class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi 251 | { 252 | public: 253 | 254 | //! User callback function type definition. 255 | typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData ); 256 | 257 | //! Default constructor that allows an optional api, client name and queue size. 258 | /*! 259 | An exception will be thrown if a MIDI system initialization 260 | error occurs. The queue size defines the maximum number of 261 | messages that can be held in the MIDI queue (when not using a 262 | callback function). If the queue size limit is reached, 263 | incoming messages will be ignored. 264 | 265 | If no API argument is specified and multiple API support has been 266 | compiled, the default order of use is ALSA, JACK (Linux) and CORE, 267 | JACK (OS-X). 268 | 269 | \param api An optional API id can be specified. 270 | \param clientName An optional client name can be specified. This 271 | will be used to group the ports that are created 272 | by the application. 273 | \param queueSizeLimit An optional size of the MIDI input queue can be specified. 274 | */ 275 | RtMidiIn( RtMidi::Api api=UNSPECIFIED, 276 | const std::string& clientName = "RtMidi Input Client", 277 | unsigned int queueSizeLimit = 100 ); 278 | 279 | //! If a MIDI connection is still open, it will be closed by the destructor. 280 | ~RtMidiIn ( void ) throw(); 281 | 282 | //! Returns the MIDI API specifier for the current instance of RtMidiIn. 283 | RtMidi::Api getCurrentApi( void ) throw(); 284 | 285 | //! Open a MIDI input connection given by enumeration number. 286 | /*! 287 | \param portNumber An optional port number greater than 0 can be specified. 288 | Otherwise, the default or first port found is opened. 289 | \param portName An optional name for the application port that is used to connect to portId can be specified. 290 | */ 291 | void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Input" ) ); 292 | 293 | //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). 294 | /*! 295 | This function creates a virtual MIDI input port to which other 296 | software applications can connect. This type of functionality 297 | is currently only supported by the Macintosh OS-X, any JACK, 298 | and Linux ALSA APIs (the function returns an error for the other APIs). 299 | 300 | \param portName An optional name for the application port that is 301 | used to connect to portId can be specified. 302 | */ 303 | void openVirtualPort( const std::string &portName = std::string( "RtMidi Input" ) ); 304 | 305 | //! Set a callback function to be invoked for incoming MIDI messages. 306 | /*! 307 | The callback function will be called whenever an incoming MIDI 308 | message is received. While not absolutely necessary, it is best 309 | to set the callback function before opening a MIDI port to avoid 310 | leaving some messages in the queue. 311 | 312 | \param callback A callback function must be given. 313 | \param userData Optionally, a pointer to additional data can be 314 | passed to the callback function whenever it is called. 315 | */ 316 | void setCallback( RtMidiCallback callback, void *userData = 0 ); 317 | 318 | //! Cancel use of the current callback function (if one exists). 319 | /*! 320 | Subsequent incoming MIDI messages will be written to the queue 321 | and can be retrieved with the \e getMessage function. 322 | */ 323 | void cancelCallback(); 324 | 325 | //! Close an open MIDI connection (if one exists). 326 | void closePort( void ); 327 | 328 | //! Returns true if a port is open and false if not. 329 | /*! 330 | Note that this only applies to connections made with the openPort() 331 | function, not to virtual ports. 332 | */ 333 | virtual bool isPortOpen() const; 334 | 335 | //! Return the number of available MIDI input ports. 336 | /*! 337 | \return This function returns the number of MIDI ports of the selected API. 338 | */ 339 | unsigned int getPortCount(); 340 | 341 | //! Return a string identifier for the specified MIDI input port number. 342 | /*! 343 | \return The name of the port with the given Id is returned. 344 | \retval An empty string is returned if an invalid port specifier 345 | is provided. User code should assume a UTF-8 encoding. 346 | */ 347 | std::string getPortName( unsigned int portNumber = 0 ); 348 | 349 | //! Specify whether certain MIDI message types should be queued or ignored during input. 350 | /*! 351 | By default, MIDI timing and active sensing messages are ignored 352 | during message input because of their relative high data rates. 353 | MIDI sysex messages are ignored by default as well. Variable 354 | values of "true" imply that the respective message type will be 355 | ignored. 356 | */ 357 | void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true ); 358 | 359 | //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds. 360 | /*! 361 | This function returns immediately whether a new message is 362 | available or not. A valid message is indicated by a non-zero 363 | vector size. An exception is thrown if an error occurs during 364 | message retrieval or an input connection was not previously 365 | established. 366 | */ 367 | double getMessage( std::vector *message ); 368 | 369 | //! Set an error callback function to be invoked when an error has occured. 370 | /*! 371 | The callback function will be called whenever an error has occured. It is best 372 | to set the error callback function before opening a port. 373 | */ 374 | virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); 375 | 376 | protected: 377 | void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ); 378 | }; 379 | 380 | /**********************************************************************/ 381 | /*! \class RtMidiOut 382 | \brief A realtime MIDI output class. 383 | 384 | This class provides a common, platform-independent API for MIDI 385 | output. It allows one to probe available MIDI output ports, to 386 | connect to one such port, and to send MIDI bytes immediately over 387 | the connection. Create multiple instances of this class to 388 | connect to more than one MIDI device at the same time. With the 389 | OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a 390 | virtual port to which other MIDI software clients can connect. 391 | 392 | by Gary P. Scavone, 2003-2017. 393 | */ 394 | /**********************************************************************/ 395 | 396 | class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi 397 | { 398 | public: 399 | //! Default constructor that allows an optional client name. 400 | /*! 401 | An exception will be thrown if a MIDI system initialization error occurs. 402 | 403 | If no API argument is specified and multiple API support has been 404 | compiled, the default order of use is ALSA, JACK (Linux) and CORE, 405 | JACK (OS-X). 406 | */ 407 | RtMidiOut( RtMidi::Api api=UNSPECIFIED, 408 | const std::string& clientName = "RtMidi Output Client" ); 409 | 410 | //! The destructor closes any open MIDI connections. 411 | ~RtMidiOut( void ) throw(); 412 | 413 | //! Returns the MIDI API specifier for the current instance of RtMidiOut. 414 | RtMidi::Api getCurrentApi( void ) throw(); 415 | 416 | //! Open a MIDI output connection. 417 | /*! 418 | An optional port number greater than 0 can be specified. 419 | Otherwise, the default or first port found is opened. An 420 | exception is thrown if an error occurs while attempting to make 421 | the port connection. 422 | */ 423 | void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Output" ) ); 424 | 425 | //! Close an open MIDI connection (if one exists). 426 | void closePort( void ); 427 | 428 | //! Returns true if a port is open and false if not. 429 | /*! 430 | Note that this only applies to connections made with the openPort() 431 | function, not to virtual ports. 432 | */ 433 | virtual bool isPortOpen() const; 434 | 435 | //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). 436 | /*! 437 | This function creates a virtual MIDI output port to which other 438 | software applications can connect. This type of functionality 439 | is currently only supported by the Macintosh OS-X, Linux ALSA 440 | and JACK APIs (the function does nothing with the other APIs). 441 | An exception is thrown if an error occurs while attempting to 442 | create the virtual port. 443 | */ 444 | void openVirtualPort( const std::string &portName = std::string( "RtMidi Output" ) ); 445 | 446 | //! Return the number of available MIDI output ports. 447 | unsigned int getPortCount( void ); 448 | 449 | //! Return a string identifier for the specified MIDI port type and number. 450 | /*! 451 | \return The name of the port with the given Id is returned. 452 | \retval An empty string is returned if an invalid port specifier 453 | is provided. User code should assume a UTF-8 encoding. 454 | */ 455 | std::string getPortName( unsigned int portNumber = 0 ); 456 | 457 | //! Immediately send a single message out an open MIDI output port. 458 | /*! 459 | An exception is thrown if an error occurs during output or an 460 | output connection was not previously established. 461 | */ 462 | void sendMessage( const std::vector *message ); 463 | 464 | //! Immediately send a single message out an open MIDI output port. 465 | /*! 466 | An exception is thrown if an error occurs during output or an 467 | output connection was not previously established. 468 | 469 | \param message A pointer to the MIDI message as raw bytes 470 | \param size Length of the MIDI message in bytes 471 | */ 472 | void sendMessage( const unsigned char *message, size_t size ); 473 | 474 | //! Set an error callback function to be invoked when an error has occured. 475 | /*! 476 | The callback function will be called whenever an error has occured. It is best 477 | to set the error callback function before opening a port. 478 | */ 479 | virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); 480 | 481 | protected: 482 | void openMidiApi( RtMidi::Api api, const std::string &clientName ); 483 | }; 484 | 485 | 486 | // **************************************************************** // 487 | // 488 | // MidiInApi / MidiOutApi class declarations. 489 | // 490 | // Subclasses of MidiInApi and MidiOutApi contain all API- and 491 | // OS-specific code necessary to fully implement the RtMidi API. 492 | // 493 | // Note that MidiInApi and MidiOutApi are abstract base classes and 494 | // cannot be explicitly instantiated. RtMidiIn and RtMidiOut will 495 | // create instances of a MidiInApi or MidiOutApi subclass. 496 | // 497 | // **************************************************************** // 498 | 499 | class RTMIDI_DLL_PUBLIC MidiApi 500 | { 501 | public: 502 | 503 | MidiApi(); 504 | virtual ~MidiApi(); 505 | virtual RtMidi::Api getCurrentApi( void ) = 0; 506 | virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0; 507 | virtual void openVirtualPort( const std::string &portName ) = 0; 508 | virtual void closePort( void ) = 0; 509 | virtual void setClientName( const std::string &clientName ) = 0; 510 | virtual void setPortName( const std::string &portName ) = 0; 511 | 512 | virtual unsigned int getPortCount( void ) = 0; 513 | virtual std::string getPortName( unsigned int portNumber ) = 0; 514 | 515 | inline bool isPortOpen() const { return connected_; } 516 | void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ); 517 | 518 | //! A basic error reporting function for RtMidi classes. 519 | void error( RtMidiError::Type type, std::string errorString ); 520 | 521 | protected: 522 | virtual void initialize( const std::string& clientName ) = 0; 523 | 524 | void *apiData_; 525 | bool connected_; 526 | std::string errorString_; 527 | RtMidiErrorCallback errorCallback_; 528 | bool firstErrorOccurred_; 529 | void *errorCallbackUserData_; 530 | }; 531 | 532 | class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi 533 | { 534 | public: 535 | 536 | MidiInApi( unsigned int queueSizeLimit ); 537 | virtual ~MidiInApi( void ); 538 | void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); 539 | void cancelCallback( void ); 540 | virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); 541 | double getMessage( std::vector *message ); 542 | 543 | // A MIDI structure used internally by the class to store incoming 544 | // messages. Each message represents one and only one MIDI message. 545 | struct MidiMessage { 546 | std::vector bytes; 547 | 548 | //! Time in seconds elapsed since the previous message 549 | double timeStamp; 550 | 551 | // Default constructor. 552 | MidiMessage() 553 | : bytes(0), timeStamp(0.0) {} 554 | }; 555 | 556 | struct MidiQueue { 557 | unsigned int front; 558 | unsigned int back; 559 | unsigned int ringSize; 560 | MidiMessage *ring; 561 | 562 | // Default constructor. 563 | MidiQueue() 564 | : front(0), back(0), ringSize(0), ring(0) {} 565 | bool push( const MidiMessage& ); 566 | bool pop( std::vector*, double* ); 567 | unsigned int size( unsigned int *back=0, unsigned int *front=0 ); 568 | }; 569 | 570 | // The RtMidiInData structure is used to pass private class data to 571 | // the MIDI input handling function or thread. 572 | struct RtMidiInData { 573 | MidiQueue queue; 574 | MidiMessage message; 575 | unsigned char ignoreFlags; 576 | bool doInput; 577 | bool firstMessage; 578 | void *apiData; 579 | bool usingCallback; 580 | RtMidiIn::RtMidiCallback userCallback; 581 | void *userData; 582 | bool continueSysex; 583 | 584 | // Default constructor. 585 | RtMidiInData() 586 | : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false), 587 | userCallback(0), userData(0), continueSysex(false) {} 588 | }; 589 | 590 | protected: 591 | RtMidiInData inputData_; 592 | }; 593 | 594 | class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi 595 | { 596 | public: 597 | 598 | MidiOutApi( void ); 599 | virtual ~MidiOutApi( void ); 600 | virtual void sendMessage( const unsigned char *message, size_t size ) = 0; 601 | }; 602 | 603 | // **************************************************************** // 604 | // 605 | // Inline RtMidiIn and RtMidiOut definitions. 606 | // 607 | // **************************************************************** // 608 | 609 | inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } 610 | inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); } 611 | inline void RtMidiIn :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } 612 | inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } 613 | inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } 614 | inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { static_cast(rtapi_)->setCallback( callback, userData ); } 615 | inline void RtMidiIn :: cancelCallback( void ) { static_cast(rtapi_)->cancelCallback(); } 616 | inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } 617 | inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } 618 | inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { static_cast(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } 619 | inline double RtMidiIn :: getMessage( std::vector *message ) { return static_cast(rtapi_)->getMessage( message ); } 620 | inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } 621 | 622 | inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } 623 | inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); } 624 | inline void RtMidiOut :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } 625 | inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } 626 | inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } 627 | inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } 628 | inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } 629 | inline void RtMidiOut :: sendMessage( const std::vector *message ) { static_cast(rtapi_)->sendMessage( &message->at(0), message->size() ); } 630 | inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { static_cast(rtapi_)->sendMessage( message, size ); } 631 | inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } 632 | 633 | #endif 634 | -------------------------------------------------------------------------------- /build-osx.sh: -------------------------------------------------------------------------------- 1 | mkdir build 2 | g++ -Wall -D__MACOSX_CORE__ index.cpp RtMidi.cpp -framework CoreMIDI -framework CoreAudio -framework CoreFoundation -framework ApplicationServices -o build/midi-to-keyboard-osx 3 | -------------------------------------------------------------------------------- /index.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Simulates a key press. 4 | void pressKey(char key); 5 | 6 | #if defined(_WIN32) 7 | 8 | #include 9 | 10 | HKL winKeyboardLayout = GetKeyboardLayout(0); 11 | 12 | void pressKey(char key) { 13 | INPUT input; 14 | input.type = INPUT_KEYBOARD; 15 | input.ki.wScan = key; 16 | input.ki.wVk = 0; 17 | input.ki.dwFlags = KEYEVENTF_UNICODE; 18 | input.ki.time = 0; 19 | input.ki.dwExtraInfo = 0; 20 | 21 | SendInput(1, &input, sizeof(INPUT)); 22 | } 23 | 24 | #elif defined(__APPLE__) 25 | 26 | #include 27 | 28 | void pressKey(char key) { 29 | CGEventRef eventDown = CGEventCreateKeyboardEvent(NULL, 8, true); 30 | CGEventRef eventUp = CGEventCreateKeyboardEvent(NULL, 8, false); 31 | 32 | if (eventDown != NULL && eventUp != NULL) { 33 | UniChar s[] = {key}; 34 | CGEventKeyboardSetUnicodeString(eventDown, 1, s); 35 | CGEventKeyboardSetUnicodeString(eventUp, 1, s); 36 | CGEventPost(kCGSessionEventTap, eventDown); 37 | CGEventPost(kCGSessionEventTap, eventUp); 38 | } 39 | 40 | CFRelease(eventDown); 41 | CFRelease(eventUp); 42 | } 43 | 44 | // TODO linux 45 | 46 | #endif 47 | 48 | #include "RtMidi.h" 49 | 50 | char midiTable[128]; 51 | 52 | void initMidiTable() { 53 | // fill with ' ' 54 | for (int i = 0; i < 128; i++) { 55 | midiTable[i] = ' '; 56 | } 57 | 58 | // full range 59 | for (int i = 32; i <= 63; i++) { 60 | midiTable[i - 32] = i; 61 | } 62 | 63 | int n1 = (63 - 32) + 1; 64 | for (int i = 96; i <= 125; i++) { 65 | midiTable[n1 + (i - 96)] = i; 66 | } 67 | } 68 | 69 | void exitWithMessage(std::string message, int code) { 70 | std::cout << message << std::endl; 71 | std::cout << "Press enter to close." << std::endl; 72 | std::cin.get(); 73 | exit(code); 74 | } 75 | 76 | int getMidiPort(RtMidiIn* midiIn, char* preferredPortName) { 77 | int portCount = midiIn->getPortCount(); 78 | if (portCount == 0) { 79 | exitWithMessage("No MIDI inputs found.", EXIT_FAILURE); 80 | } 81 | 82 | if (portCount > 1) { 83 | // Find best port from name. 84 | if (preferredPortName != nullptr) { 85 | for (int i = 0; i < portCount; i++) { 86 | auto portName = midiIn->getPortName(i); 87 | if (portName.find(preferredPortName) != std::string::npos) { 88 | return i; 89 | } 90 | } 91 | } 92 | } 93 | 94 | return 0; 95 | } 96 | 97 | void midiInputCallback(double deltaTime, std::vector *message, void *userData) { 98 | if (message->size() == 3) { 99 | auto status = message->at(0); 100 | auto data1 = message->at(1); 101 | // auto data2 = message->at(2); 102 | 103 | int a = status & 0b11110000; 104 | int b = 0b10010000; 105 | if (a == b) { 106 | int note = data1 & 0b01111111; 107 | 108 | pressKey(midiTable[note]); 109 | } 110 | } 111 | } 112 | 113 | int main(int argc, char** argv) { 114 | char* preferredPortName = argc <= 1 ? nullptr : argv[1]; 115 | 116 | initMidiTable(); 117 | 118 | RtMidiIn* midiIn = new RtMidiIn(); 119 | 120 | int port = getMidiPort(midiIn, preferredPortName); 121 | midiIn->openPort(port); 122 | 123 | midiIn->setCallback(&midiInputCallback); 124 | 125 | auto portName = midiIn->getPortName(port); 126 | 127 | std::cout << "PICO-8 MIDI to Keyboard ready!" << std::endl; 128 | std::cout << "MIDI device: " << portName << std::endl; 129 | 130 | // wait 131 | std::cin.get(); 132 | 133 | return 0; 134 | } 135 | -------------------------------------------------------------------------------- /midi-to-keyboard.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.852 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "midi-to-keyboard", "midi-to-keyboard.vcxproj", "{4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Debug|x64.ActiveCfg = Debug|x64 17 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Debug|x64.Build.0 = Debug|x64 18 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Debug|x86.ActiveCfg = Debug|Win32 19 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Debug|x86.Build.0 = Debug|Win32 20 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Release|x64.ActiveCfg = Release|x64 21 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Release|x64.Build.0 = Release|x64 22 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Release|x86.ActiveCfg = Release|Win32 23 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F31E0F3B-AF9A-4F8A-A2C9-5F1F1885C724} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /midi-to-keyboard.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {4E7BBF49-EAF3-4A52-8D75-9B376C7BB06C} 24 | miditokeyboard 25 | 10.0.17763.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v141 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | Disabled 77 | true 78 | true 79 | __WINDOWS_MM__;%(PreprocessorDefinitions) 80 | 81 | 82 | winmm.lib;%(AdditionalDependencies) 83 | 84 | 85 | 86 | 87 | Level3 88 | Disabled 89 | true 90 | true 91 | __WINDOWS_MM__;%(PreprocessorDefinitions) 92 | 93 | 94 | 95 | 96 | Level3 97 | MaxSpeed 98 | true 99 | true 100 | true 101 | true 102 | __WINDOWS_MM__;%(PreprocessorDefinitions) 103 | 104 | 105 | true 106 | true 107 | winmm.lib;%(AdditionalDependencies) 108 | 109 | 110 | 111 | 112 | Level3 113 | MaxSpeed 114 | true 115 | true 116 | true 117 | true 118 | __WINDOWS_MM__;%(PreprocessorDefinitions) 119 | 120 | 121 | true 122 | true 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /midi-to-keyboard.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | --------------------------------------------------------------------------------