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