Chris@559
|
1 /**********************************************************************/
|
Chris@559
|
2 /*! \class RtMidi
|
Chris@559
|
3 \brief An abstract base class for realtime MIDI input/output.
|
Chris@559
|
4
|
Chris@559
|
5 This class implements some common functionality for the realtime
|
Chris@559
|
6 MIDI input/output subclasses RtMidiIn and RtMidiOut.
|
Chris@559
|
7
|
Chris@559
|
8 RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
|
Chris@559
|
9
|
Chris@559
|
10 RtMidi: realtime MIDI i/o C++ classes
|
Chris@1397
|
11 Copyright (c) 2003-2016 Gary P. Scavone
|
Chris@559
|
12
|
Chris@559
|
13 Permission is hereby granted, free of charge, to any person
|
Chris@559
|
14 obtaining a copy of this software and associated documentation files
|
Chris@559
|
15 (the "Software"), to deal in the Software without restriction,
|
Chris@559
|
16 including without limitation the rights to use, copy, modify, merge,
|
Chris@559
|
17 publish, distribute, sublicense, and/or sell copies of the Software,
|
Chris@559
|
18 and to permit persons to whom the Software is furnished to do so,
|
Chris@559
|
19 subject to the following conditions:
|
Chris@559
|
20
|
Chris@559
|
21 The above copyright notice and this permission notice shall be
|
Chris@559
|
22 included in all copies or substantial portions of the Software.
|
Chris@559
|
23
|
Chris@559
|
24 Any person wishing to distribute modifications to the Software is
|
Chris@1397
|
25 asked to send the modifications to the original developer so that
|
Chris@1397
|
26 they can be incorporated into the canonical version. This is,
|
Chris@1397
|
27 however, not a binding provision of this license.
|
Chris@559
|
28
|
Chris@559
|
29 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@559
|
30 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@559
|
31 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
Chris@559
|
32 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
Chris@559
|
33 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@559
|
34 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@559
|
35 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@559
|
36 */
|
Chris@559
|
37 /**********************************************************************/
|
Chris@559
|
38
|
Chris@559
|
39 #include "RtMidi.h"
|
Chris@559
|
40 #include <sstream>
|
Chris@559
|
41
|
Chris@1402
|
42 // CC be gung-ho about this here, assume upstream has it in hand
|
Chris@1402
|
43 #pragma GCC diagnostic ignored "-Wconversion"
|
Chris@1402
|
44
|
Chris@1397
|
45 #if defined(__MACOSX_CORE__)
|
Chris@1397
|
46 #if TARGET_OS_IPHONE
|
Chris@1397
|
47 #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime
|
Chris@1397
|
48 #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos
|
Chris@1397
|
49 #endif
|
Chris@1397
|
50 #endif
|
Chris@843
|
51
|
Chris@559
|
52 //*********************************************************************//
|
Chris@1397
|
53 // RtMidi Definitions
|
Chris@559
|
54 //*********************************************************************//
|
Chris@559
|
55
|
Chris@559
|
56 RtMidi :: RtMidi()
|
Chris@1397
|
57 : rtapi_(0)
|
Chris@559
|
58 {
|
Chris@559
|
59 }
|
Chris@559
|
60
|
Chris@1397
|
61 RtMidi :: ~RtMidi()
|
Chris@559
|
62 {
|
Chris@1397
|
63 if ( rtapi_ )
|
Chris@1397
|
64 delete rtapi_;
|
Chris@1397
|
65 rtapi_ = 0;
|
Chris@1397
|
66 }
|
Chris@1397
|
67
|
Chris@1397
|
68 std::string RtMidi :: getVersion( void ) throw()
|
Chris@1397
|
69 {
|
Chris@1397
|
70 return std::string( RTMIDI_VERSION );
|
Chris@1397
|
71 }
|
Chris@1397
|
72
|
Chris@1397
|
73 void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw()
|
Chris@1397
|
74 {
|
Chris@1397
|
75 apis.clear();
|
Chris@1397
|
76
|
Chris@1397
|
77 // The order here will control the order of RtMidi's API search in
|
Chris@1397
|
78 // the constructor.
|
Chris@1397
|
79 #if defined(__MACOSX_CORE__)
|
Chris@1397
|
80 apis.push_back( MACOSX_CORE );
|
Chris@1397
|
81 #endif
|
Chris@1397
|
82 #if defined(__LINUX_ALSA__)
|
Chris@1397
|
83 apis.push_back( LINUX_ALSA );
|
Chris@1397
|
84 #endif
|
Chris@1397
|
85 #if defined(__UNIX_JACK__)
|
Chris@1397
|
86 apis.push_back( UNIX_JACK );
|
Chris@1397
|
87 #endif
|
Chris@1397
|
88 #if defined(__WINDOWS_MM__)
|
Chris@1397
|
89 apis.push_back( WINDOWS_MM );
|
Chris@1397
|
90 #endif
|
Chris@1397
|
91 #if defined(__RTMIDI_DUMMY__)
|
Chris@1397
|
92 apis.push_back( RTMIDI_DUMMY );
|
Chris@1397
|
93 #endif
|
Chris@1397
|
94 }
|
Chris@1397
|
95
|
Chris@1397
|
96 //*********************************************************************//
|
Chris@1397
|
97 // RtMidiIn Definitions
|
Chris@1397
|
98 //*********************************************************************//
|
Chris@1397
|
99
|
Chris@1397
|
100 void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )
|
Chris@1397
|
101 {
|
Chris@1397
|
102 if ( rtapi_ )
|
Chris@1397
|
103 delete rtapi_;
|
Chris@1397
|
104 rtapi_ = 0;
|
Chris@1397
|
105
|
Chris@1397
|
106 #if defined(__UNIX_JACK__)
|
Chris@1397
|
107 if ( api == UNIX_JACK )
|
Chris@1397
|
108 rtapi_ = new MidiInJack( clientName, queueSizeLimit );
|
Chris@1397
|
109 #endif
|
Chris@1397
|
110 #if defined(__LINUX_ALSA__)
|
Chris@1397
|
111 if ( api == LINUX_ALSA )
|
Chris@1397
|
112 rtapi_ = new MidiInAlsa( clientName, queueSizeLimit );
|
Chris@1397
|
113 #endif
|
Chris@1397
|
114 #if defined(__WINDOWS_MM__)
|
Chris@1397
|
115 if ( api == WINDOWS_MM )
|
Chris@1397
|
116 rtapi_ = new MidiInWinMM( clientName, queueSizeLimit );
|
Chris@1397
|
117 #endif
|
Chris@1397
|
118 #if defined(__MACOSX_CORE__)
|
Chris@1397
|
119 if ( api == MACOSX_CORE )
|
Chris@1397
|
120 rtapi_ = new MidiInCore( clientName, queueSizeLimit );
|
Chris@1397
|
121 #endif
|
Chris@1397
|
122 #if defined(__RTMIDI_DUMMY__)
|
Chris@1397
|
123 if ( api == RTMIDI_DUMMY )
|
Chris@1397
|
124 rtapi_ = new MidiInDummy( clientName, queueSizeLimit );
|
Chris@1397
|
125 #endif
|
Chris@1397
|
126 }
|
Chris@1397
|
127
|
Chris@1397
|
128 RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )
|
Chris@1397
|
129 : RtMidi()
|
Chris@1397
|
130 {
|
Chris@1397
|
131 if ( api != UNSPECIFIED ) {
|
Chris@1397
|
132 // Attempt to open the specified API.
|
Chris@1397
|
133 openMidiApi( api, clientName, queueSizeLimit );
|
Chris@1397
|
134 if ( rtapi_ ) return;
|
Chris@1397
|
135
|
Chris@1397
|
136 // No compiled support for specified API value. Issue a warning
|
Chris@1397
|
137 // and continue as if no API was specified.
|
Chris@1397
|
138 std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl;
|
Chris@559
|
139 }
|
Chris@1397
|
140
|
Chris@1397
|
141 // Iterate through the compiled APIs and return as soon as we find
|
Chris@1397
|
142 // one with at least one port or we reach the end of the list.
|
Chris@1397
|
143 std::vector< RtMidi::Api > apis;
|
Chris@1397
|
144 getCompiledApi( apis );
|
Chris@1397
|
145 for ( unsigned int i=0; i<apis.size(); i++ ) {
|
Chris@1397
|
146 openMidiApi( apis[i], clientName, queueSizeLimit );
|
Chris@1397
|
147 if ( rtapi_->getPortCount() ) break;
|
Chris@1397
|
148 }
|
Chris@1397
|
149
|
Chris@1397
|
150 if ( rtapi_ ) return;
|
Chris@1397
|
151
|
Chris@1397
|
152 // It should not be possible to get here because the preprocessor
|
Chris@1397
|
153 // definition __RTMIDI_DUMMY__ is automatically defined if no
|
Chris@1397
|
154 // API-specific definitions are passed to the compiler. But just in
|
Chris@1397
|
155 // case something weird happens, we'll throw an error.
|
Chris@1397
|
156 std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!";
|
Chris@1397
|
157 throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
|
Chris@1397
|
158 }
|
Chris@1397
|
159
|
Chris@1397
|
160 RtMidiIn :: ~RtMidiIn() throw()
|
Chris@1397
|
161 {
|
Chris@1397
|
162 }
|
Chris@1397
|
163
|
Chris@1397
|
164
|
Chris@1397
|
165 //*********************************************************************//
|
Chris@1397
|
166 // RtMidiOut Definitions
|
Chris@1397
|
167 //*********************************************************************//
|
Chris@1397
|
168
|
Chris@1397
|
169 void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName )
|
Chris@1397
|
170 {
|
Chris@1397
|
171 if ( rtapi_ )
|
Chris@1397
|
172 delete rtapi_;
|
Chris@1397
|
173 rtapi_ = 0;
|
Chris@1397
|
174
|
Chris@1397
|
175 #if defined(__UNIX_JACK__)
|
Chris@1397
|
176 if ( api == UNIX_JACK )
|
Chris@1397
|
177 rtapi_ = new MidiOutJack( clientName );
|
Chris@1397
|
178 #endif
|
Chris@1397
|
179 #if defined(__LINUX_ALSA__)
|
Chris@1397
|
180 if ( api == LINUX_ALSA )
|
Chris@1397
|
181 rtapi_ = new MidiOutAlsa( clientName );
|
Chris@1397
|
182 #endif
|
Chris@1397
|
183 #if defined(__WINDOWS_MM__)
|
Chris@1397
|
184 if ( api == WINDOWS_MM )
|
Chris@1397
|
185 rtapi_ = new MidiOutWinMM( clientName );
|
Chris@1397
|
186 #endif
|
Chris@1397
|
187 #if defined(__MACOSX_CORE__)
|
Chris@1397
|
188 if ( api == MACOSX_CORE )
|
Chris@1397
|
189 rtapi_ = new MidiOutCore( clientName );
|
Chris@1397
|
190 #endif
|
Chris@1397
|
191 #if defined(__RTMIDI_DUMMY__)
|
Chris@1397
|
192 if ( api == RTMIDI_DUMMY )
|
Chris@1397
|
193 rtapi_ = new MidiOutDummy( clientName );
|
Chris@1397
|
194 #endif
|
Chris@1397
|
195 }
|
Chris@1397
|
196
|
Chris@1397
|
197 RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName )
|
Chris@1397
|
198 {
|
Chris@1397
|
199 if ( api != UNSPECIFIED ) {
|
Chris@1397
|
200 // Attempt to open the specified API.
|
Chris@1397
|
201 openMidiApi( api, clientName );
|
Chris@1397
|
202 if ( rtapi_ ) return;
|
Chris@1397
|
203
|
Chris@1397
|
204 // No compiled support for specified API value. Issue a warning
|
Chris@1397
|
205 // and continue as if no API was specified.
|
Chris@1397
|
206 std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl;
|
Chris@1397
|
207 }
|
Chris@1397
|
208
|
Chris@1397
|
209 // Iterate through the compiled APIs and return as soon as we find
|
Chris@1397
|
210 // one with at least one port or we reach the end of the list.
|
Chris@1397
|
211 std::vector< RtMidi::Api > apis;
|
Chris@1397
|
212 getCompiledApi( apis );
|
Chris@1397
|
213 for ( unsigned int i=0; i<apis.size(); i++ ) {
|
Chris@1397
|
214 openMidiApi( apis[i], clientName );
|
Chris@1397
|
215 if ( rtapi_->getPortCount() ) break;
|
Chris@1397
|
216 }
|
Chris@1397
|
217
|
Chris@1397
|
218 if ( rtapi_ ) return;
|
Chris@1397
|
219
|
Chris@1397
|
220 // It should not be possible to get here because the preprocessor
|
Chris@1397
|
221 // definition __RTMIDI_DUMMY__ is automatically defined if no
|
Chris@1397
|
222 // API-specific definitions are passed to the compiler. But just in
|
Chris@1397
|
223 // case something weird happens, we'll thrown an error.
|
Chris@1397
|
224 std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!";
|
Chris@1397
|
225 throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );
|
Chris@1397
|
226 }
|
Chris@1397
|
227
|
Chris@1397
|
228 RtMidiOut :: ~RtMidiOut() throw()
|
Chris@1397
|
229 {
|
Chris@1397
|
230 }
|
Chris@1397
|
231
|
Chris@1397
|
232 //*********************************************************************//
|
Chris@1397
|
233 // Common MidiApi Definitions
|
Chris@1397
|
234 //*********************************************************************//
|
Chris@1397
|
235
|
Chris@1397
|
236 MidiApi :: MidiApi( void )
|
Chris@1397
|
237 : apiData_( 0 ), connected_( false ), errorCallback_(0), errorCallbackUserData_(0)
|
Chris@1397
|
238 {
|
Chris@1397
|
239 }
|
Chris@1397
|
240
|
Chris@1397
|
241 MidiApi :: ~MidiApi( void )
|
Chris@1397
|
242 {
|
Chris@1397
|
243 }
|
Chris@1397
|
244
|
Chris@1397
|
245 void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 )
|
Chris@1397
|
246 {
|
Chris@1397
|
247 errorCallback_ = errorCallback;
|
Chris@1397
|
248 errorCallbackUserData_ = userData;
|
Chris@1397
|
249 }
|
Chris@1397
|
250
|
Chris@1397
|
251 void MidiApi :: error( RtMidiError::Type type, std::string errorString )
|
Chris@1397
|
252 {
|
Chris@1397
|
253 if ( errorCallback_ ) {
|
Chris@1397
|
254
|
Chris@1397
|
255 if ( firstErrorOccurred_ )
|
Chris@1397
|
256 return;
|
Chris@1397
|
257
|
Chris@1397
|
258 firstErrorOccurred_ = true;
|
Chris@1397
|
259 const std::string errorMessage = errorString;
|
Chris@1397
|
260
|
Chris@1397
|
261 errorCallback_( type, errorMessage, errorCallbackUserData_);
|
Chris@1397
|
262 firstErrorOccurred_ = false;
|
Chris@1397
|
263 return;
|
Chris@1397
|
264 }
|
Chris@1397
|
265
|
Chris@1397
|
266 if ( type == RtMidiError::WARNING ) {
|
Chris@1397
|
267 std::cerr << '\n' << errorString << "\n\n";
|
Chris@1397
|
268 }
|
Chris@1397
|
269 else if ( type == RtMidiError::DEBUG_WARNING ) {
|
Chris@559
|
270 #if defined(__RTMIDI_DEBUG__)
|
Chris@1397
|
271 std::cerr << '\n' << errorString << "\n\n";
|
Chris@559
|
272 #endif
|
Chris@559
|
273 }
|
Chris@559
|
274 else {
|
Chris@1397
|
275 std::cerr << '\n' << errorString << "\n\n";
|
Chris@1397
|
276 throw RtMidiError( errorString, type );
|
Chris@559
|
277 }
|
Chris@559
|
278 }
|
Chris@559
|
279
|
Chris@559
|
280 //*********************************************************************//
|
Chris@1397
|
281 // Common MidiInApi Definitions
|
Chris@559
|
282 //*********************************************************************//
|
Chris@559
|
283
|
Chris@1397
|
284 MidiInApi :: MidiInApi( unsigned int queueSizeLimit )
|
Chris@1397
|
285 : MidiApi()
|
Chris@559
|
286 {
|
Chris@1397
|
287 // Allocate the MIDI queue.
|
Chris@1397
|
288 inputData_.queue.ringSize = queueSizeLimit;
|
Chris@1397
|
289 if ( inputData_.queue.ringSize > 0 )
|
Chris@1397
|
290 inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ];
|
Chris@559
|
291 }
|
Chris@559
|
292
|
Chris@1397
|
293 MidiInApi :: ~MidiInApi( void )
|
Chris@1397
|
294 {
|
Chris@1397
|
295 // Delete the MIDI queue.
|
Chris@1397
|
296 if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring;
|
Chris@1397
|
297 }
|
Chris@1397
|
298
|
Chris@1397
|
299 void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData )
|
Chris@559
|
300 {
|
Chris@559
|
301 if ( inputData_.usingCallback ) {
|
Chris@1397
|
302 errorString_ = "MidiInApi::setCallback: a callback function is already set!";
|
Chris@1397
|
303 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
304 return;
|
Chris@559
|
305 }
|
Chris@559
|
306
|
Chris@559
|
307 if ( !callback ) {
|
Chris@559
|
308 errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";
|
Chris@1397
|
309 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
310 return;
|
Chris@559
|
311 }
|
Chris@559
|
312
|
Chris@1397
|
313 inputData_.userCallback = callback;
|
Chris@559
|
314 inputData_.userData = userData;
|
Chris@559
|
315 inputData_.usingCallback = true;
|
Chris@559
|
316 }
|
Chris@559
|
317
|
Chris@1397
|
318 void MidiInApi :: cancelCallback()
|
Chris@559
|
319 {
|
Chris@559
|
320 if ( !inputData_.usingCallback ) {
|
Chris@559
|
321 errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";
|
Chris@1397
|
322 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
323 return;
|
Chris@559
|
324 }
|
Chris@559
|
325
|
Chris@559
|
326 inputData_.userCallback = 0;
|
Chris@559
|
327 inputData_.userData = 0;
|
Chris@559
|
328 inputData_.usingCallback = false;
|
Chris@559
|
329 }
|
Chris@559
|
330
|
Chris@1397
|
331 void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )
|
Chris@559
|
332 {
|
Chris@559
|
333 inputData_.ignoreFlags = 0;
|
Chris@559
|
334 if ( midiSysex ) inputData_.ignoreFlags = 0x01;
|
Chris@559
|
335 if ( midiTime ) inputData_.ignoreFlags |= 0x02;
|
Chris@559
|
336 if ( midiSense ) inputData_.ignoreFlags |= 0x04;
|
Chris@559
|
337 }
|
Chris@559
|
338
|
Chris@1397
|
339 double MidiInApi :: getMessage( std::vector<unsigned char> *message )
|
Chris@559
|
340 {
|
Chris@559
|
341 message->clear();
|
Chris@559
|
342
|
Chris@559
|
343 if ( inputData_.usingCallback ) {
|
Chris@559
|
344 errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";
|
Chris@1397
|
345 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
346 return 0.0;
|
Chris@559
|
347 }
|
Chris@559
|
348
|
Chris@1397
|
349 if ( inputData_.queue.size == 0 ) return 0.0;
|
Chris@559
|
350
|
Chris@559
|
351 // Copy queued message to the vector pointer argument and then "pop" it.
|
Chris@1397
|
352 std::vector<unsigned char> *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes);
|
Chris@559
|
353 message->assign( bytes->begin(), bytes->end() );
|
Chris@1397
|
354 double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp;
|
Chris@1397
|
355 inputData_.queue.size--;
|
Chris@1397
|
356 inputData_.queue.front++;
|
Chris@1397
|
357 if ( inputData_.queue.front == inputData_.queue.ringSize )
|
Chris@1397
|
358 inputData_.queue.front = 0;
|
Chris@559
|
359
|
Chris@559
|
360 return deltaTime;
|
Chris@559
|
361 }
|
Chris@559
|
362
|
Chris@559
|
363 //*********************************************************************//
|
Chris@1397
|
364 // Common MidiOutApi Definitions
|
Chris@559
|
365 //*********************************************************************//
|
Chris@559
|
366
|
Chris@1397
|
367 MidiOutApi :: MidiOutApi( void )
|
Chris@1397
|
368 : MidiApi()
|
Chris@559
|
369 {
|
Chris@559
|
370 }
|
Chris@559
|
371
|
Chris@1397
|
372 MidiOutApi :: ~MidiOutApi( void )
|
Chris@1397
|
373 {
|
Chris@1397
|
374 }
|
Chris@1397
|
375
|
Chris@1397
|
376 // *************************************************** //
|
Chris@1397
|
377 //
|
Chris@1397
|
378 // OS/API-specific methods.
|
Chris@1397
|
379 //
|
Chris@1397
|
380 // *************************************************** //
|
Chris@559
|
381
|
Chris@559
|
382 #if defined(__MACOSX_CORE__)
|
Chris@559
|
383
|
Chris@559
|
384 // The CoreMIDI API is based on the use of a callback function for
|
Chris@559
|
385 // MIDI input. We convert the system specific time stamps to delta
|
Chris@559
|
386 // time values.
|
Chris@559
|
387
|
Chris@559
|
388 // OS-X CoreMIDI header files.
|
Chris@559
|
389 #include <CoreMIDI/CoreMIDI.h>
|
Chris@559
|
390 #include <CoreAudio/HostTime.h>
|
Chris@1397
|
391 #include <CoreServices/CoreServices.h>
|
Chris@559
|
392
|
Chris@559
|
393 // A structure to hold variables related to the CoreMIDI API
|
Chris@559
|
394 // implementation.
|
Chris@559
|
395 struct CoreMidiData {
|
Chris@559
|
396 MIDIClientRef client;
|
Chris@559
|
397 MIDIPortRef port;
|
Chris@559
|
398 MIDIEndpointRef endpoint;
|
Chris@559
|
399 MIDIEndpointRef destinationId;
|
Chris@559
|
400 unsigned long long lastTime;
|
Chris@1397
|
401 MIDISysexSendRequest sysexreq;
|
Chris@559
|
402 };
|
Chris@559
|
403
|
Chris@559
|
404 //*********************************************************************//
|
Chris@559
|
405 // API: OS-X
|
Chris@1397
|
406 // Class Definitions: MidiInCore
|
Chris@559
|
407 //*********************************************************************//
|
Chris@559
|
408
|
Chris@1397
|
409 static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ )
|
Chris@559
|
410 {
|
Chris@1397
|
411 MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef);
|
Chris@559
|
412 CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData);
|
Chris@559
|
413
|
Chris@559
|
414 unsigned char status;
|
Chris@559
|
415 unsigned short nBytes, iByte, size;
|
Chris@559
|
416 unsigned long long time;
|
Chris@565
|
417
|
Chris@565
|
418 bool& continueSysex = data->continueSysex;
|
Chris@1397
|
419 MidiInApi::MidiMessage& message = data->message;
|
Chris@565
|
420
|
Chris@559
|
421 const MIDIPacket *packet = &list->packet[0];
|
Chris@559
|
422 for ( unsigned int i=0; i<list->numPackets; ++i ) {
|
Chris@559
|
423
|
Chris@559
|
424 // My interpretation of the CoreMIDI documentation: all message
|
Chris@559
|
425 // types, except sysex, are complete within a packet and there may
|
Chris@559
|
426 // be several of them in a single packet. Sysex messages can be
|
Chris@565
|
427 // broken across multiple packets and PacketLists but are bundled
|
Chris@565
|
428 // alone within each packet (these packets do not contain other
|
Chris@565
|
429 // message types). If sysex messages are split across multiple
|
Chris@565
|
430 // MIDIPacketLists, they must be handled by multiple calls to this
|
Chris@565
|
431 // function.
|
Chris@559
|
432
|
Chris@559
|
433 nBytes = packet->length;
|
Chris@559
|
434 if ( nBytes == 0 ) continue;
|
Chris@559
|
435
|
Chris@559
|
436 // Calculate time stamp.
|
Chris@1397
|
437
|
Chris@1397
|
438 if ( data->firstMessage ) {
|
Chris@1397
|
439 message.timeStamp = 0.0;
|
Chris@559
|
440 data->firstMessage = false;
|
Chris@1397
|
441 }
|
Chris@559
|
442 else {
|
Chris@559
|
443 time = packet->timeStamp;
|
Chris@1397
|
444 if ( time == 0 ) { // this happens when receiving asynchronous sysex messages
|
Chris@1397
|
445 time = AudioGetCurrentHostTime();
|
Chris@1397
|
446 }
|
Chris@559
|
447 time -= apiData->lastTime;
|
Chris@559
|
448 time = AudioConvertHostTimeToNanos( time );
|
Chris@1397
|
449 if ( !continueSysex )
|
Chris@1397
|
450 message.timeStamp = time * 0.000000001;
|
Chris@559
|
451 }
|
Chris@559
|
452 apiData->lastTime = packet->timeStamp;
|
Chris@1397
|
453 if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages
|
Chris@1397
|
454 apiData->lastTime = AudioGetCurrentHostTime();
|
Chris@1397
|
455 }
|
Chris@1397
|
456 //std::cout << "TimeStamp = " << packet->timeStamp << std::endl;
|
Chris@559
|
457
|
Chris@559
|
458 iByte = 0;
|
Chris@559
|
459 if ( continueSysex ) {
|
Chris@559
|
460 // We have a continuing, segmented sysex message.
|
Chris@565
|
461 if ( !( data->ignoreFlags & 0x01 ) ) {
|
Chris@559
|
462 // If we're not ignoring sysex messages, copy the entire packet.
|
Chris@1397
|
463 for ( unsigned int j=0; j<nBytes; ++j )
|
Chris@559
|
464 message.bytes.push_back( packet->data[j] );
|
Chris@559
|
465 }
|
Chris@565
|
466 continueSysex = packet->data[nBytes-1] != 0xF7;
|
Chris@565
|
467
|
Chris@1397
|
468 if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) {
|
Chris@559
|
469 // If not a continuing sysex message, invoke the user callback function or queue the message.
|
Chris@1397
|
470 if ( data->usingCallback ) {
|
Chris@559
|
471 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
|
Chris@559
|
472 callback( message.timeStamp, &message.bytes, data->userData );
|
Chris@559
|
473 }
|
Chris@559
|
474 else {
|
Chris@559
|
475 // As long as we haven't reached our queue size limit, push the message.
|
Chris@1397
|
476 if ( data->queue.size < data->queue.ringSize ) {
|
Chris@1397
|
477 data->queue.ring[data->queue.back++] = message;
|
Chris@1397
|
478 if ( data->queue.back == data->queue.ringSize )
|
Chris@1397
|
479 data->queue.back = 0;
|
Chris@1397
|
480 data->queue.size++;
|
Chris@1397
|
481 }
|
Chris@559
|
482 else
|
Chris@1397
|
483 std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
|
Chris@559
|
484 }
|
Chris@559
|
485 message.bytes.clear();
|
Chris@559
|
486 }
|
Chris@559
|
487 }
|
Chris@559
|
488 else {
|
Chris@559
|
489 while ( iByte < nBytes ) {
|
Chris@559
|
490 size = 0;
|
Chris@559
|
491 // We are expecting that the next byte in the packet is a status byte.
|
Chris@559
|
492 status = packet->data[iByte];
|
Chris@559
|
493 if ( !(status & 0x80) ) break;
|
Chris@559
|
494 // Determine the number of bytes in the MIDI message.
|
Chris@559
|
495 if ( status < 0xC0 ) size = 3;
|
Chris@559
|
496 else if ( status < 0xE0 ) size = 2;
|
Chris@559
|
497 else if ( status < 0xF0 ) size = 3;
|
Chris@559
|
498 else if ( status == 0xF0 ) {
|
Chris@559
|
499 // A MIDI sysex
|
Chris@559
|
500 if ( data->ignoreFlags & 0x01 ) {
|
Chris@559
|
501 size = 0;
|
Chris@559
|
502 iByte = nBytes;
|
Chris@559
|
503 }
|
Chris@559
|
504 else size = nBytes - iByte;
|
Chris@1397
|
505 continueSysex = packet->data[nBytes-1] != 0xF7;
|
Chris@559
|
506 }
|
Chris@1397
|
507 else if ( status == 0xF1 ) {
|
Chris@1397
|
508 // A MIDI time code message
|
Chris@1397
|
509 if ( data->ignoreFlags & 0x02 ) {
|
Chris@559
|
510 size = 0;
|
Chris@1397
|
511 iByte += 2;
|
Chris@1397
|
512 }
|
Chris@1397
|
513 else size = 2;
|
Chris@559
|
514 }
|
Chris@1397
|
515 else if ( status == 0xF2 ) size = 3;
|
Chris@559
|
516 else if ( status == 0xF3 ) size = 2;
|
Chris@1397
|
517 else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {
|
Chris@1397
|
518 // A MIDI timing tick message and we're ignoring it.
|
Chris@1397
|
519 size = 0;
|
Chris@1397
|
520 iByte += 1;
|
Chris@559
|
521 }
|
Chris@1397
|
522 else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {
|
Chris@559
|
523 // A MIDI active sensing message and we're ignoring it.
|
Chris@559
|
524 size = 0;
|
Chris@559
|
525 iByte += 1;
|
Chris@559
|
526 }
|
Chris@559
|
527 else size = 1;
|
Chris@559
|
528
|
Chris@559
|
529 // Copy the MIDI data to our vector.
|
Chris@559
|
530 if ( size ) {
|
Chris@559
|
531 message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );
|
Chris@559
|
532 if ( !continueSysex ) {
|
Chris@559
|
533 // If not a continuing sysex message, invoke the user callback function or queue the message.
|
Chris@559
|
534 if ( data->usingCallback ) {
|
Chris@559
|
535 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
|
Chris@559
|
536 callback( message.timeStamp, &message.bytes, data->userData );
|
Chris@559
|
537 }
|
Chris@559
|
538 else {
|
Chris@559
|
539 // As long as we haven't reached our queue size limit, push the message.
|
Chris@1397
|
540 if ( data->queue.size < data->queue.ringSize ) {
|
Chris@1397
|
541 data->queue.ring[data->queue.back++] = message;
|
Chris@1397
|
542 if ( data->queue.back == data->queue.ringSize )
|
Chris@1397
|
543 data->queue.back = 0;
|
Chris@1397
|
544 data->queue.size++;
|
Chris@1397
|
545 }
|
Chris@559
|
546 else
|
Chris@1397
|
547 std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";
|
Chris@559
|
548 }
|
Chris@559
|
549 message.bytes.clear();
|
Chris@559
|
550 }
|
Chris@559
|
551 iByte += size;
|
Chris@559
|
552 }
|
Chris@559
|
553 }
|
Chris@559
|
554 }
|
Chris@559
|
555 packet = MIDIPacketNext(packet);
|
Chris@559
|
556 }
|
Chris@559
|
557 }
|
Chris@559
|
558
|
Chris@1397
|
559 MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
|
Chris@1397
|
560 {
|
Chris@1397
|
561 initialize( clientName );
|
Chris@1397
|
562 }
|
Chris@1397
|
563
|
Chris@1397
|
564 MidiInCore :: ~MidiInCore( void )
|
Chris@1397
|
565 {
|
Chris@1397
|
566 // Close a connection if it exists.
|
Chris@1397
|
567 closePort();
|
Chris@1397
|
568
|
Chris@1397
|
569 // Cleanup.
|
Chris@1397
|
570 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@1397
|
571 MIDIClientDispose( data->client );
|
Chris@1397
|
572 if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
|
Chris@1397
|
573 delete data;
|
Chris@1397
|
574 }
|
Chris@1397
|
575
|
Chris@1397
|
576 void MidiInCore :: initialize( const std::string& clientName )
|
Chris@559
|
577 {
|
Chris@559
|
578 // Set up our client.
|
Chris@559
|
579 MIDIClientRef client;
|
Chris@1397
|
580 CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
|
Chris@1397
|
581 OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
|
Chris@559
|
582 if ( result != noErr ) {
|
Chris@1397
|
583 errorString_ = "MidiInCore::initialize: error creating OS-X MIDI client object.";
|
Chris@1397
|
584 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
585 return;
|
Chris@559
|
586 }
|
Chris@559
|
587
|
Chris@559
|
588 // Save our api-specific connection information.
|
Chris@559
|
589 CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
|
Chris@559
|
590 data->client = client;
|
Chris@559
|
591 data->endpoint = 0;
|
Chris@559
|
592 apiData_ = (void *) data;
|
Chris@559
|
593 inputData_.apiData = (void *) data;
|
Chris@1397
|
594 CFRelease(name);
|
Chris@559
|
595 }
|
Chris@559
|
596
|
Chris@1397
|
597 void MidiInCore :: openPort( unsigned int portNumber, const std::string portName )
|
Chris@559
|
598 {
|
Chris@559
|
599 if ( connected_ ) {
|
Chris@1397
|
600 errorString_ = "MidiInCore::openPort: a valid connection already exists!";
|
Chris@1397
|
601 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
602 return;
|
Chris@559
|
603 }
|
Chris@559
|
604
|
Chris@1397
|
605 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
|
Chris@559
|
606 unsigned int nSrc = MIDIGetNumberOfSources();
|
Chris@559
|
607 if (nSrc < 1) {
|
Chris@1397
|
608 errorString_ = "MidiInCore::openPort: no MIDI input sources found!";
|
Chris@1397
|
609 error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
|
Chris@1397
|
610 return;
|
Chris@559
|
611 }
|
Chris@559
|
612
|
Chris@559
|
613 if ( portNumber >= nSrc ) {
|
Chris@1397
|
614 std::ostringstream ost;
|
Chris@1397
|
615 ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@559
|
616 errorString_ = ost.str();
|
Chris@1397
|
617 error( RtMidiError::INVALID_PARAMETER, errorString_ );
|
Chris@1397
|
618 return;
|
Chris@559
|
619 }
|
Chris@559
|
620
|
Chris@559
|
621 MIDIPortRef port;
|
Chris@559
|
622 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@565
|
623 OSStatus result = MIDIInputPortCreate( data->client,
|
Chris@565
|
624 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
|
Chris@565
|
625 midiInputCallback, (void *)&inputData_, &port );
|
Chris@559
|
626 if ( result != noErr ) {
|
Chris@559
|
627 MIDIClientDispose( data->client );
|
Chris@1397
|
628 errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port.";
|
Chris@1397
|
629 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
630 return;
|
Chris@559
|
631 }
|
Chris@559
|
632
|
Chris@559
|
633 // Get the desired input source identifier.
|
Chris@559
|
634 MIDIEndpointRef endpoint = MIDIGetSource( portNumber );
|
Chris@1397
|
635 if ( endpoint == 0 ) {
|
Chris@559
|
636 MIDIPortDispose( port );
|
Chris@559
|
637 MIDIClientDispose( data->client );
|
Chris@1397
|
638 errorString_ = "MidiInCore::openPort: error getting MIDI input source reference.";
|
Chris@1397
|
639 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
640 return;
|
Chris@559
|
641 }
|
Chris@559
|
642
|
Chris@559
|
643 // Make the connection.
|
Chris@559
|
644 result = MIDIPortConnectSource( port, endpoint, NULL );
|
Chris@559
|
645 if ( result != noErr ) {
|
Chris@559
|
646 MIDIPortDispose( port );
|
Chris@559
|
647 MIDIClientDispose( data->client );
|
Chris@1397
|
648 errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port.";
|
Chris@1397
|
649 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
650 return;
|
Chris@559
|
651 }
|
Chris@559
|
652
|
Chris@559
|
653 // Save our api-specific port information.
|
Chris@559
|
654 data->port = port;
|
Chris@559
|
655
|
Chris@559
|
656 connected_ = true;
|
Chris@559
|
657 }
|
Chris@559
|
658
|
Chris@1397
|
659 void MidiInCore :: openVirtualPort( const std::string portName )
|
Chris@559
|
660 {
|
Chris@559
|
661 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@559
|
662
|
Chris@559
|
663 // Create a virtual MIDI input destination.
|
Chris@559
|
664 MIDIEndpointRef endpoint;
|
Chris@559
|
665 OSStatus result = MIDIDestinationCreate( data->client,
|
Chris@559
|
666 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
|
Chris@559
|
667 midiInputCallback, (void *)&inputData_, &endpoint );
|
Chris@559
|
668 if ( result != noErr ) {
|
Chris@1397
|
669 errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination.";
|
Chris@1397
|
670 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
671 return;
|
Chris@559
|
672 }
|
Chris@559
|
673
|
Chris@559
|
674 // Save our api-specific connection information.
|
Chris@559
|
675 data->endpoint = endpoint;
|
Chris@559
|
676 }
|
Chris@559
|
677
|
Chris@1397
|
678 void MidiInCore :: closePort( void )
|
Chris@559
|
679 {
|
Chris@1397
|
680 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@1397
|
681
|
Chris@1397
|
682 if ( data->endpoint ) {
|
Chris@1397
|
683 MIDIEndpointDispose( data->endpoint );
|
Chris@1397
|
684 }
|
Chris@1397
|
685
|
Chris@1397
|
686 if ( data->port ) {
|
Chris@559
|
687 MIDIPortDispose( data->port );
|
Chris@559
|
688 }
|
Chris@1397
|
689
|
Chris@1397
|
690 connected_ = false;
|
Chris@559
|
691 }
|
Chris@559
|
692
|
Chris@1397
|
693 unsigned int MidiInCore :: getPortCount()
|
Chris@1397
|
694 {
|
Chris@1397
|
695 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
|
Chris@1397
|
696 return MIDIGetNumberOfSources();
|
Chris@1397
|
697 }
|
Chris@1397
|
698
|
Chris@1397
|
699 // This function was submitted by Douglas Casey Tucker and apparently
|
Chris@1397
|
700 // derived largely from PortMidi.
|
Chris@1397
|
701 CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal )
|
Chris@1397
|
702 {
|
Chris@1397
|
703 CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
|
Chris@1397
|
704 CFStringRef str;
|
Chris@1397
|
705
|
Chris@1397
|
706 // Begin with the endpoint's name.
|
Chris@1397
|
707 str = NULL;
|
Chris@1397
|
708 MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str );
|
Chris@1397
|
709 if ( str != NULL ) {
|
Chris@1397
|
710 CFStringAppend( result, str );
|
Chris@1397
|
711 CFRelease( str );
|
Chris@1397
|
712 }
|
Chris@1397
|
713
|
Chris@1397
|
714 MIDIEntityRef entity = 0;
|
Chris@1397
|
715 MIDIEndpointGetEntity( endpoint, &entity );
|
Chris@1397
|
716 if ( entity == 0 )
|
Chris@1397
|
717 // probably virtual
|
Chris@1397
|
718 return result;
|
Chris@1397
|
719
|
Chris@1397
|
720 if ( CFStringGetLength( result ) == 0 ) {
|
Chris@1397
|
721 // endpoint name has zero length -- try the entity
|
Chris@1397
|
722 str = NULL;
|
Chris@1397
|
723 MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str );
|
Chris@1397
|
724 if ( str != NULL ) {
|
Chris@1397
|
725 CFStringAppend( result, str );
|
Chris@1397
|
726 CFRelease( str );
|
Chris@1397
|
727 }
|
Chris@1397
|
728 }
|
Chris@1397
|
729 // now consider the device's name
|
Chris@1397
|
730 MIDIDeviceRef device = 0;
|
Chris@1397
|
731 MIDIEntityGetDevice( entity, &device );
|
Chris@1397
|
732 if ( device == 0 )
|
Chris@1397
|
733 return result;
|
Chris@1397
|
734
|
Chris@1397
|
735 str = NULL;
|
Chris@1397
|
736 MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str );
|
Chris@1397
|
737 if ( CFStringGetLength( result ) == 0 ) {
|
Chris@1397
|
738 CFRelease( result );
|
Chris@1397
|
739 return str;
|
Chris@1397
|
740 }
|
Chris@1397
|
741 if ( str != NULL ) {
|
Chris@1397
|
742 // if an external device has only one entity, throw away
|
Chris@1397
|
743 // the endpoint name and just use the device name
|
Chris@1397
|
744 if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) {
|
Chris@1397
|
745 CFRelease( result );
|
Chris@1397
|
746 return str;
|
Chris@1397
|
747 } else {
|
Chris@1397
|
748 if ( CFStringGetLength( str ) == 0 ) {
|
Chris@1397
|
749 CFRelease( str );
|
Chris@1397
|
750 return result;
|
Chris@1397
|
751 }
|
Chris@1397
|
752 // does the entity name already start with the device name?
|
Chris@1397
|
753 // (some drivers do this though they shouldn't)
|
Chris@1397
|
754 // if so, do not prepend
|
Chris@1397
|
755 if ( CFStringCompareWithOptions( result, /* endpoint name */
|
Chris@1397
|
756 str /* device name */,
|
Chris@1397
|
757 CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) {
|
Chris@1397
|
758 // prepend the device name to the entity name
|
Chris@1397
|
759 if ( CFStringGetLength( result ) > 0 )
|
Chris@1397
|
760 CFStringInsert( result, 0, CFSTR(" ") );
|
Chris@1397
|
761 CFStringInsert( result, 0, str );
|
Chris@1397
|
762 }
|
Chris@1397
|
763 CFRelease( str );
|
Chris@1397
|
764 }
|
Chris@1397
|
765 }
|
Chris@1397
|
766 return result;
|
Chris@1397
|
767 }
|
Chris@1397
|
768
|
Chris@1397
|
769 // This function was submitted by Douglas Casey Tucker and apparently
|
Chris@1397
|
770 // derived largely from PortMidi.
|
Chris@1397
|
771 static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint )
|
Chris@1397
|
772 {
|
Chris@1397
|
773 CFMutableStringRef result = CFStringCreateMutable( NULL, 0 );
|
Chris@1397
|
774 CFStringRef str;
|
Chris@1397
|
775 OSStatus err;
|
Chris@1397
|
776 int i;
|
Chris@1397
|
777
|
Chris@1397
|
778 // Does the endpoint have connections?
|
Chris@1397
|
779 CFDataRef connections = NULL;
|
Chris@1397
|
780 int nConnected = 0;
|
Chris@1397
|
781 bool anyStrings = false;
|
Chris@1397
|
782 err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections );
|
Chris@1397
|
783 if ( connections != NULL ) {
|
Chris@1397
|
784 // It has connections, follow them
|
Chris@1397
|
785 // Concatenate the names of all connected devices
|
Chris@1397
|
786 nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID);
|
Chris@1397
|
787 if ( nConnected ) {
|
Chris@1397
|
788 const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
|
Chris@1397
|
789 for ( i=0; i<nConnected; ++i, ++pid ) {
|
Chris@1397
|
790 MIDIUniqueID id = EndianS32_BtoN( *pid );
|
Chris@1397
|
791 MIDIObjectRef connObject;
|
Chris@1397
|
792 MIDIObjectType connObjectType;
|
Chris@1397
|
793 err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType );
|
Chris@1397
|
794 if ( err == noErr ) {
|
Chris@1397
|
795 if ( connObjectType == kMIDIObjectType_ExternalSource ||
|
Chris@1397
|
796 connObjectType == kMIDIObjectType_ExternalDestination ) {
|
Chris@1397
|
797 // Connected to an external device's endpoint (10.3 and later).
|
Chris@1397
|
798 str = EndpointName( (MIDIEndpointRef)(connObject), true );
|
Chris@1397
|
799 } else {
|
Chris@1397
|
800 // Connected to an external device (10.2) (or something else, catch-
|
Chris@1397
|
801 str = NULL;
|
Chris@1397
|
802 MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str );
|
Chris@1397
|
803 }
|
Chris@1397
|
804 if ( str != NULL ) {
|
Chris@1397
|
805 if ( anyStrings )
|
Chris@1397
|
806 CFStringAppend( result, CFSTR(", ") );
|
Chris@1397
|
807 else anyStrings = true;
|
Chris@1397
|
808 CFStringAppend( result, str );
|
Chris@1397
|
809 CFRelease( str );
|
Chris@1397
|
810 }
|
Chris@1397
|
811 }
|
Chris@1397
|
812 }
|
Chris@1397
|
813 }
|
Chris@1397
|
814 CFRelease( connections );
|
Chris@1397
|
815 }
|
Chris@1397
|
816 if ( anyStrings )
|
Chris@1397
|
817 return result;
|
Chris@1397
|
818
|
Chris@1397
|
819 CFRelease( result );
|
Chris@1397
|
820
|
Chris@1397
|
821 // Here, either the endpoint had no connections, or we failed to obtain names
|
Chris@1397
|
822 return EndpointName( endpoint, false );
|
Chris@1397
|
823 }
|
Chris@1397
|
824
|
Chris@1397
|
825 std::string MidiInCore :: getPortName( unsigned int portNumber )
|
Chris@1397
|
826 {
|
Chris@1397
|
827 CFStringRef nameRef;
|
Chris@1397
|
828 MIDIEndpointRef portRef;
|
Chris@1397
|
829 char name[128];
|
Chris@1397
|
830
|
Chris@1397
|
831 std::string stringName;
|
Chris@1397
|
832 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
|
Chris@1397
|
833 if ( portNumber >= MIDIGetNumberOfSources() ) {
|
Chris@1397
|
834 std::ostringstream ost;
|
Chris@1397
|
835 ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@1397
|
836 errorString_ = ost.str();
|
Chris@1397
|
837 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
838 return stringName;
|
Chris@1397
|
839 }
|
Chris@1397
|
840
|
Chris@1397
|
841 portRef = MIDIGetSource( portNumber );
|
Chris@1397
|
842 nameRef = ConnectedEndpointName(portRef);
|
Chris@1397
|
843 CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());
|
Chris@1397
|
844 CFRelease( nameRef );
|
Chris@1397
|
845
|
Chris@1397
|
846 return stringName = name;
|
Chris@1397
|
847 }
|
Chris@1397
|
848
|
Chris@1397
|
849 //*********************************************************************//
|
Chris@1397
|
850 // API: OS-X
|
Chris@1397
|
851 // Class Definitions: MidiOutCore
|
Chris@1397
|
852 //*********************************************************************//
|
Chris@1397
|
853
|
Chris@1397
|
854 MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi()
|
Chris@1397
|
855 {
|
Chris@1397
|
856 initialize( clientName );
|
Chris@1397
|
857 }
|
Chris@1397
|
858
|
Chris@1397
|
859 MidiOutCore :: ~MidiOutCore( void )
|
Chris@559
|
860 {
|
Chris@559
|
861 // Close a connection if it exists.
|
Chris@559
|
862 closePort();
|
Chris@559
|
863
|
Chris@559
|
864 // Cleanup.
|
Chris@559
|
865 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@559
|
866 MIDIClientDispose( data->client );
|
Chris@559
|
867 if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );
|
Chris@559
|
868 delete data;
|
Chris@559
|
869 }
|
Chris@559
|
870
|
Chris@1397
|
871 void MidiOutCore :: initialize( const std::string& clientName )
|
Chris@559
|
872 {
|
Chris@559
|
873 // Set up our client.
|
Chris@559
|
874 MIDIClientRef client;
|
Chris@1397
|
875 CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );
|
Chris@1397
|
876 OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );
|
Chris@559
|
877 if ( result != noErr ) {
|
Chris@1397
|
878 errorString_ = "MidiOutCore::initialize: error creating OS-X MIDI client object.";
|
Chris@1397
|
879 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
880 return;
|
Chris@559
|
881 }
|
Chris@559
|
882
|
Chris@559
|
883 // Save our api-specific connection information.
|
Chris@559
|
884 CoreMidiData *data = (CoreMidiData *) new CoreMidiData;
|
Chris@559
|
885 data->client = client;
|
Chris@559
|
886 data->endpoint = 0;
|
Chris@559
|
887 apiData_ = (void *) data;
|
Chris@1397
|
888 CFRelease( name );
|
Chris@559
|
889 }
|
Chris@559
|
890
|
Chris@1397
|
891 unsigned int MidiOutCore :: getPortCount()
|
Chris@1397
|
892 {
|
Chris@1397
|
893 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
|
Chris@1397
|
894 return MIDIGetNumberOfDestinations();
|
Chris@1397
|
895 }
|
Chris@1397
|
896
|
Chris@1397
|
897 std::string MidiOutCore :: getPortName( unsigned int portNumber )
|
Chris@1397
|
898 {
|
Chris@1397
|
899 CFStringRef nameRef;
|
Chris@1397
|
900 MIDIEndpointRef portRef;
|
Chris@1397
|
901 char name[128];
|
Chris@1397
|
902
|
Chris@1397
|
903 std::string stringName;
|
Chris@1397
|
904 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
|
Chris@1397
|
905 if ( portNumber >= MIDIGetNumberOfDestinations() ) {
|
Chris@1397
|
906 std::ostringstream ost;
|
Chris@1397
|
907 ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@1397
|
908 errorString_ = ost.str();
|
Chris@1397
|
909 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
910 return stringName;
|
Chris@1397
|
911 }
|
Chris@1397
|
912
|
Chris@1397
|
913 portRef = MIDIGetDestination( portNumber );
|
Chris@1397
|
914 nameRef = ConnectedEndpointName(portRef);
|
Chris@1397
|
915 CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());
|
Chris@1397
|
916 CFRelease( nameRef );
|
Chris@1397
|
917
|
Chris@1397
|
918 return stringName = name;
|
Chris@1397
|
919 }
|
Chris@1397
|
920
|
Chris@1397
|
921 void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName )
|
Chris@559
|
922 {
|
Chris@559
|
923 if ( connected_ ) {
|
Chris@1397
|
924 errorString_ = "MidiOutCore::openPort: a valid connection already exists!";
|
Chris@1397
|
925 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
926 return;
|
Chris@559
|
927 }
|
Chris@559
|
928
|
Chris@1397
|
929 CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );
|
Chris@559
|
930 unsigned int nDest = MIDIGetNumberOfDestinations();
|
Chris@559
|
931 if (nDest < 1) {
|
Chris@1397
|
932 errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!";
|
Chris@1397
|
933 error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
|
Chris@1397
|
934 return;
|
Chris@559
|
935 }
|
Chris@559
|
936
|
Chris@559
|
937 if ( portNumber >= nDest ) {
|
Chris@1397
|
938 std::ostringstream ost;
|
Chris@1397
|
939 ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@559
|
940 errorString_ = ost.str();
|
Chris@1397
|
941 error( RtMidiError::INVALID_PARAMETER, errorString_ );
|
Chris@1397
|
942 return;
|
Chris@559
|
943 }
|
Chris@559
|
944
|
Chris@559
|
945 MIDIPortRef port;
|
Chris@559
|
946 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@565
|
947 OSStatus result = MIDIOutputPortCreate( data->client,
|
Chris@565
|
948 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
|
Chris@565
|
949 &port );
|
Chris@559
|
950 if ( result != noErr ) {
|
Chris@559
|
951 MIDIClientDispose( data->client );
|
Chris@1397
|
952 errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port.";
|
Chris@1397
|
953 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
954 return;
|
Chris@559
|
955 }
|
Chris@559
|
956
|
Chris@559
|
957 // Get the desired output port identifier.
|
Chris@559
|
958 MIDIEndpointRef destination = MIDIGetDestination( portNumber );
|
Chris@1397
|
959 if ( destination == 0 ) {
|
Chris@559
|
960 MIDIPortDispose( port );
|
Chris@559
|
961 MIDIClientDispose( data->client );
|
Chris@1397
|
962 errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference.";
|
Chris@1397
|
963 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
964 return;
|
Chris@559
|
965 }
|
Chris@559
|
966
|
Chris@559
|
967 // Save our api-specific connection information.
|
Chris@559
|
968 data->port = port;
|
Chris@559
|
969 data->destinationId = destination;
|
Chris@559
|
970 connected_ = true;
|
Chris@559
|
971 }
|
Chris@559
|
972
|
Chris@1397
|
973 void MidiOutCore :: closePort( void )
|
Chris@559
|
974 {
|
Chris@559
|
975 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@559
|
976
|
Chris@559
|
977 if ( data->endpoint ) {
|
Chris@1397
|
978 MIDIEndpointDispose( data->endpoint );
|
Chris@1397
|
979 }
|
Chris@1397
|
980
|
Chris@1397
|
981 if ( data->port ) {
|
Chris@1397
|
982 MIDIPortDispose( data->port );
|
Chris@1397
|
983 }
|
Chris@1397
|
984
|
Chris@1397
|
985 connected_ = false;
|
Chris@1397
|
986 }
|
Chris@1397
|
987
|
Chris@1397
|
988 void MidiOutCore :: openVirtualPort( std::string portName )
|
Chris@1397
|
989 {
|
Chris@1397
|
990 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@1397
|
991
|
Chris@1397
|
992 if ( data->endpoint ) {
|
Chris@1397
|
993 errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!";
|
Chris@1397
|
994 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
995 return;
|
Chris@559
|
996 }
|
Chris@559
|
997
|
Chris@559
|
998 // Create a virtual MIDI output source.
|
Chris@559
|
999 MIDIEndpointRef endpoint;
|
Chris@559
|
1000 OSStatus result = MIDISourceCreate( data->client,
|
Chris@559
|
1001 CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),
|
Chris@559
|
1002 &endpoint );
|
Chris@559
|
1003 if ( result != noErr ) {
|
Chris@1397
|
1004 errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source.";
|
Chris@1397
|
1005 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1006 return;
|
Chris@559
|
1007 }
|
Chris@559
|
1008
|
Chris@559
|
1009 // Save our api-specific connection information.
|
Chris@559
|
1010 data->endpoint = endpoint;
|
Chris@559
|
1011 }
|
Chris@559
|
1012
|
Chris@1397
|
1013 void MidiOutCore :: sendMessage( std::vector<unsigned char> *message )
|
Chris@559
|
1014 {
|
Chris@1397
|
1015 // We use the MIDISendSysex() function to asynchronously send sysex
|
Chris@1397
|
1016 // messages. Otherwise, we use a single CoreMidi MIDIPacket.
|
Chris@559
|
1017 unsigned int nBytes = message->size();
|
Chris@565
|
1018 if ( nBytes == 0 ) {
|
Chris@1397
|
1019 errorString_ = "MidiOutCore::sendMessage: no data in message argument!";
|
Chris@1397
|
1020 error( RtMidiError::WARNING, errorString_ );
|
Chris@565
|
1021 return;
|
Chris@565
|
1022 }
|
Chris@559
|
1023
|
Chris@1397
|
1024 MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
|
Chris@1397
|
1025 CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
|
Chris@1397
|
1026 OSStatus result;
|
Chris@1397
|
1027
|
Chris@1397
|
1028 if ( message->at(0) != 0xF0 && nBytes > 3 ) {
|
Chris@1397
|
1029 errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";
|
Chris@1397
|
1030 error( RtMidiError::WARNING, errorString_ );
|
Chris@565
|
1031 return;
|
Chris@565
|
1032 }
|
Chris@565
|
1033
|
Chris@1397
|
1034 Byte buffer[nBytes+(sizeof(MIDIPacketList))];
|
Chris@1397
|
1035 ByteCount listSize = sizeof(buffer);
|
Chris@1397
|
1036 MIDIPacketList *packetList = (MIDIPacketList*)buffer;
|
Chris@1397
|
1037 MIDIPacket *packet = MIDIPacketListInit( packetList );
|
Chris@1397
|
1038
|
Chris@1397
|
1039 ByteCount remainingBytes = nBytes;
|
Chris@1397
|
1040 while (remainingBytes && packet) {
|
Chris@1397
|
1041 ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket
|
Chris@1397
|
1042 const Byte* dataStartPtr = (const Byte *) &message->at( nBytes - remainingBytes );
|
Chris@1397
|
1043 packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr);
|
Chris@1397
|
1044 remainingBytes -= bytesForPacket;
|
Chris@1397
|
1045 }
|
Chris@1397
|
1046
|
Chris@1397
|
1047 if ( !packet ) {
|
Chris@1397
|
1048 errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";
|
Chris@1397
|
1049 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1050 return;
|
Chris@1397
|
1051 }
|
Chris@1397
|
1052
|
Chris@1397
|
1053 // Send to any destinations that may have connected to us.
|
Chris@1397
|
1054 if ( data->endpoint ) {
|
Chris@1397
|
1055 result = MIDIReceived( data->endpoint, packetList );
|
Chris@1397
|
1056 if ( result != noErr ) {
|
Chris@1397
|
1057 errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";
|
Chris@1397
|
1058 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
1059 }
|
Chris@1397
|
1060 }
|
Chris@1397
|
1061
|
Chris@1397
|
1062 // And send to an explicit destination port if we're connected.
|
Chris@1397
|
1063 if ( connected_ ) {
|
Chris@1397
|
1064 result = MIDISend( data->port, data->destinationId, packetList );
|
Chris@1397
|
1065 if ( result != noErr ) {
|
Chris@1397
|
1066 errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";
|
Chris@1397
|
1067 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
1068 }
|
Chris@559
|
1069 }
|
Chris@559
|
1070 }
|
Chris@559
|
1071
|
Chris@559
|
1072 #endif // __MACOSX_CORE__
|
Chris@559
|
1073
|
Chris@559
|
1074
|
Chris@559
|
1075 //*********************************************************************//
|
Chris@559
|
1076 // API: LINUX ALSA SEQUENCER
|
Chris@559
|
1077 //*********************************************************************//
|
Chris@559
|
1078
|
Chris@559
|
1079 // API information found at:
|
Chris@559
|
1080 // - http://www.alsa-project.org/documentation.php#Library
|
Chris@559
|
1081
|
Chris@1397
|
1082 #if defined(__LINUX_ALSA__)
|
Chris@559
|
1083
|
Chris@559
|
1084 // The ALSA Sequencer API is based on the use of a callback function for
|
Chris@559
|
1085 // MIDI input.
|
Chris@559
|
1086 //
|
Chris@559
|
1087 // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
|
Chris@559
|
1088 // time stamps and other assorted fixes!!!
|
Chris@559
|
1089
|
Chris@1397
|
1090 // If you don't need timestamping for incoming MIDI events, define the
|
Chris@1397
|
1091 // preprocessor definition AVOID_TIMESTAMPING to save resources
|
Chris@1397
|
1092 // associated with the ALSA sequencer queues.
|
Chris@1397
|
1093
|
Chris@559
|
1094 #include <pthread.h>
|
Chris@559
|
1095 #include <sys/time.h>
|
Chris@559
|
1096
|
Chris@559
|
1097 // ALSA header file.
|
Chris@559
|
1098 #include <alsa/asoundlib.h>
|
Chris@559
|
1099
|
Chris@559
|
1100 // A structure to hold variables related to the ALSA API
|
Chris@559
|
1101 // implementation.
|
Chris@559
|
1102 struct AlsaMidiData {
|
Chris@559
|
1103 snd_seq_t *seq;
|
Chris@1397
|
1104 unsigned int portNum;
|
Chris@559
|
1105 int vport;
|
Chris@559
|
1106 snd_seq_port_subscribe_t *subscription;
|
Chris@559
|
1107 snd_midi_event_t *coder;
|
Chris@559
|
1108 unsigned int bufferSize;
|
Chris@559
|
1109 unsigned char *buffer;
|
Chris@559
|
1110 pthread_t thread;
|
Chris@1397
|
1111 pthread_t dummy_thread_id;
|
Chris@559
|
1112 unsigned long long lastTime;
|
Chris@559
|
1113 int queue_id; // an input queue is needed to get timestamped events
|
Chris@1397
|
1114 int trigger_fds[2];
|
Chris@559
|
1115 };
|
Chris@559
|
1116
|
Chris@559
|
1117 #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
|
Chris@559
|
1118
|
Chris@559
|
1119 //*********************************************************************//
|
Chris@559
|
1120 // API: LINUX ALSA
|
Chris@1397
|
1121 // Class Definitions: MidiInAlsa
|
Chris@559
|
1122 //*********************************************************************//
|
Chris@559
|
1123
|
Chris@1397
|
1124 static void *alsaMidiHandler( void *ptr )
|
Chris@559
|
1125 {
|
Chris@1397
|
1126 MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr);
|
Chris@559
|
1127 AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);
|
Chris@559
|
1128
|
Chris@559
|
1129 long nBytes;
|
Chris@559
|
1130 unsigned long long time, lastTime;
|
Chris@559
|
1131 bool continueSysex = false;
|
Chris@1397
|
1132 bool doDecode = false;
|
Chris@1397
|
1133 MidiInApi::MidiMessage message;
|
Chris@1397
|
1134 int poll_fd_count;
|
Chris@1397
|
1135 struct pollfd *poll_fds;
|
Chris@559
|
1136
|
Chris@559
|
1137 snd_seq_event_t *ev;
|
Chris@559
|
1138 int result;
|
Chris@559
|
1139 apiData->bufferSize = 32;
|
Chris@559
|
1140 result = snd_midi_event_new( 0, &apiData->coder );
|
Chris@559
|
1141 if ( result < 0 ) {
|
Chris@559
|
1142 data->doInput = false;
|
Chris@1397
|
1143 std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";
|
Chris@559
|
1144 return 0;
|
Chris@559
|
1145 }
|
Chris@559
|
1146 unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
|
Chris@559
|
1147 if ( buffer == NULL ) {
|
Chris@559
|
1148 data->doInput = false;
|
Chris@1397
|
1149 snd_midi_event_free( apiData->coder );
|
Chris@1397
|
1150 apiData->coder = 0;
|
Chris@1397
|
1151 std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n";
|
Chris@559
|
1152 return 0;
|
Chris@559
|
1153 }
|
Chris@559
|
1154 snd_midi_event_init( apiData->coder );
|
Chris@559
|
1155 snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages
|
Chris@559
|
1156
|
Chris@1397
|
1157 poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;
|
Chris@1397
|
1158 poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));
|
Chris@1397
|
1159 snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );
|
Chris@1397
|
1160 poll_fds[0].fd = apiData->trigger_fds[0];
|
Chris@1397
|
1161 poll_fds[0].events = POLLIN;
|
Chris@1397
|
1162
|
Chris@559
|
1163 while ( data->doInput ) {
|
Chris@559
|
1164
|
Chris@559
|
1165 if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {
|
Chris@1397
|
1166 // No data pending
|
Chris@1397
|
1167 if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {
|
Chris@1397
|
1168 if ( poll_fds[0].revents & POLLIN ) {
|
Chris@1397
|
1169 bool dummy;
|
Chris@1397
|
1170 int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );
|
Chris@1397
|
1171 (void) res;
|
Chris@1397
|
1172 }
|
Chris@1397
|
1173 }
|
Chris@559
|
1174 continue;
|
Chris@559
|
1175 }
|
Chris@559
|
1176
|
Chris@559
|
1177 // If here, there should be data.
|
Chris@559
|
1178 result = snd_seq_event_input( apiData->seq, &ev );
|
Chris@559
|
1179 if ( result == -ENOSPC ) {
|
Chris@1397
|
1180 std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";
|
Chris@559
|
1181 continue;
|
Chris@559
|
1182 }
|
Chris@559
|
1183 else if ( result <= 0 ) {
|
Chris@1397
|
1184 std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";
|
Chris@1397
|
1185 perror("System reports");
|
Chris@559
|
1186 continue;
|
Chris@559
|
1187 }
|
Chris@559
|
1188
|
Chris@559
|
1189 // This is a bit weird, but we now have to decode an ALSA MIDI
|
Chris@559
|
1190 // event (back) into MIDI bytes. We'll ignore non-MIDI types.
|
Chris@1397
|
1191 if ( !continueSysex ) message.bytes.clear();
|
Chris@1397
|
1192
|
Chris@1397
|
1193 doDecode = false;
|
Chris@559
|
1194 switch ( ev->type ) {
|
Chris@559
|
1195
|
Chris@1397
|
1196 case SND_SEQ_EVENT_PORT_SUBSCRIBED:
|
Chris@559
|
1197 #if defined(__RTMIDI_DEBUG__)
|
Chris@1397
|
1198 std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";
|
Chris@559
|
1199 #endif
|
Chris@559
|
1200 break;
|
Chris@559
|
1201
|
Chris@1397
|
1202 case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
|
Chris@565
|
1203 #if defined(__RTMIDI_DEBUG__)
|
Chris@1397
|
1204 std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";
|
Chris@1397
|
1205 std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"
|
Chris@1397
|
1206 << (int) ev->data.connect.sender.port
|
Chris@1397
|
1207 << ", dest = " << (int) ev->data.connect.dest.client << ":"
|
Chris@1397
|
1208 << (int) ev->data.connect.dest.port
|
Chris@1397
|
1209 << std::endl;
|
Chris@565
|
1210 #endif
|
Chris@559
|
1211 break;
|
Chris@559
|
1212
|
Chris@559
|
1213 case SND_SEQ_EVENT_QFRAME: // MIDI time code
|
Chris@1397
|
1214 if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
|
Chris@1397
|
1215 break;
|
Chris@1397
|
1216
|
Chris@1397
|
1217 case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick
|
Chris@1397
|
1218 if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
|
Chris@1397
|
1219 break;
|
Chris@1397
|
1220
|
Chris@1397
|
1221 case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick
|
Chris@1397
|
1222 if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
|
Chris@1397
|
1223 break;
|
Chris@559
|
1224
|
Chris@559
|
1225 case SND_SEQ_EVENT_SENSING: // Active sensing
|
Chris@1397
|
1226 if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;
|
Chris@1397
|
1227 break;
|
Chris@559
|
1228
|
Chris@559
|
1229 case SND_SEQ_EVENT_SYSEX:
|
Chris@559
|
1230 if ( (data->ignoreFlags & 0x01) ) break;
|
Chris@559
|
1231 if ( ev->data.ext.len > apiData->bufferSize ) {
|
Chris@559
|
1232 apiData->bufferSize = ev->data.ext.len;
|
Chris@559
|
1233 free( buffer );
|
Chris@559
|
1234 buffer = (unsigned char *) malloc( apiData->bufferSize );
|
Chris@559
|
1235 if ( buffer == NULL ) {
|
Chris@559
|
1236 data->doInput = false;
|
Chris@1397
|
1237 std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";
|
Chris@559
|
1238 break;
|
Chris@559
|
1239 }
|
Chris@559
|
1240 }
|
Chris@559
|
1241
|
Chris@559
|
1242 default:
|
Chris@1397
|
1243 doDecode = true;
|
Chris@1397
|
1244 }
|
Chris@1397
|
1245
|
Chris@1397
|
1246 if ( doDecode ) {
|
Chris@1397
|
1247
|
Chris@559
|
1248 nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );
|
Chris@1397
|
1249 if ( nBytes > 0 ) {
|
Chris@1397
|
1250 // The ALSA sequencer has a maximum buffer size for MIDI sysex
|
Chris@1397
|
1251 // events of 256 bytes. If a device sends sysex messages larger
|
Chris@1397
|
1252 // than this, they are segmented into 256 byte chunks. So,
|
Chris@1397
|
1253 // we'll watch for this and concatenate sysex chunks into a
|
Chris@1397
|
1254 // single sysex message if necessary.
|
Chris@1397
|
1255 if ( !continueSysex )
|
Chris@1397
|
1256 message.bytes.assign( buffer, &buffer[nBytes] );
|
Chris@1397
|
1257 else
|
Chris@1397
|
1258 message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
|
Chris@1397
|
1259
|
Chris@1397
|
1260 continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
|
Chris@1397
|
1261 if ( !continueSysex ) {
|
Chris@1397
|
1262
|
Chris@1397
|
1263 // Calculate the time stamp:
|
Chris@1397
|
1264 message.timeStamp = 0.0;
|
Chris@1397
|
1265
|
Chris@1397
|
1266 // Method 1: Use the system time.
|
Chris@1397
|
1267 //(void)gettimeofday(&tv, (struct timezone *)NULL);
|
Chris@1397
|
1268 //time = (tv.tv_sec * 1000000) + tv.tv_usec;
|
Chris@1397
|
1269
|
Chris@1397
|
1270 // Method 2: Use the ALSA sequencer event time data.
|
Chris@1397
|
1271 // (thanks to Pedro Lopez-Cabanillas!).
|
Chris@1397
|
1272 time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );
|
Chris@1397
|
1273 lastTime = time;
|
Chris@1397
|
1274 time -= apiData->lastTime;
|
Chris@1397
|
1275 apiData->lastTime = lastTime;
|
Chris@1397
|
1276 if ( data->firstMessage == true )
|
Chris@1397
|
1277 data->firstMessage = false;
|
Chris@1397
|
1278 else
|
Chris@1397
|
1279 message.timeStamp = time * 0.000001;
|
Chris@1397
|
1280 }
|
Chris@1397
|
1281 else {
|
Chris@559
|
1282 #if defined(__RTMIDI_DEBUG__)
|
Chris@1397
|
1283 std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
|
Chris@559
|
1284 #endif
|
Chris@1397
|
1285 }
|
Chris@559
|
1286 }
|
Chris@559
|
1287 }
|
Chris@559
|
1288
|
Chris@1397
|
1289 snd_seq_free_event( ev );
|
Chris@1397
|
1290 if ( message.bytes.size() == 0 || continueSysex ) continue;
|
Chris@1397
|
1291
|
Chris@1397
|
1292 if ( data->usingCallback ) {
|
Chris@559
|
1293 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
|
Chris@559
|
1294 callback( message.timeStamp, &message.bytes, data->userData );
|
Chris@559
|
1295 }
|
Chris@559
|
1296 else {
|
Chris@559
|
1297 // As long as we haven't reached our queue size limit, push the message.
|
Chris@1397
|
1298 if ( data->queue.size < data->queue.ringSize ) {
|
Chris@1397
|
1299 data->queue.ring[data->queue.back++] = message;
|
Chris@1397
|
1300 if ( data->queue.back == data->queue.ringSize )
|
Chris@1397
|
1301 data->queue.back = 0;
|
Chris@1397
|
1302 data->queue.size++;
|
Chris@1397
|
1303 }
|
Chris@559
|
1304 else
|
Chris@1397
|
1305 std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";
|
Chris@559
|
1306 }
|
Chris@559
|
1307 }
|
Chris@559
|
1308
|
Chris@559
|
1309 if ( buffer ) free( buffer );
|
Chris@559
|
1310 snd_midi_event_free( apiData->coder );
|
Chris@559
|
1311 apiData->coder = 0;
|
Chris@1397
|
1312 apiData->thread = apiData->dummy_thread_id;
|
Chris@559
|
1313 return 0;
|
Chris@559
|
1314 }
|
Chris@559
|
1315
|
Chris@1397
|
1316 MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
|
Chris@1397
|
1317 {
|
Chris@1397
|
1318 initialize( clientName );
|
Chris@1397
|
1319 }
|
Chris@1397
|
1320
|
Chris@1397
|
1321 MidiInAlsa :: ~MidiInAlsa()
|
Chris@1397
|
1322 {
|
Chris@1397
|
1323 // Close a connection if it exists.
|
Chris@1397
|
1324 closePort();
|
Chris@1397
|
1325
|
Chris@1397
|
1326 // Shutdown the input thread.
|
Chris@1397
|
1327 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1328 if ( inputData_.doInput ) {
|
Chris@1397
|
1329 inputData_.doInput = false;
|
Chris@1397
|
1330 int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
|
Chris@1397
|
1331 (void) res;
|
Chris@1397
|
1332 if ( !pthread_equal(data->thread, data->dummy_thread_id) )
|
Chris@1397
|
1333 pthread_join( data->thread, NULL );
|
Chris@1397
|
1334 }
|
Chris@1397
|
1335
|
Chris@1397
|
1336 // Cleanup.
|
Chris@1397
|
1337 close ( data->trigger_fds[0] );
|
Chris@1397
|
1338 close ( data->trigger_fds[1] );
|
Chris@1397
|
1339 if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
|
Chris@1397
|
1340 #ifndef AVOID_TIMESTAMPING
|
Chris@1397
|
1341 snd_seq_free_queue( data->seq, data->queue_id );
|
Chris@1397
|
1342 #endif
|
Chris@1397
|
1343 snd_seq_close( data->seq );
|
Chris@1397
|
1344 delete data;
|
Chris@1397
|
1345 }
|
Chris@1397
|
1346
|
Chris@1397
|
1347 void MidiInAlsa :: initialize( const std::string& clientName )
|
Chris@559
|
1348 {
|
Chris@559
|
1349 // Set up the ALSA sequencer client.
|
Chris@565
|
1350 snd_seq_t *seq;
|
Chris@559
|
1351 int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
|
Chris@559
|
1352 if ( result < 0 ) {
|
Chris@1397
|
1353 errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";
|
Chris@1397
|
1354 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1355 return;
|
Chris@1397
|
1356 }
|
Chris@559
|
1357
|
Chris@559
|
1358 // Set client name.
|
Chris@565
|
1359 snd_seq_set_client_name( seq, clientName.c_str() );
|
Chris@559
|
1360
|
Chris@559
|
1361 // Save our api-specific connection information.
|
Chris@559
|
1362 AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
|
Chris@559
|
1363 data->seq = seq;
|
Chris@1397
|
1364 data->portNum = -1;
|
Chris@559
|
1365 data->vport = -1;
|
Chris@1397
|
1366 data->subscription = 0;
|
Chris@1397
|
1367 data->dummy_thread_id = pthread_self();
|
Chris@1397
|
1368 data->thread = data->dummy_thread_id;
|
Chris@1397
|
1369 data->trigger_fds[0] = -1;
|
Chris@1397
|
1370 data->trigger_fds[1] = -1;
|
Chris@559
|
1371 apiData_ = (void *) data;
|
Chris@559
|
1372 inputData_.apiData = (void *) data;
|
Chris@559
|
1373
|
Chris@1397
|
1374 if ( pipe(data->trigger_fds) == -1 ) {
|
Chris@1397
|
1375 errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";
|
Chris@1397
|
1376 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1377 return;
|
Chris@1397
|
1378 }
|
Chris@1397
|
1379
|
Chris@559
|
1380 // Create the input queue
|
Chris@1397
|
1381 #ifndef AVOID_TIMESTAMPING
|
Chris@559
|
1382 data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");
|
Chris@559
|
1383 // Set arbitrary tempo (mm=100) and resolution (240)
|
Chris@559
|
1384 snd_seq_queue_tempo_t *qtempo;
|
Chris@559
|
1385 snd_seq_queue_tempo_alloca(&qtempo);
|
Chris@559
|
1386 snd_seq_queue_tempo_set_tempo(qtempo, 600000);
|
Chris@559
|
1387 snd_seq_queue_tempo_set_ppq(qtempo, 240);
|
Chris@559
|
1388 snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo);
|
Chris@559
|
1389 snd_seq_drain_output(data->seq);
|
Chris@1397
|
1390 #endif
|
Chris@559
|
1391 }
|
Chris@559
|
1392
|
Chris@559
|
1393 // This function is used to count or get the pinfo structure for a given port number.
|
Chris@559
|
1394 unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )
|
Chris@559
|
1395 {
|
Chris@1397
|
1396 snd_seq_client_info_t *cinfo;
|
Chris@559
|
1397 int client;
|
Chris@559
|
1398 int count = 0;
|
Chris@1397
|
1399 snd_seq_client_info_alloca( &cinfo );
|
Chris@1397
|
1400
|
Chris@1397
|
1401 snd_seq_client_info_set_client( cinfo, -1 );
|
Chris@1397
|
1402 while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
|
Chris@559
|
1403 client = snd_seq_client_info_get_client( cinfo );
|
Chris@559
|
1404 if ( client == 0 ) continue;
|
Chris@1397
|
1405 // Reset query info
|
Chris@1397
|
1406 snd_seq_port_info_set_client( pinfo, client );
|
Chris@1397
|
1407 snd_seq_port_info_set_port( pinfo, -1 );
|
Chris@1397
|
1408 while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
|
Chris@565
|
1409 unsigned int atyp = snd_seq_port_info_get_type( pinfo );
|
Chris@1397
|
1410 if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) &&
|
Chris@1397
|
1411 ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) ) continue;
|
Chris@565
|
1412 unsigned int caps = snd_seq_port_info_get_capability( pinfo );
|
Chris@565
|
1413 if ( ( caps & type ) != type ) continue;
|
Chris@559
|
1414 if ( count == portNumber ) return 1;
|
Chris@1397
|
1415 ++count;
|
Chris@1397
|
1416 }
|
Chris@1397
|
1417 }
|
Chris@559
|
1418
|
Chris@559
|
1419 // If a negative portNumber was used, return the port count.
|
Chris@559
|
1420 if ( portNumber < 0 ) return count;
|
Chris@559
|
1421 return 0;
|
Chris@559
|
1422 }
|
Chris@559
|
1423
|
Chris@1397
|
1424 unsigned int MidiInAlsa :: getPortCount()
|
Chris@1397
|
1425 {
|
Chris@1397
|
1426 snd_seq_port_info_t *pinfo;
|
Chris@1397
|
1427 snd_seq_port_info_alloca( &pinfo );
|
Chris@1397
|
1428
|
Chris@1397
|
1429 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1430 return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
|
Chris@1397
|
1431 }
|
Chris@1397
|
1432
|
Chris@1397
|
1433 std::string MidiInAlsa :: getPortName( unsigned int portNumber )
|
Chris@1397
|
1434 {
|
Chris@1397
|
1435 snd_seq_client_info_t *cinfo;
|
Chris@1397
|
1436 snd_seq_port_info_t *pinfo;
|
Chris@1397
|
1437 snd_seq_client_info_alloca( &cinfo );
|
Chris@1397
|
1438 snd_seq_port_info_alloca( &pinfo );
|
Chris@1397
|
1439
|
Chris@1397
|
1440 std::string stringName;
|
Chris@1397
|
1441 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1442 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
|
Chris@1397
|
1443 int cnum = snd_seq_port_info_get_client( pinfo );
|
Chris@1397
|
1444 snd_seq_get_any_client_info( data->seq, cnum, cinfo );
|
Chris@1397
|
1445 std::ostringstream os;
|
Chris@1397
|
1446 os << snd_seq_client_info_get_name( cinfo );
|
Chris@1397
|
1447 os << " "; // These lines added to make sure devices are listed
|
Chris@1397
|
1448 os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names
|
Chris@1397
|
1449 os << ":";
|
Chris@1397
|
1450 os << snd_seq_port_info_get_port( pinfo );
|
Chris@1397
|
1451 stringName = os.str();
|
Chris@1397
|
1452 return stringName;
|
Chris@1397
|
1453 }
|
Chris@1397
|
1454
|
Chris@1397
|
1455 // If we get here, we didn't find a match.
|
Chris@1397
|
1456 errorString_ = "MidiInAlsa::getPortName: error looking for port name!";
|
Chris@1397
|
1457 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
1458 return stringName;
|
Chris@1397
|
1459 }
|
Chris@1397
|
1460
|
Chris@1397
|
1461 void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName )
|
Chris@559
|
1462 {
|
Chris@559
|
1463 if ( connected_ ) {
|
Chris@1397
|
1464 errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";
|
Chris@1397
|
1465 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
1466 return;
|
Chris@559
|
1467 }
|
Chris@559
|
1468
|
Chris@559
|
1469 unsigned int nSrc = this->getPortCount();
|
Chris@1397
|
1470 if ( nSrc < 1 ) {
|
Chris@1397
|
1471 errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";
|
Chris@1397
|
1472 error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
|
Chris@1397
|
1473 return;
|
Chris@559
|
1474 }
|
Chris@559
|
1475
|
Chris@1397
|
1476 snd_seq_port_info_t *src_pinfo;
|
Chris@1397
|
1477 snd_seq_port_info_alloca( &src_pinfo );
|
Chris@559
|
1478 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1479 if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
|
Chris@1397
|
1480 std::ostringstream ost;
|
Chris@1397
|
1481 ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@559
|
1482 errorString_ = ost.str();
|
Chris@1397
|
1483 error( RtMidiError::INVALID_PARAMETER, errorString_ );
|
Chris@1397
|
1484 return;
|
Chris@559
|
1485 }
|
Chris@559
|
1486
|
Chris@559
|
1487 snd_seq_addr_t sender, receiver;
|
Chris@1397
|
1488 sender.client = snd_seq_port_info_get_client( src_pinfo );
|
Chris@1397
|
1489 sender.port = snd_seq_port_info_get_port( src_pinfo );
|
Chris@1397
|
1490 receiver.client = snd_seq_client_id( data->seq );
|
Chris@1397
|
1491
|
Chris@1397
|
1492 snd_seq_port_info_t *pinfo;
|
Chris@1397
|
1493 snd_seq_port_info_alloca( &pinfo );
|
Chris@559
|
1494 if ( data->vport < 0 ) {
|
Chris@559
|
1495 snd_seq_port_info_set_client( pinfo, 0 );
|
Chris@559
|
1496 snd_seq_port_info_set_port( pinfo, 0 );
|
Chris@559
|
1497 snd_seq_port_info_set_capability( pinfo,
|
Chris@559
|
1498 SND_SEQ_PORT_CAP_WRITE |
|
Chris@559
|
1499 SND_SEQ_PORT_CAP_SUBS_WRITE );
|
Chris@559
|
1500 snd_seq_port_info_set_type( pinfo,
|
Chris@559
|
1501 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
Chris@559
|
1502 SND_SEQ_PORT_TYPE_APPLICATION );
|
Chris@559
|
1503 snd_seq_port_info_set_midi_channels(pinfo, 16);
|
Chris@1397
|
1504 #ifndef AVOID_TIMESTAMPING
|
Chris@559
|
1505 snd_seq_port_info_set_timestamping(pinfo, 1);
|
Chris@559
|
1506 snd_seq_port_info_set_timestamp_real(pinfo, 1);
|
Chris@559
|
1507 snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
|
Chris@1397
|
1508 #endif
|
Chris@565
|
1509 snd_seq_port_info_set_name(pinfo, portName.c_str() );
|
Chris@559
|
1510 data->vport = snd_seq_create_port(data->seq, pinfo);
|
Chris@559
|
1511
|
Chris@559
|
1512 if ( data->vport < 0 ) {
|
Chris@1397
|
1513 errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";
|
Chris@1397
|
1514 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1515 return;
|
Chris@1397
|
1516 }
|
Chris@1397
|
1517 data->vport = snd_seq_port_info_get_port(pinfo);
|
Chris@1397
|
1518 }
|
Chris@1397
|
1519
|
Chris@1397
|
1520 receiver.port = data->vport;
|
Chris@1397
|
1521
|
Chris@1397
|
1522 if ( !data->subscription ) {
|
Chris@1397
|
1523 // Make subscription
|
Chris@1397
|
1524 if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
|
Chris@1397
|
1525 errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";
|
Chris@1397
|
1526 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1527 return;
|
Chris@1397
|
1528 }
|
Chris@1397
|
1529 snd_seq_port_subscribe_set_sender(data->subscription, &sender);
|
Chris@1397
|
1530 snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
|
Chris@1397
|
1531 if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
|
Chris@1397
|
1532 snd_seq_port_subscribe_free( data->subscription );
|
Chris@1397
|
1533 data->subscription = 0;
|
Chris@1397
|
1534 errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";
|
Chris@1397
|
1535 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1536 return;
|
Chris@559
|
1537 }
|
Chris@559
|
1538 }
|
Chris@559
|
1539
|
Chris@559
|
1540 if ( inputData_.doInput == false ) {
|
Chris@559
|
1541 // Start the input queue
|
Chris@1397
|
1542 #ifndef AVOID_TIMESTAMPING
|
Chris@559
|
1543 snd_seq_start_queue( data->seq, data->queue_id, NULL );
|
Chris@559
|
1544 snd_seq_drain_output( data->seq );
|
Chris@1397
|
1545 #endif
|
Chris@559
|
1546 // Start our MIDI input thread.
|
Chris@559
|
1547 pthread_attr_t attr;
|
Chris@559
|
1548 pthread_attr_init(&attr);
|
Chris@559
|
1549 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
Chris@559
|
1550 pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
|
Chris@559
|
1551
|
Chris@559
|
1552 inputData_.doInput = true;
|
Chris@559
|
1553 int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
|
Chris@559
|
1554 pthread_attr_destroy(&attr);
|
Chris@1397
|
1555 if ( err ) {
|
Chris@559
|
1556 snd_seq_unsubscribe_port( data->seq, data->subscription );
|
Chris@559
|
1557 snd_seq_port_subscribe_free( data->subscription );
|
Chris@1397
|
1558 data->subscription = 0;
|
Chris@559
|
1559 inputData_.doInput = false;
|
Chris@1397
|
1560 errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
|
Chris@1397
|
1561 error( RtMidiError::THREAD_ERROR, errorString_ );
|
Chris@1397
|
1562 return;
|
Chris@559
|
1563 }
|
Chris@559
|
1564 }
|
Chris@559
|
1565
|
Chris@559
|
1566 connected_ = true;
|
Chris@559
|
1567 }
|
Chris@559
|
1568
|
Chris@1397
|
1569 void MidiInAlsa :: openVirtualPort( std::string portName )
|
Chris@559
|
1570 {
|
Chris@559
|
1571 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@559
|
1572 if ( data->vport < 0 ) {
|
Chris@559
|
1573 snd_seq_port_info_t *pinfo;
|
Chris@559
|
1574 snd_seq_port_info_alloca( &pinfo );
|
Chris@559
|
1575 snd_seq_port_info_set_capability( pinfo,
|
Chris@559
|
1576 SND_SEQ_PORT_CAP_WRITE |
|
Chris@559
|
1577 SND_SEQ_PORT_CAP_SUBS_WRITE );
|
Chris@559
|
1578 snd_seq_port_info_set_type( pinfo,
|
Chris@559
|
1579 SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
Chris@559
|
1580 SND_SEQ_PORT_TYPE_APPLICATION );
|
Chris@559
|
1581 snd_seq_port_info_set_midi_channels(pinfo, 16);
|
Chris@1397
|
1582 #ifndef AVOID_TIMESTAMPING
|
Chris@559
|
1583 snd_seq_port_info_set_timestamping(pinfo, 1);
|
Chris@559
|
1584 snd_seq_port_info_set_timestamp_real(pinfo, 1);
|
Chris@559
|
1585 snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
|
Chris@1397
|
1586 #endif
|
Chris@559
|
1587 snd_seq_port_info_set_name(pinfo, portName.c_str());
|
Chris@559
|
1588 data->vport = snd_seq_create_port(data->seq, pinfo);
|
Chris@559
|
1589
|
Chris@559
|
1590 if ( data->vport < 0 ) {
|
Chris@1397
|
1591 errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";
|
Chris@1397
|
1592 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1593 return;
|
Chris@559
|
1594 }
|
Chris@1397
|
1595 data->vport = snd_seq_port_info_get_port(pinfo);
|
Chris@559
|
1596 }
|
Chris@559
|
1597
|
Chris@559
|
1598 if ( inputData_.doInput == false ) {
|
Chris@1397
|
1599 // Wait for old thread to stop, if still running
|
Chris@1397
|
1600 if ( !pthread_equal(data->thread, data->dummy_thread_id) )
|
Chris@1397
|
1601 pthread_join( data->thread, NULL );
|
Chris@1397
|
1602
|
Chris@559
|
1603 // Start the input queue
|
Chris@1397
|
1604 #ifndef AVOID_TIMESTAMPING
|
Chris@559
|
1605 snd_seq_start_queue( data->seq, data->queue_id, NULL );
|
Chris@559
|
1606 snd_seq_drain_output( data->seq );
|
Chris@1397
|
1607 #endif
|
Chris@559
|
1608 // Start our MIDI input thread.
|
Chris@559
|
1609 pthread_attr_t attr;
|
Chris@559
|
1610 pthread_attr_init(&attr);
|
Chris@559
|
1611 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
Chris@559
|
1612 pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
|
Chris@559
|
1613
|
Chris@559
|
1614 inputData_.doInput = true;
|
Chris@559
|
1615 int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
|
Chris@559
|
1616 pthread_attr_destroy(&attr);
|
Chris@1397
|
1617 if ( err ) {
|
Chris@1397
|
1618 if ( data->subscription ) {
|
Chris@1397
|
1619 snd_seq_unsubscribe_port( data->seq, data->subscription );
|
Chris@1397
|
1620 snd_seq_port_subscribe_free( data->subscription );
|
Chris@1397
|
1621 data->subscription = 0;
|
Chris@1397
|
1622 }
|
Chris@559
|
1623 inputData_.doInput = false;
|
Chris@1397
|
1624 errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
|
Chris@1397
|
1625 error( RtMidiError::THREAD_ERROR, errorString_ );
|
Chris@1397
|
1626 return;
|
Chris@559
|
1627 }
|
Chris@559
|
1628 }
|
Chris@559
|
1629 }
|
Chris@559
|
1630
|
Chris@1397
|
1631 void MidiInAlsa :: closePort( void )
|
Chris@559
|
1632 {
|
Chris@1397
|
1633 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1634
|
Chris@559
|
1635 if ( connected_ ) {
|
Chris@1397
|
1636 if ( data->subscription ) {
|
Chris@1397
|
1637 snd_seq_unsubscribe_port( data->seq, data->subscription );
|
Chris@1397
|
1638 snd_seq_port_subscribe_free( data->subscription );
|
Chris@1397
|
1639 data->subscription = 0;
|
Chris@1397
|
1640 }
|
Chris@559
|
1641 // Stop the input queue
|
Chris@1397
|
1642 #ifndef AVOID_TIMESTAMPING
|
Chris@559
|
1643 snd_seq_stop_queue( data->seq, data->queue_id, NULL );
|
Chris@559
|
1644 snd_seq_drain_output( data->seq );
|
Chris@1397
|
1645 #endif
|
Chris@559
|
1646 connected_ = false;
|
Chris@559
|
1647 }
|
Chris@1397
|
1648
|
Chris@1397
|
1649 // Stop thread to avoid triggering the callback, while the port is intended to be closed
|
Chris@1397
|
1650 if ( inputData_.doInput ) {
|
Chris@1397
|
1651 inputData_.doInput = false;
|
Chris@1397
|
1652 int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
|
Chris@1397
|
1653 (void) res;
|
Chris@1397
|
1654 if ( !pthread_equal(data->thread, data->dummy_thread_id) )
|
Chris@1397
|
1655 pthread_join( data->thread, NULL );
|
Chris@1397
|
1656 }
|
Chris@559
|
1657 }
|
Chris@559
|
1658
|
Chris@1397
|
1659 //*********************************************************************//
|
Chris@1397
|
1660 // API: LINUX ALSA
|
Chris@1397
|
1661 // Class Definitions: MidiOutAlsa
|
Chris@1397
|
1662 //*********************************************************************//
|
Chris@1397
|
1663
|
Chris@1397
|
1664 MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi()
|
Chris@1397
|
1665 {
|
Chris@1397
|
1666 initialize( clientName );
|
Chris@1397
|
1667 }
|
Chris@1397
|
1668
|
Chris@1397
|
1669 MidiOutAlsa :: ~MidiOutAlsa()
|
Chris@559
|
1670 {
|
Chris@559
|
1671 // Close a connection if it exists.
|
Chris@559
|
1672 closePort();
|
Chris@559
|
1673
|
Chris@1397
|
1674 // Cleanup.
|
Chris@559
|
1675 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@559
|
1676 if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
|
Chris@1397
|
1677 if ( data->coder ) snd_midi_event_free( data->coder );
|
Chris@1397
|
1678 if ( data->buffer ) free( data->buffer );
|
Chris@559
|
1679 snd_seq_close( data->seq );
|
Chris@559
|
1680 delete data;
|
Chris@559
|
1681 }
|
Chris@559
|
1682
|
Chris@1397
|
1683 void MidiOutAlsa :: initialize( const std::string& clientName )
|
Chris@1397
|
1684 {
|
Chris@1397
|
1685 // Set up the ALSA sequencer client.
|
Chris@1397
|
1686 snd_seq_t *seq;
|
Chris@1397
|
1687 int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
|
Chris@1397
|
1688 if ( result1 < 0 ) {
|
Chris@1397
|
1689 errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";
|
Chris@1397
|
1690 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1691 return;
|
Chris@1397
|
1692 }
|
Chris@1397
|
1693
|
Chris@1397
|
1694 // Set client name.
|
Chris@1397
|
1695 snd_seq_set_client_name( seq, clientName.c_str() );
|
Chris@1397
|
1696
|
Chris@1397
|
1697 // Save our api-specific connection information.
|
Chris@1397
|
1698 AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
|
Chris@1397
|
1699 data->seq = seq;
|
Chris@1397
|
1700 data->portNum = -1;
|
Chris@1397
|
1701 data->vport = -1;
|
Chris@1397
|
1702 data->bufferSize = 32;
|
Chris@1397
|
1703 data->coder = 0;
|
Chris@1397
|
1704 data->buffer = 0;
|
Chris@1397
|
1705 int result = snd_midi_event_new( data->bufferSize, &data->coder );
|
Chris@1397
|
1706 if ( result < 0 ) {
|
Chris@1397
|
1707 delete data;
|
Chris@1397
|
1708 errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";
|
Chris@1397
|
1709 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1710 return;
|
Chris@1397
|
1711 }
|
Chris@1397
|
1712 data->buffer = (unsigned char *) malloc( data->bufferSize );
|
Chris@1397
|
1713 if ( data->buffer == NULL ) {
|
Chris@1397
|
1714 delete data;
|
Chris@1397
|
1715 errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
|
Chris@1397
|
1716 error( RtMidiError::MEMORY_ERROR, errorString_ );
|
Chris@1397
|
1717 return;
|
Chris@1397
|
1718 }
|
Chris@1397
|
1719 snd_midi_event_init( data->coder );
|
Chris@1397
|
1720 apiData_ = (void *) data;
|
Chris@1397
|
1721 }
|
Chris@1397
|
1722
|
Chris@1397
|
1723 unsigned int MidiOutAlsa :: getPortCount()
|
Chris@559
|
1724 {
|
Chris@559
|
1725 snd_seq_port_info_t *pinfo;
|
Chris@559
|
1726 snd_seq_port_info_alloca( &pinfo );
|
Chris@559
|
1727
|
Chris@559
|
1728 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1729 return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
|
Chris@559
|
1730 }
|
Chris@559
|
1731
|
Chris@1397
|
1732 std::string MidiOutAlsa :: getPortName( unsigned int portNumber )
|
Chris@559
|
1733 {
|
Chris@565
|
1734 snd_seq_client_info_t *cinfo;
|
Chris@565
|
1735 snd_seq_port_info_t *pinfo;
|
Chris@565
|
1736 snd_seq_client_info_alloca( &cinfo );
|
Chris@565
|
1737 snd_seq_port_info_alloca( &pinfo );
|
Chris@559
|
1738
|
Chris@1397
|
1739 std::string stringName;
|
Chris@559
|
1740 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@559
|
1741 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {
|
Chris@565
|
1742 int cnum = snd_seq_port_info_get_client(pinfo);
|
Chris@565
|
1743 snd_seq_get_any_client_info( data->seq, cnum, cinfo );
|
Chris@565
|
1744 std::ostringstream os;
|
Chris@565
|
1745 os << snd_seq_client_info_get_name(cinfo);
|
Chris@1397
|
1746 os << " "; // These lines added to make sure devices are listed
|
Chris@1397
|
1747 os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names
|
Chris@565
|
1748 os << ":";
|
Chris@565
|
1749 os << snd_seq_port_info_get_port(pinfo);
|
Chris@1397
|
1750 stringName = os.str();
|
Chris@559
|
1751 return stringName;
|
Chris@559
|
1752 }
|
Chris@559
|
1753
|
Chris@559
|
1754 // If we get here, we didn't find a match.
|
Chris@1397
|
1755 errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";
|
Chris@1397
|
1756 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
1757 return stringName;
|
Chris@559
|
1758 }
|
Chris@559
|
1759
|
Chris@1397
|
1760 void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName )
|
Chris@559
|
1761 {
|
Chris@559
|
1762 if ( connected_ ) {
|
Chris@1397
|
1763 errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";
|
Chris@1397
|
1764 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
1765 return;
|
Chris@559
|
1766 }
|
Chris@559
|
1767
|
Chris@559
|
1768 unsigned int nSrc = this->getPortCount();
|
Chris@559
|
1769 if (nSrc < 1) {
|
Chris@1397
|
1770 errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";
|
Chris@1397
|
1771 error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
|
Chris@1397
|
1772 return;
|
Chris@559
|
1773 }
|
Chris@559
|
1774
|
Chris@565
|
1775 snd_seq_port_info_t *pinfo;
|
Chris@565
|
1776 snd_seq_port_info_alloca( &pinfo );
|
Chris@559
|
1777 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@559
|
1778 if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {
|
Chris@1397
|
1779 std::ostringstream ost;
|
Chris@1397
|
1780 ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@559
|
1781 errorString_ = ost.str();
|
Chris@1397
|
1782 error( RtMidiError::INVALID_PARAMETER, errorString_ );
|
Chris@1397
|
1783 return;
|
Chris@559
|
1784 }
|
Chris@559
|
1785
|
Chris@559
|
1786 snd_seq_addr_t sender, receiver;
|
Chris@1397
|
1787 receiver.client = snd_seq_port_info_get_client( pinfo );
|
Chris@1397
|
1788 receiver.port = snd_seq_port_info_get_port( pinfo );
|
Chris@1397
|
1789 sender.client = snd_seq_client_id( data->seq );
|
Chris@559
|
1790
|
Chris@559
|
1791 if ( data->vport < 0 ) {
|
Chris@565
|
1792 data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
|
Chris@559
|
1793 SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
|
Chris@1397
|
1794 SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
|
Chris@559
|
1795 if ( data->vport < 0 ) {
|
Chris@1397
|
1796 errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";
|
Chris@1397
|
1797 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1798 return;
|
Chris@559
|
1799 }
|
Chris@559
|
1800 }
|
Chris@559
|
1801
|
Chris@1397
|
1802 sender.port = data->vport;
|
Chris@559
|
1803
|
Chris@559
|
1804 // Make subscription
|
Chris@1397
|
1805 if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
|
Chris@1397
|
1806 snd_seq_port_subscribe_free( data->subscription );
|
Chris@1397
|
1807 errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";
|
Chris@1397
|
1808 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1809 return;
|
Chris@1397
|
1810 }
|
Chris@559
|
1811 snd_seq_port_subscribe_set_sender(data->subscription, &sender);
|
Chris@559
|
1812 snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
|
Chris@559
|
1813 snd_seq_port_subscribe_set_time_update(data->subscription, 1);
|
Chris@559
|
1814 snd_seq_port_subscribe_set_time_real(data->subscription, 1);
|
Chris@559
|
1815 if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
|
Chris@1397
|
1816 snd_seq_port_subscribe_free( data->subscription );
|
Chris@1397
|
1817 errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";
|
Chris@1397
|
1818 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1819 return;
|
Chris@559
|
1820 }
|
Chris@559
|
1821
|
Chris@559
|
1822 connected_ = true;
|
Chris@559
|
1823 }
|
Chris@559
|
1824
|
Chris@1397
|
1825 void MidiOutAlsa :: closePort( void )
|
Chris@559
|
1826 {
|
Chris@559
|
1827 if ( connected_ ) {
|
Chris@559
|
1828 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@559
|
1829 snd_seq_unsubscribe_port( data->seq, data->subscription );
|
Chris@559
|
1830 snd_seq_port_subscribe_free( data->subscription );
|
Chris@559
|
1831 connected_ = false;
|
Chris@559
|
1832 }
|
Chris@559
|
1833 }
|
Chris@559
|
1834
|
Chris@1397
|
1835 void MidiOutAlsa :: openVirtualPort( std::string portName )
|
Chris@559
|
1836 {
|
Chris@559
|
1837 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@559
|
1838 if ( data->vport < 0 ) {
|
Chris@559
|
1839 data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
|
Chris@559
|
1840 SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
|
Chris@1397
|
1841 SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
|
Chris@559
|
1842
|
Chris@559
|
1843 if ( data->vport < 0 ) {
|
Chris@1397
|
1844 errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";
|
Chris@1397
|
1845 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@559
|
1846 }
|
Chris@559
|
1847 }
|
Chris@559
|
1848 }
|
Chris@559
|
1849
|
Chris@1397
|
1850 void MidiOutAlsa :: sendMessage( std::vector<unsigned char> *message )
|
Chris@559
|
1851 {
|
Chris@559
|
1852 int result;
|
Chris@559
|
1853 AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
Chris@1397
|
1854 unsigned int nBytes = message->size();
|
Chris@559
|
1855 if ( nBytes > data->bufferSize ) {
|
Chris@559
|
1856 data->bufferSize = nBytes;
|
Chris@559
|
1857 result = snd_midi_event_resize_buffer ( data->coder, nBytes);
|
Chris@559
|
1858 if ( result != 0 ) {
|
Chris@1397
|
1859 errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";
|
Chris@1397
|
1860 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
1861 return;
|
Chris@559
|
1862 }
|
Chris@559
|
1863 free (data->buffer);
|
Chris@559
|
1864 data->buffer = (unsigned char *) malloc( data->bufferSize );
|
Chris@559
|
1865 if ( data->buffer == NULL ) {
|
Chris@1397
|
1866 errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
|
Chris@1397
|
1867 error( RtMidiError::MEMORY_ERROR, errorString_ );
|
Chris@1397
|
1868 return;
|
Chris@559
|
1869 }
|
Chris@559
|
1870 }
|
Chris@559
|
1871
|
Chris@559
|
1872 snd_seq_event_t ev;
|
Chris@559
|
1873 snd_seq_ev_clear(&ev);
|
Chris@1397
|
1874 snd_seq_ev_set_source(&ev, data->vport);
|
Chris@559
|
1875 snd_seq_ev_set_subs(&ev);
|
Chris@559
|
1876 snd_seq_ev_set_direct(&ev);
|
Chris@1397
|
1877 for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message->at(i);
|
Chris@1397
|
1878 result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );
|
Chris@559
|
1879 if ( result < (int)nBytes ) {
|
Chris@1397
|
1880 errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";
|
Chris@1397
|
1881 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
1882 return;
|
Chris@559
|
1883 }
|
Chris@559
|
1884
|
Chris@559
|
1885 // Send the event.
|
Chris@559
|
1886 result = snd_seq_event_output(data->seq, &ev);
|
Chris@559
|
1887 if ( result < 0 ) {
|
Chris@1397
|
1888 errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";
|
Chris@1397
|
1889 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
1890 return;
|
Chris@559
|
1891 }
|
Chris@559
|
1892 snd_seq_drain_output(data->seq);
|
Chris@559
|
1893 }
|
Chris@559
|
1894
|
Chris@559
|
1895 #endif // __LINUX_ALSA__
|
Chris@559
|
1896
|
Chris@559
|
1897
|
Chris@559
|
1898 //*********************************************************************//
|
Chris@559
|
1899 // API: Windows Multimedia Library (MM)
|
Chris@559
|
1900 //*********************************************************************//
|
Chris@559
|
1901
|
Chris@559
|
1902 // API information deciphered from:
|
Chris@559
|
1903 // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp
|
Chris@559
|
1904
|
Chris@559
|
1905 // Thanks to Jean-Baptiste Berruchon for the sysex code.
|
Chris@559
|
1906
|
Chris@559
|
1907 #if defined(__WINDOWS_MM__)
|
Chris@559
|
1908
|
Chris@559
|
1909 // The Windows MM API is based on the use of a callback function for
|
Chris@559
|
1910 // MIDI input. We convert the system specific time stamps to delta
|
Chris@559
|
1911 // time values.
|
Chris@559
|
1912
|
Chris@559
|
1913 // Windows MM MIDI header files.
|
Chris@559
|
1914 #include <windows.h>
|
Chris@559
|
1915 #include <mmsystem.h>
|
Chris@559
|
1916
|
Chris@1397
|
1917 #define RT_SYSEX_BUFFER_SIZE 1024
|
Chris@1397
|
1918 #define RT_SYSEX_BUFFER_COUNT 4
|
Chris@1397
|
1919
|
Chris@559
|
1920 // A structure to hold variables related to the CoreMIDI API
|
Chris@559
|
1921 // implementation.
|
Chris@559
|
1922 struct WinMidiData {
|
Chris@559
|
1923 HMIDIIN inHandle; // Handle to Midi Input Device
|
Chris@559
|
1924 HMIDIOUT outHandle; // Handle to Midi Output Device
|
Chris@559
|
1925 DWORD lastTime;
|
Chris@1397
|
1926 MidiInApi::MidiMessage message;
|
Chris@1397
|
1927 LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];
|
Chris@1397
|
1928 CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
|
Chris@559
|
1929 };
|
Chris@559
|
1930
|
Chris@559
|
1931 //*********************************************************************//
|
Chris@559
|
1932 // API: Windows MM
|
Chris@1397
|
1933 // Class Definitions: MidiInWinMM
|
Chris@559
|
1934 //*********************************************************************//
|
Chris@559
|
1935
|
Chris@1397
|
1936 static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,
|
Chris@559
|
1937 UINT inputStatus,
|
Chris@1397
|
1938 DWORD_PTR instancePtr,
|
Chris@1397
|
1939 DWORD_PTR midiMessage,
|
Chris@559
|
1940 DWORD timestamp )
|
Chris@559
|
1941 {
|
Chris@1397
|
1942 if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;
|
Chris@1397
|
1943
|
Chris@1397
|
1944 //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr);
|
Chris@1397
|
1945 MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;
|
Chris@559
|
1946 WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);
|
Chris@559
|
1947
|
Chris@559
|
1948 // Calculate time stamp.
|
Chris@1397
|
1949 if ( data->firstMessage == true ) {
|
Chris@1397
|
1950 apiData->message.timeStamp = 0.0;
|
Chris@1397
|
1951 data->firstMessage = false;
|
Chris@1397
|
1952 }
|
Chris@559
|
1953 else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;
|
Chris@559
|
1954 apiData->lastTime = timestamp;
|
Chris@559
|
1955
|
Chris@559
|
1956 if ( inputStatus == MIM_DATA ) { // Channel or system message
|
Chris@559
|
1957
|
Chris@559
|
1958 // Make sure the first byte is a status byte.
|
Chris@559
|
1959 unsigned char status = (unsigned char) (midiMessage & 0x000000FF);
|
Chris@559
|
1960 if ( !(status & 0x80) ) return;
|
Chris@559
|
1961
|
Chris@559
|
1962 // Determine the number of bytes in the MIDI message.
|
Chris@559
|
1963 unsigned short nBytes = 1;
|
Chris@559
|
1964 if ( status < 0xC0 ) nBytes = 3;
|
Chris@559
|
1965 else if ( status < 0xE0 ) nBytes = 2;
|
Chris@559
|
1966 else if ( status < 0xF0 ) nBytes = 3;
|
Chris@1397
|
1967 else if ( status == 0xF1 ) {
|
Chris@1397
|
1968 if ( data->ignoreFlags & 0x02 ) return;
|
Chris@1397
|
1969 else nBytes = 2;
|
Chris@559
|
1970 }
|
Chris@1397
|
1971 else if ( status == 0xF2 ) nBytes = 3;
|
Chris@559
|
1972 else if ( status == 0xF3 ) nBytes = 2;
|
Chris@559
|
1973 else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) {
|
Chris@559
|
1974 // A MIDI timing tick message and we're ignoring it.
|
Chris@559
|
1975 return;
|
Chris@559
|
1976 }
|
Chris@559
|
1977 else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
|
Chris@559
|
1978 // A MIDI active sensing message and we're ignoring it.
|
Chris@559
|
1979 return;
|
Chris@559
|
1980 }
|
Chris@559
|
1981
|
Chris@559
|
1982 // Copy bytes to our MIDI message.
|
Chris@559
|
1983 unsigned char *ptr = (unsigned char *) &midiMessage;
|
Chris@1397
|
1984 for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ );
|
Chris@559
|
1985 }
|
Chris@1397
|
1986 else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
|
Chris@565
|
1987 MIDIHDR *sysex = ( MIDIHDR *) midiMessage;
|
Chris@1397
|
1988 if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) {
|
Chris@565
|
1989 // Sysex message and we're not ignoring it
|
Chris@1397
|
1990 for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )
|
Chris@565
|
1991 apiData->message.bytes.push_back( sysex->lpData[i] );
|
Chris@565
|
1992 }
|
Chris@559
|
1993
|
Chris@565
|
1994 // The WinMM API requires that the sysex buffer be requeued after
|
Chris@565
|
1995 // input of each sysex message. Even if we are ignoring sysex
|
Chris@565
|
1996 // messages, we still need to requeue the buffer in case the user
|
Chris@565
|
1997 // decides to not ignore sysex messages in the future. However,
|
Chris@565
|
1998 // it seems that WinMM calls this function with an empty sysex
|
Chris@565
|
1999 // buffer when an application closes and in this case, we should
|
Chris@565
|
2000 // avoid requeueing it, else the computer suddenly reboots after
|
Chris@565
|
2001 // one or two minutes.
|
Chris@1397
|
2002 if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) {
|
Chris@565
|
2003 //if ( sysex->dwBytesRecorded > 0 ) {
|
Chris@1397
|
2004 EnterCriticalSection( &(apiData->_mutex) );
|
Chris@1397
|
2005 MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) );
|
Chris@1397
|
2006 LeaveCriticalSection( &(apiData->_mutex) );
|
Chris@559
|
2007 if ( result != MMSYSERR_NOERROR )
|
Chris@1397
|
2008 std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";
|
Chris@565
|
2009
|
Chris@565
|
2010 if ( data->ignoreFlags & 0x01 ) return;
|
Chris@559
|
2011 }
|
Chris@565
|
2012 else return;
|
Chris@559
|
2013 }
|
Chris@559
|
2014
|
Chris@559
|
2015 if ( data->usingCallback ) {
|
Chris@559
|
2016 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
|
Chris@559
|
2017 callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData );
|
Chris@559
|
2018 }
|
Chris@559
|
2019 else {
|
Chris@559
|
2020 // As long as we haven't reached our queue size limit, push the message.
|
Chris@1397
|
2021 if ( data->queue.size < data->queue.ringSize ) {
|
Chris@1397
|
2022 data->queue.ring[data->queue.back++] = apiData->message;
|
Chris@1397
|
2023 if ( data->queue.back == data->queue.ringSize )
|
Chris@1397
|
2024 data->queue.back = 0;
|
Chris@1397
|
2025 data->queue.size++;
|
Chris@1397
|
2026 }
|
Chris@559
|
2027 else
|
Chris@1397
|
2028 std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n";
|
Chris@559
|
2029 }
|
Chris@559
|
2030
|
Chris@565
|
2031 // Clear the vector for the next input message.
|
Chris@559
|
2032 apiData->message.bytes.clear();
|
Chris@559
|
2033 }
|
Chris@559
|
2034
|
Chris@1397
|
2035 MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
|
Chris@1397
|
2036 {
|
Chris@1397
|
2037 initialize( clientName );
|
Chris@1397
|
2038 }
|
Chris@1397
|
2039
|
Chris@1397
|
2040 MidiInWinMM :: ~MidiInWinMM()
|
Chris@1397
|
2041 {
|
Chris@1397
|
2042 // Close a connection if it exists.
|
Chris@1397
|
2043 closePort();
|
Chris@1397
|
2044
|
Chris@1397
|
2045 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@1397
|
2046 DeleteCriticalSection( &(data->_mutex) );
|
Chris@1397
|
2047
|
Chris@1397
|
2048 // Cleanup.
|
Chris@1397
|
2049 delete data;
|
Chris@1397
|
2050 }
|
Chris@1397
|
2051
|
Chris@1397
|
2052 void MidiInWinMM :: initialize( const std::string& /*clientName*/ )
|
Chris@559
|
2053 {
|
Chris@559
|
2054 // We'll issue a warning here if no devices are available but not
|
Chris@559
|
2055 // throw an error since the user can plugin something later.
|
Chris@559
|
2056 unsigned int nDevices = midiInGetNumDevs();
|
Chris@559
|
2057 if ( nDevices == 0 ) {
|
Chris@1397
|
2058 errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available.";
|
Chris@1397
|
2059 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2060 }
|
Chris@559
|
2061
|
Chris@559
|
2062 // Save our api-specific connection information.
|
Chris@559
|
2063 WinMidiData *data = (WinMidiData *) new WinMidiData;
|
Chris@559
|
2064 apiData_ = (void *) data;
|
Chris@559
|
2065 inputData_.apiData = (void *) data;
|
Chris@559
|
2066 data->message.bytes.clear(); // needs to be empty for first input message
|
Chris@1397
|
2067
|
Chris@1397
|
2068 if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) {
|
Chris@1397
|
2069 errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed.";
|
Chris@1397
|
2070 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2071 }
|
Chris@559
|
2072 }
|
Chris@559
|
2073
|
Chris@1397
|
2074 void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )
|
Chris@559
|
2075 {
|
Chris@559
|
2076 if ( connected_ ) {
|
Chris@1397
|
2077 errorString_ = "MidiInWinMM::openPort: a valid connection already exists!";
|
Chris@1397
|
2078 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2079 return;
|
Chris@559
|
2080 }
|
Chris@559
|
2081
|
Chris@559
|
2082 unsigned int nDevices = midiInGetNumDevs();
|
Chris@559
|
2083 if (nDevices == 0) {
|
Chris@1397
|
2084 errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!";
|
Chris@1397
|
2085 error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
|
Chris@1397
|
2086 return;
|
Chris@559
|
2087 }
|
Chris@559
|
2088
|
Chris@559
|
2089 if ( portNumber >= nDevices ) {
|
Chris@1397
|
2090 std::ostringstream ost;
|
Chris@1397
|
2091 ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@559
|
2092 errorString_ = ost.str();
|
Chris@1397
|
2093 error( RtMidiError::INVALID_PARAMETER, errorString_ );
|
Chris@1397
|
2094 return;
|
Chris@559
|
2095 }
|
Chris@559
|
2096
|
Chris@559
|
2097 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@559
|
2098 MMRESULT result = midiInOpen( &data->inHandle,
|
Chris@559
|
2099 portNumber,
|
Chris@1397
|
2100 (DWORD_PTR)&midiInputCallback,
|
Chris@1397
|
2101 (DWORD_PTR)&inputData_,
|
Chris@559
|
2102 CALLBACK_FUNCTION );
|
Chris@559
|
2103 if ( result != MMSYSERR_NOERROR ) {
|
Chris@1397
|
2104 errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port.";
|
Chris@1397
|
2105 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2106 return;
|
Chris@559
|
2107 }
|
Chris@559
|
2108
|
Chris@1397
|
2109 // Allocate and init the sysex buffers.
|
Chris@1397
|
2110 for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {
|
Chris@1397
|
2111 data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];
|
Chris@1397
|
2112 data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];
|
Chris@1397
|
2113 data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE;
|
Chris@1397
|
2114 data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator
|
Chris@1397
|
2115 data->sysexBuffer[i]->dwFlags = 0;
|
Chris@1397
|
2116
|
Chris@1397
|
2117 result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
|
Chris@1397
|
2118 if ( result != MMSYSERR_NOERROR ) {
|
Chris@1397
|
2119 midiInClose( data->inHandle );
|
Chris@1397
|
2120 errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader).";
|
Chris@1397
|
2121 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2122 return;
|
Chris@1397
|
2123 }
|
Chris@1397
|
2124
|
Chris@1397
|
2125 // Register the buffer.
|
Chris@1397
|
2126 result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );
|
Chris@1397
|
2127 if ( result != MMSYSERR_NOERROR ) {
|
Chris@1397
|
2128 midiInClose( data->inHandle );
|
Chris@1397
|
2129 errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer).";
|
Chris@1397
|
2130 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2131 return;
|
Chris@1397
|
2132 }
|
Chris@559
|
2133 }
|
Chris@559
|
2134
|
Chris@559
|
2135 result = midiInStart( data->inHandle );
|
Chris@559
|
2136 if ( result != MMSYSERR_NOERROR ) {
|
Chris@559
|
2137 midiInClose( data->inHandle );
|
Chris@1397
|
2138 errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port.";
|
Chris@1397
|
2139 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2140 return;
|
Chris@559
|
2141 }
|
Chris@559
|
2142
|
Chris@559
|
2143 connected_ = true;
|
Chris@559
|
2144 }
|
Chris@559
|
2145
|
Chris@1397
|
2146 void MidiInWinMM :: openVirtualPort( std::string /*portName*/ )
|
Chris@559
|
2147 {
|
Chris@559
|
2148 // This function cannot be implemented for the Windows MM MIDI API.
|
Chris@1397
|
2149 errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
|
Chris@1397
|
2150 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2151 }
|
Chris@559
|
2152
|
Chris@1397
|
2153 void MidiInWinMM :: closePort( void )
|
Chris@559
|
2154 {
|
Chris@559
|
2155 if ( connected_ ) {
|
Chris@559
|
2156 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@1397
|
2157 EnterCriticalSection( &(data->_mutex) );
|
Chris@559
|
2158 midiInReset( data->inHandle );
|
Chris@559
|
2159 midiInStop( data->inHandle );
|
Chris@559
|
2160
|
Chris@1397
|
2161 for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {
|
Chris@1397
|
2162 int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR));
|
Chris@1397
|
2163 delete [] data->sysexBuffer[i]->lpData;
|
Chris@1397
|
2164 delete [] data->sysexBuffer[i];
|
Chris@1397
|
2165 if ( result != MMSYSERR_NOERROR ) {
|
Chris@1397
|
2166 midiInClose( data->inHandle );
|
Chris@1397
|
2167 errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";
|
Chris@1397
|
2168 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2169 return;
|
Chris@1397
|
2170 }
|
Chris@559
|
2171 }
|
Chris@559
|
2172
|
Chris@559
|
2173 midiInClose( data->inHandle );
|
Chris@559
|
2174 connected_ = false;
|
Chris@1397
|
2175 LeaveCriticalSection( &(data->_mutex) );
|
Chris@559
|
2176 }
|
Chris@559
|
2177 }
|
Chris@559
|
2178
|
Chris@1397
|
2179 unsigned int MidiInWinMM :: getPortCount()
|
Chris@1397
|
2180 {
|
Chris@1397
|
2181 return midiInGetNumDevs();
|
Chris@1397
|
2182 }
|
Chris@1397
|
2183
|
Chris@1397
|
2184 std::string MidiInWinMM :: getPortName( unsigned int portNumber )
|
Chris@1397
|
2185 {
|
Chris@1397
|
2186 std::string stringName;
|
Chris@1397
|
2187 unsigned int nDevices = midiInGetNumDevs();
|
Chris@1397
|
2188 if ( portNumber >= nDevices ) {
|
Chris@1397
|
2189 std::ostringstream ost;
|
Chris@1397
|
2190 ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@1397
|
2191 errorString_ = ost.str();
|
Chris@1397
|
2192 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2193 return stringName;
|
Chris@1397
|
2194 }
|
Chris@1397
|
2195
|
Chris@1397
|
2196 MIDIINCAPS deviceCaps;
|
Chris@1397
|
2197 midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));
|
Chris@1397
|
2198
|
Chris@1397
|
2199 #if defined( UNICODE ) || defined( _UNICODE )
|
Chris@1397
|
2200 int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;
|
Chris@1397
|
2201 stringName.assign( length, 0 );
|
Chris@1397
|
2202 length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);
|
Chris@1397
|
2203 #else
|
Chris@1397
|
2204 stringName = std::string( deviceCaps.szPname );
|
Chris@1397
|
2205 #endif
|
Chris@1397
|
2206
|
Chris@1397
|
2207 // Next lines added to add the portNumber to the name so that
|
Chris@1397
|
2208 // the device's names are sure to be listed with individual names
|
Chris@1397
|
2209 // even when they have the same brand name
|
Chris@1397
|
2210 std::ostringstream os;
|
Chris@1397
|
2211 os << " ";
|
Chris@1397
|
2212 os << portNumber;
|
Chris@1397
|
2213 stringName += os.str();
|
Chris@1397
|
2214
|
Chris@1397
|
2215 return stringName;
|
Chris@1397
|
2216 }
|
Chris@1397
|
2217
|
Chris@1397
|
2218 //*********************************************************************//
|
Chris@1397
|
2219 // API: Windows MM
|
Chris@1397
|
2220 // Class Definitions: MidiOutWinMM
|
Chris@1397
|
2221 //*********************************************************************//
|
Chris@1397
|
2222
|
Chris@1397
|
2223 MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi()
|
Chris@1397
|
2224 {
|
Chris@1397
|
2225 initialize( clientName );
|
Chris@1397
|
2226 }
|
Chris@1397
|
2227
|
Chris@1397
|
2228 MidiOutWinMM :: ~MidiOutWinMM()
|
Chris@559
|
2229 {
|
Chris@559
|
2230 // Close a connection if it exists.
|
Chris@559
|
2231 closePort();
|
Chris@559
|
2232
|
Chris@559
|
2233 // Cleanup.
|
Chris@559
|
2234 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@559
|
2235 delete data;
|
Chris@559
|
2236 }
|
Chris@559
|
2237
|
Chris@1397
|
2238 void MidiOutWinMM :: initialize( const std::string& /*clientName*/ )
|
Chris@559
|
2239 {
|
Chris@559
|
2240 // We'll issue a warning here if no devices are available but not
|
Chris@559
|
2241 // throw an error since the user can plug something in later.
|
Chris@559
|
2242 unsigned int nDevices = midiOutGetNumDevs();
|
Chris@559
|
2243 if ( nDevices == 0 ) {
|
Chris@1397
|
2244 errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available.";
|
Chris@1397
|
2245 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2246 }
|
Chris@559
|
2247
|
Chris@559
|
2248 // Save our api-specific connection information.
|
Chris@559
|
2249 WinMidiData *data = (WinMidiData *) new WinMidiData;
|
Chris@559
|
2250 apiData_ = (void *) data;
|
Chris@559
|
2251 }
|
Chris@559
|
2252
|
Chris@1397
|
2253 unsigned int MidiOutWinMM :: getPortCount()
|
Chris@1397
|
2254 {
|
Chris@1397
|
2255 return midiOutGetNumDevs();
|
Chris@1397
|
2256 }
|
Chris@1397
|
2257
|
Chris@1397
|
2258 std::string MidiOutWinMM :: getPortName( unsigned int portNumber )
|
Chris@1397
|
2259 {
|
Chris@1397
|
2260 std::string stringName;
|
Chris@1397
|
2261 unsigned int nDevices = midiOutGetNumDevs();
|
Chris@1397
|
2262 if ( portNumber >= nDevices ) {
|
Chris@1397
|
2263 std::ostringstream ost;
|
Chris@1397
|
2264 ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@1397
|
2265 errorString_ = ost.str();
|
Chris@1397
|
2266 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2267 return stringName;
|
Chris@1397
|
2268 }
|
Chris@1397
|
2269
|
Chris@1397
|
2270 MIDIOUTCAPS deviceCaps;
|
Chris@1397
|
2271 midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS));
|
Chris@1397
|
2272
|
Chris@1397
|
2273 #if defined( UNICODE ) || defined( _UNICODE )
|
Chris@1397
|
2274 int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;
|
Chris@1397
|
2275 stringName.assign( length, 0 );
|
Chris@1397
|
2276 length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);
|
Chris@1397
|
2277 #else
|
Chris@1397
|
2278 stringName = std::string( deviceCaps.szPname );
|
Chris@1397
|
2279 #endif
|
Chris@1397
|
2280
|
Chris@1397
|
2281 // Next lines added to add the portNumber to the name so that
|
Chris@1397
|
2282 // the device's names are sure to be listed with individual names
|
Chris@1397
|
2283 // even when they have the same brand name
|
Chris@1397
|
2284 std::ostringstream os;
|
Chris@1397
|
2285 os << " ";
|
Chris@1397
|
2286 os << portNumber;
|
Chris@1397
|
2287 stringName += os.str();
|
Chris@1397
|
2288
|
Chris@1397
|
2289 return stringName;
|
Chris@1397
|
2290 }
|
Chris@1397
|
2291
|
Chris@1397
|
2292 void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )
|
Chris@559
|
2293 {
|
Chris@559
|
2294 if ( connected_ ) {
|
Chris@1397
|
2295 errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!";
|
Chris@1397
|
2296 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2297 return;
|
Chris@559
|
2298 }
|
Chris@559
|
2299
|
Chris@559
|
2300 unsigned int nDevices = midiOutGetNumDevs();
|
Chris@559
|
2301 if (nDevices < 1) {
|
Chris@1397
|
2302 errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!";
|
Chris@1397
|
2303 error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
|
Chris@1397
|
2304 return;
|
Chris@559
|
2305 }
|
Chris@559
|
2306
|
Chris@559
|
2307 if ( portNumber >= nDevices ) {
|
Chris@1397
|
2308 std::ostringstream ost;
|
Chris@1397
|
2309 ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@559
|
2310 errorString_ = ost.str();
|
Chris@1397
|
2311 error( RtMidiError::INVALID_PARAMETER, errorString_ );
|
Chris@1397
|
2312 return;
|
Chris@559
|
2313 }
|
Chris@559
|
2314
|
Chris@559
|
2315 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@559
|
2316 MMRESULT result = midiOutOpen( &data->outHandle,
|
Chris@559
|
2317 portNumber,
|
Chris@559
|
2318 (DWORD)NULL,
|
Chris@559
|
2319 (DWORD)NULL,
|
Chris@559
|
2320 CALLBACK_NULL );
|
Chris@559
|
2321 if ( result != MMSYSERR_NOERROR ) {
|
Chris@1397
|
2322 errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port.";
|
Chris@1397
|
2323 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2324 return;
|
Chris@559
|
2325 }
|
Chris@559
|
2326
|
Chris@559
|
2327 connected_ = true;
|
Chris@559
|
2328 }
|
Chris@559
|
2329
|
Chris@1397
|
2330 void MidiOutWinMM :: closePort( void )
|
Chris@559
|
2331 {
|
Chris@559
|
2332 if ( connected_ ) {
|
Chris@559
|
2333 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@559
|
2334 midiOutReset( data->outHandle );
|
Chris@559
|
2335 midiOutClose( data->outHandle );
|
Chris@559
|
2336 connected_ = false;
|
Chris@559
|
2337 }
|
Chris@559
|
2338 }
|
Chris@559
|
2339
|
Chris@1397
|
2340 void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ )
|
Chris@559
|
2341 {
|
Chris@559
|
2342 // This function cannot be implemented for the Windows MM MIDI API.
|
Chris@1397
|
2343 errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";
|
Chris@1397
|
2344 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2345 }
|
Chris@559
|
2346
|
Chris@1397
|
2347 void MidiOutWinMM :: sendMessage( std::vector<unsigned char> *message )
|
Chris@559
|
2348 {
|
Chris@1397
|
2349 if ( !connected_ ) return;
|
Chris@1397
|
2350
|
Chris@1397
|
2351 unsigned int nBytes = static_cast<unsigned int>(message->size());
|
Chris@559
|
2352 if ( nBytes == 0 ) {
|
Chris@1397
|
2353 errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!";
|
Chris@1397
|
2354 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2355 return;
|
Chris@559
|
2356 }
|
Chris@559
|
2357
|
Chris@559
|
2358 MMRESULT result;
|
Chris@559
|
2359 WinMidiData *data = static_cast<WinMidiData *> (apiData_);
|
Chris@559
|
2360 if ( message->at(0) == 0xF0 ) { // Sysex message
|
Chris@559
|
2361
|
Chris@559
|
2362 // Allocate buffer for sysex data.
|
Chris@559
|
2363 char *buffer = (char *) malloc( nBytes );
|
Chris@559
|
2364 if ( buffer == NULL ) {
|
Chris@1397
|
2365 errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!";
|
Chris@1397
|
2366 error( RtMidiError::MEMORY_ERROR, errorString_ );
|
Chris@1397
|
2367 return;
|
Chris@559
|
2368 }
|
Chris@559
|
2369
|
Chris@559
|
2370 // Copy data to buffer.
|
Chris@1397
|
2371 for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message->at(i);
|
Chris@559
|
2372
|
Chris@559
|
2373 // Create and prepare MIDIHDR structure.
|
Chris@559
|
2374 MIDIHDR sysex;
|
Chris@559
|
2375 sysex.lpData = (LPSTR) buffer;
|
Chris@559
|
2376 sysex.dwBufferLength = nBytes;
|
Chris@559
|
2377 sysex.dwFlags = 0;
|
Chris@559
|
2378 result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) );
|
Chris@559
|
2379 if ( result != MMSYSERR_NOERROR ) {
|
Chris@559
|
2380 free( buffer );
|
Chris@1397
|
2381 errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header.";
|
Chris@1397
|
2382 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2383 return;
|
Chris@559
|
2384 }
|
Chris@559
|
2385
|
Chris@559
|
2386 // Send the message.
|
Chris@559
|
2387 result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) );
|
Chris@559
|
2388 if ( result != MMSYSERR_NOERROR ) {
|
Chris@559
|
2389 free( buffer );
|
Chris@1397
|
2390 errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message.";
|
Chris@1397
|
2391 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2392 return;
|
Chris@559
|
2393 }
|
Chris@559
|
2394
|
Chris@559
|
2395 // Unprepare the buffer and MIDIHDR.
|
Chris@559
|
2396 while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 );
|
Chris@559
|
2397 free( buffer );
|
Chris@559
|
2398 }
|
Chris@559
|
2399 else { // Channel or system message.
|
Chris@559
|
2400
|
Chris@559
|
2401 // Make sure the message size isn't too big.
|
Chris@559
|
2402 if ( nBytes > 3 ) {
|
Chris@1397
|
2403 errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!";
|
Chris@1397
|
2404 error( RtMidiError::WARNING, errorString_ );
|
Chris@559
|
2405 return;
|
Chris@559
|
2406 }
|
Chris@559
|
2407
|
Chris@559
|
2408 // Pack MIDI bytes into double word.
|
Chris@559
|
2409 DWORD packet;
|
Chris@559
|
2410 unsigned char *ptr = (unsigned char *) &packet;
|
Chris@1397
|
2411 for ( unsigned int i=0; i<nBytes; ++i ) {
|
Chris@559
|
2412 *ptr = message->at(i);
|
Chris@1397
|
2413 ++ptr;
|
Chris@559
|
2414 }
|
Chris@559
|
2415
|
Chris@559
|
2416 // Send the message immediately.
|
Chris@559
|
2417 result = midiOutShortMsg( data->outHandle, packet );
|
Chris@559
|
2418 if ( result != MMSYSERR_NOERROR ) {
|
Chris@1397
|
2419 errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";
|
Chris@1397
|
2420 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@559
|
2421 }
|
Chris@559
|
2422 }
|
Chris@559
|
2423 }
|
Chris@559
|
2424
|
Chris@559
|
2425 #endif // __WINDOWS_MM__
|
Chris@609
|
2426
|
Chris@1397
|
2427
|
Chris@1397
|
2428 //*********************************************************************//
|
Chris@1397
|
2429 // API: UNIX JACK
|
Chris@1397
|
2430 //
|
Chris@1397
|
2431 // Written primarily by Alexander Svetalkin, with updates for delta
|
Chris@1397
|
2432 // time by Gary Scavone, April 2011.
|
Chris@1397
|
2433 //
|
Chris@1397
|
2434 // *********************************************************************//
|
Chris@1397
|
2435
|
Chris@1397
|
2436 #if defined(__UNIX_JACK__)
|
Chris@1397
|
2437
|
Chris@1397
|
2438 // JACK header files
|
Chris@1397
|
2439 #include <jack/jack.h>
|
Chris@1397
|
2440 #include <jack/midiport.h>
|
Chris@1397
|
2441 #include <jack/ringbuffer.h>
|
Chris@1397
|
2442
|
Chris@1397
|
2443 #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer
|
Chris@1397
|
2444
|
Chris@1397
|
2445 struct JackMidiData {
|
Chris@1397
|
2446 jack_client_t *client;
|
Chris@1397
|
2447 jack_port_t *port;
|
Chris@1397
|
2448 jack_ringbuffer_t *buffSize;
|
Chris@1397
|
2449 jack_ringbuffer_t *buffMessage;
|
Chris@1397
|
2450 jack_time_t lastTime;
|
Chris@1397
|
2451 MidiInApi :: RtMidiInData *rtMidiIn;
|
Chris@1397
|
2452 };
|
Chris@1397
|
2453
|
Chris@1397
|
2454 //*********************************************************************//
|
Chris@1397
|
2455 // API: JACK
|
Chris@1397
|
2456 // Class Definitions: MidiInJack
|
Chris@1397
|
2457 //*********************************************************************//
|
Chris@1397
|
2458
|
Chris@1397
|
2459 static int jackProcessIn( jack_nframes_t nframes, void *arg )
|
Chris@609
|
2460 {
|
Chris@1397
|
2461 JackMidiData *jData = (JackMidiData *) arg;
|
Chris@1397
|
2462 MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn;
|
Chris@1397
|
2463 jack_midi_event_t event;
|
Chris@1397
|
2464 jack_time_t time;
|
Chris@1397
|
2465
|
Chris@1397
|
2466 // Is port created?
|
Chris@1397
|
2467 if ( jData->port == NULL ) return 0;
|
Chris@1397
|
2468 void *buff = jack_port_get_buffer( jData->port, nframes );
|
Chris@1397
|
2469
|
Chris@1397
|
2470 // We have midi events in buffer
|
Chris@1397
|
2471 int evCount = jack_midi_get_event_count( buff );
|
Chris@1397
|
2472 for (int j = 0; j < evCount; j++) {
|
Chris@1397
|
2473 MidiInApi::MidiMessage message;
|
Chris@1397
|
2474 message.bytes.clear();
|
Chris@1397
|
2475
|
Chris@1397
|
2476 jack_midi_event_get( &event, buff, j );
|
Chris@1397
|
2477
|
Chris@1397
|
2478 for ( unsigned int i = 0; i < event.size; i++ )
|
Chris@1397
|
2479 message.bytes.push_back( event.buffer[i] );
|
Chris@1397
|
2480
|
Chris@1397
|
2481 // Compute the delta time.
|
Chris@1397
|
2482 time = jack_get_time();
|
Chris@1397
|
2483 if ( rtData->firstMessage == true )
|
Chris@1397
|
2484 rtData->firstMessage = false;
|
Chris@1397
|
2485 else
|
Chris@1397
|
2486 message.timeStamp = ( time - jData->lastTime ) * 0.000001;
|
Chris@1397
|
2487
|
Chris@1397
|
2488 jData->lastTime = time;
|
Chris@1397
|
2489
|
Chris@1397
|
2490 if ( !rtData->continueSysex ) {
|
Chris@1397
|
2491 if ( rtData->usingCallback ) {
|
Chris@1397
|
2492 RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback;
|
Chris@1397
|
2493 callback( message.timeStamp, &message.bytes, rtData->userData );
|
Chris@1397
|
2494 }
|
Chris@1397
|
2495 else {
|
Chris@1397
|
2496 // As long as we haven't reached our queue size limit, push the message.
|
Chris@1397
|
2497 if ( rtData->queue.size < rtData->queue.ringSize ) {
|
Chris@1397
|
2498 rtData->queue.ring[rtData->queue.back++] = message;
|
Chris@1397
|
2499 if ( rtData->queue.back == rtData->queue.ringSize )
|
Chris@1397
|
2500 rtData->queue.back = 0;
|
Chris@1397
|
2501 rtData->queue.size++;
|
Chris@1397
|
2502 }
|
Chris@1397
|
2503 else
|
Chris@1397
|
2504 std::cerr << "\nMidiInJack: message queue limit reached!!\n\n";
|
Chris@1397
|
2505 }
|
Chris@1397
|
2506 }
|
Chris@1397
|
2507 }
|
Chris@1397
|
2508
|
Chris@1397
|
2509 return 0;
|
Chris@609
|
2510 }
|
Chris@609
|
2511
|
Chris@1397
|
2512 MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
|
Chris@609
|
2513 {
|
Chris@1397
|
2514 initialize( clientName );
|
Chris@609
|
2515 }
|
Chris@609
|
2516
|
Chris@1397
|
2517 void MidiInJack :: initialize( const std::string& clientName )
|
Chris@609
|
2518 {
|
Chris@1397
|
2519 JackMidiData *data = new JackMidiData;
|
Chris@1397
|
2520 apiData_ = (void *) data;
|
Chris@1397
|
2521
|
Chris@1397
|
2522 data->rtMidiIn = &inputData_;
|
Chris@1397
|
2523 data->port = NULL;
|
Chris@1397
|
2524 data->client = NULL;
|
Chris@1397
|
2525 this->clientName = clientName;
|
Chris@1397
|
2526
|
Chris@1397
|
2527 connect();
|
Chris@609
|
2528 }
|
Chris@609
|
2529
|
Chris@1397
|
2530 void MidiInJack :: connect()
|
Chris@609
|
2531 {
|
Chris@1397
|
2532 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2533 if ( data->client )
|
Chris@1397
|
2534 return;
|
Chris@1397
|
2535
|
Chris@1397
|
2536 // Initialize JACK client
|
Chris@1397
|
2537 if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
|
Chris@1397
|
2538 errorString_ = "MidiInJack::initialize: JACK server not running?";
|
Chris@1397
|
2539 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2540 return;
|
Chris@1397
|
2541 }
|
Chris@1397
|
2542
|
Chris@1397
|
2543 jack_set_process_callback( data->client, jackProcessIn, data );
|
Chris@1397
|
2544 jack_activate( data->client );
|
Chris@609
|
2545 }
|
Chris@609
|
2546
|
Chris@1397
|
2547 MidiInJack :: ~MidiInJack()
|
Chris@609
|
2548 {
|
Chris@1397
|
2549 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2550 closePort();
|
Chris@1397
|
2551
|
Chris@1397
|
2552 if ( data->client )
|
Chris@1397
|
2553 jack_client_close( data->client );
|
Chris@1397
|
2554 delete data;
|
Chris@609
|
2555 }
|
Chris@609
|
2556
|
Chris@1397
|
2557 void MidiInJack :: openPort( unsigned int portNumber, const std::string portName )
|
Chris@609
|
2558 {
|
Chris@1397
|
2559 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2560
|
Chris@1397
|
2561 connect();
|
Chris@1397
|
2562
|
Chris@1397
|
2563 // Creating new port
|
Chris@1397
|
2564 if ( data->port == NULL)
|
Chris@1397
|
2565 data->port = jack_port_register( data->client, portName.c_str(),
|
Chris@1397
|
2566 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
|
Chris@1397
|
2567
|
Chris@1397
|
2568 if ( data->port == NULL) {
|
Chris@1397
|
2569 errorString_ = "MidiInJack::openPort: JACK error creating port";
|
Chris@1397
|
2570 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2571 return;
|
Chris@1397
|
2572 }
|
Chris@1397
|
2573
|
Chris@1397
|
2574 // Connecting to the output
|
Chris@1397
|
2575 std::string name = getPortName( portNumber );
|
Chris@1397
|
2576 jack_connect( data->client, name.c_str(), jack_port_name( data->port ) );
|
Chris@1397
|
2577 }
|
Chris@1397
|
2578
|
Chris@1397
|
2579 void MidiInJack :: openVirtualPort( const std::string portName )
|
Chris@1397
|
2580 {
|
Chris@1397
|
2581 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2582
|
Chris@1397
|
2583 connect();
|
Chris@1397
|
2584 if ( data->port == NULL )
|
Chris@1397
|
2585 data->port = jack_port_register( data->client, portName.c_str(),
|
Chris@1397
|
2586 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );
|
Chris@1397
|
2587
|
Chris@1397
|
2588 if ( data->port == NULL ) {
|
Chris@1397
|
2589 errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";
|
Chris@1397
|
2590 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2591 }
|
Chris@1397
|
2592 }
|
Chris@1397
|
2593
|
Chris@1397
|
2594 unsigned int MidiInJack :: getPortCount()
|
Chris@1397
|
2595 {
|
Chris@1397
|
2596 int count = 0;
|
Chris@1397
|
2597 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2598 connect();
|
Chris@1397
|
2599 if ( !data->client )
|
Chris@609
|
2600 return 0;
|
Chris@1397
|
2601
|
Chris@1397
|
2602 // List of available ports
|
Chris@1397
|
2603 const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
|
Chris@1397
|
2604
|
Chris@1397
|
2605 if ( ports == NULL ) return 0;
|
Chris@1397
|
2606 while ( ports[count] != NULL )
|
Chris@1397
|
2607 count++;
|
Chris@1397
|
2608
|
Chris@1397
|
2609 free( ports );
|
Chris@1397
|
2610
|
Chris@1397
|
2611 return count;
|
Chris@609
|
2612 }
|
Chris@609
|
2613
|
Chris@1397
|
2614 std::string MidiInJack :: getPortName( unsigned int portNumber )
|
Chris@609
|
2615 {
|
Chris@1397
|
2616 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2617 std::string retStr("");
|
Chris@1397
|
2618
|
Chris@1397
|
2619 connect();
|
Chris@1397
|
2620
|
Chris@1397
|
2621 // List of available ports
|
Chris@1397
|
2622 const char **ports = jack_get_ports( data->client, NULL,
|
Chris@1397
|
2623 JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );
|
Chris@1397
|
2624
|
Chris@1397
|
2625 // Check port validity
|
Chris@1397
|
2626 if ( ports == NULL ) {
|
Chris@1397
|
2627 errorString_ = "MidiInJack::getPortName: no ports available!";
|
Chris@1397
|
2628 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2629 return retStr;
|
Chris@1397
|
2630 }
|
Chris@1397
|
2631
|
Chris@1397
|
2632 if ( ports[portNumber] == NULL ) {
|
Chris@1397
|
2633 std::ostringstream ost;
|
Chris@1397
|
2634 ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@1397
|
2635 errorString_ = ost.str();
|
Chris@1397
|
2636 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2637 }
|
Chris@1397
|
2638 else retStr.assign( ports[portNumber] );
|
Chris@1397
|
2639
|
Chris@1397
|
2640 free( ports );
|
Chris@1397
|
2641 return retStr;
|
Chris@609
|
2642 }
|
Chris@609
|
2643
|
Chris@1397
|
2644 void MidiInJack :: closePort()
|
Chris@609
|
2645 {
|
Chris@1397
|
2646 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2647
|
Chris@1397
|
2648 if ( data->port == NULL ) return;
|
Chris@1397
|
2649 jack_port_unregister( data->client, data->port );
|
Chris@1397
|
2650 data->port = NULL;
|
Chris@1397
|
2651 }
|
Chris@1397
|
2652
|
Chris@1397
|
2653 //*********************************************************************//
|
Chris@1397
|
2654 // API: JACK
|
Chris@1397
|
2655 // Class Definitions: MidiOutJack
|
Chris@1397
|
2656 //*********************************************************************//
|
Chris@1397
|
2657
|
Chris@1397
|
2658 // Jack process callback
|
Chris@1397
|
2659 static int jackProcessOut( jack_nframes_t nframes, void *arg )
|
Chris@1397
|
2660 {
|
Chris@1397
|
2661 JackMidiData *data = (JackMidiData *) arg;
|
Chris@1397
|
2662 jack_midi_data_t *midiData;
|
Chris@1397
|
2663 int space;
|
Chris@1397
|
2664
|
Chris@1397
|
2665 // Is port created?
|
Chris@1397
|
2666 if ( data->port == NULL ) return 0;
|
Chris@1397
|
2667
|
Chris@1397
|
2668 void *buff = jack_port_get_buffer( data->port, nframes );
|
Chris@1397
|
2669 jack_midi_clear_buffer( buff );
|
Chris@1397
|
2670
|
Chris@1397
|
2671 while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) {
|
Chris@1397
|
2672 jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) );
|
Chris@1397
|
2673 midiData = jack_midi_event_reserve( buff, 0, space );
|
Chris@1397
|
2674
|
Chris@1397
|
2675 jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space );
|
Chris@1397
|
2676 }
|
Chris@1397
|
2677
|
Chris@1397
|
2678 return 0;
|
Chris@1397
|
2679 }
|
Chris@1397
|
2680
|
Chris@1397
|
2681 MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi()
|
Chris@1397
|
2682 {
|
Chris@1397
|
2683 initialize( clientName );
|
Chris@1397
|
2684 }
|
Chris@1397
|
2685
|
Chris@1397
|
2686 void MidiOutJack :: initialize( const std::string& clientName )
|
Chris@1397
|
2687 {
|
Chris@1397
|
2688 JackMidiData *data = new JackMidiData;
|
Chris@1397
|
2689 apiData_ = (void *) data;
|
Chris@1397
|
2690
|
Chris@1397
|
2691 data->port = NULL;
|
Chris@1397
|
2692 data->client = NULL;
|
Chris@1397
|
2693 this->clientName = clientName;
|
Chris@1397
|
2694
|
Chris@1397
|
2695 connect();
|
Chris@1397
|
2696 }
|
Chris@1397
|
2697
|
Chris@1397
|
2698 void MidiOutJack :: connect()
|
Chris@1397
|
2699 {
|
Chris@1397
|
2700 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2701 if ( data->client )
|
Chris@1397
|
2702 return;
|
Chris@1397
|
2703
|
Chris@1397
|
2704 // Initialize output ringbuffers
|
Chris@1397
|
2705 data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
|
Chris@1397
|
2706 data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );
|
Chris@1397
|
2707
|
Chris@1397
|
2708 // Initialize JACK client
|
Chris@1397
|
2709 if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {
|
Chris@1397
|
2710 errorString_ = "MidiOutJack::initialize: JACK server not running?";
|
Chris@1397
|
2711 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2712 return;
|
Chris@1397
|
2713 }
|
Chris@1397
|
2714
|
Chris@1397
|
2715 jack_set_process_callback( data->client, jackProcessOut, data );
|
Chris@1397
|
2716 jack_activate( data->client );
|
Chris@1397
|
2717 }
|
Chris@1397
|
2718
|
Chris@1397
|
2719 MidiOutJack :: ~MidiOutJack()
|
Chris@1397
|
2720 {
|
Chris@1397
|
2721 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2722 closePort();
|
Chris@1397
|
2723
|
Chris@1397
|
2724 // Cleanup
|
Chris@1397
|
2725 jack_ringbuffer_free( data->buffSize );
|
Chris@1397
|
2726 jack_ringbuffer_free( data->buffMessage );
|
Chris@1397
|
2727 if ( data->client ) {
|
Chris@1397
|
2728 jack_client_close( data->client );
|
Chris@1397
|
2729 }
|
Chris@1397
|
2730
|
Chris@1397
|
2731 delete data;
|
Chris@1397
|
2732 }
|
Chris@1397
|
2733
|
Chris@1397
|
2734 void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName )
|
Chris@1397
|
2735 {
|
Chris@1397
|
2736 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2737
|
Chris@1397
|
2738 connect();
|
Chris@1397
|
2739
|
Chris@1397
|
2740 // Creating new port
|
Chris@1397
|
2741 if ( data->port == NULL )
|
Chris@1397
|
2742 data->port = jack_port_register( data->client, portName.c_str(),
|
Chris@1397
|
2743 JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
|
Chris@1397
|
2744
|
Chris@1397
|
2745 if ( data->port == NULL ) {
|
Chris@1397
|
2746 errorString_ = "MidiOutJack::openPort: JACK error creating port";
|
Chris@1397
|
2747 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2748 return;
|
Chris@1397
|
2749 }
|
Chris@1397
|
2750
|
Chris@1397
|
2751 // Connecting to the output
|
Chris@1397
|
2752 std::string name = getPortName( portNumber );
|
Chris@1397
|
2753 jack_connect( data->client, jack_port_name( data->port ), name.c_str() );
|
Chris@1397
|
2754 }
|
Chris@1397
|
2755
|
Chris@1397
|
2756 void MidiOutJack :: openVirtualPort( const std::string portName )
|
Chris@1397
|
2757 {
|
Chris@1397
|
2758 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2759
|
Chris@1397
|
2760 connect();
|
Chris@1397
|
2761 if ( data->port == NULL )
|
Chris@1397
|
2762 data->port = jack_port_register( data->client, portName.c_str(),
|
Chris@1397
|
2763 JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );
|
Chris@1397
|
2764
|
Chris@1397
|
2765 if ( data->port == NULL ) {
|
Chris@1397
|
2766 errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";
|
Chris@1397
|
2767 error( RtMidiError::DRIVER_ERROR, errorString_ );
|
Chris@1397
|
2768 }
|
Chris@1397
|
2769 }
|
Chris@1397
|
2770
|
Chris@1397
|
2771 unsigned int MidiOutJack :: getPortCount()
|
Chris@1397
|
2772 {
|
Chris@1397
|
2773 int count = 0;
|
Chris@1397
|
2774 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2775 connect();
|
Chris@1397
|
2776 if ( !data->client )
|
Chris@609
|
2777 return 0;
|
Chris@1397
|
2778
|
Chris@1397
|
2779 // List of available ports
|
Chris@1397
|
2780 const char **ports = jack_get_ports( data->client, NULL,
|
Chris@1397
|
2781 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
|
Chris@1397
|
2782
|
Chris@1397
|
2783 if ( ports == NULL ) return 0;
|
Chris@1397
|
2784 while ( ports[count] != NULL )
|
Chris@1397
|
2785 count++;
|
Chris@1397
|
2786
|
Chris@1397
|
2787 free( ports );
|
Chris@1397
|
2788
|
Chris@1397
|
2789 return count;
|
Chris@609
|
2790 }
|
Chris@609
|
2791
|
Chris@1397
|
2792 std::string MidiOutJack :: getPortName( unsigned int portNumber )
|
Chris@609
|
2793 {
|
Chris@1397
|
2794 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2795 std::string retStr("");
|
Chris@1397
|
2796
|
Chris@1397
|
2797 connect();
|
Chris@1397
|
2798
|
Chris@1397
|
2799 // List of available ports
|
Chris@1397
|
2800 const char **ports = jack_get_ports( data->client, NULL,
|
Chris@1397
|
2801 JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );
|
Chris@1397
|
2802
|
Chris@1397
|
2803 // Check port validity
|
Chris@1397
|
2804 if ( ports == NULL) {
|
Chris@1397
|
2805 errorString_ = "MidiOutJack::getPortName: no ports available!";
|
Chris@1397
|
2806 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2807 return retStr;
|
Chris@1397
|
2808 }
|
Chris@1397
|
2809
|
Chris@1397
|
2810 if ( ports[portNumber] == NULL) {
|
Chris@1397
|
2811 std::ostringstream ost;
|
Chris@1397
|
2812 ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";
|
Chris@1397
|
2813 errorString_ = ost.str();
|
Chris@1397
|
2814 error( RtMidiError::WARNING, errorString_ );
|
Chris@1397
|
2815 }
|
Chris@1397
|
2816 else retStr.assign( ports[portNumber] );
|
Chris@1397
|
2817
|
Chris@1397
|
2818 free( ports );
|
Chris@1397
|
2819 return retStr;
|
Chris@609
|
2820 }
|
Chris@609
|
2821
|
Chris@1397
|
2822 void MidiOutJack :: closePort()
|
Chris@609
|
2823 {
|
Chris@1397
|
2824 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2825
|
Chris@1397
|
2826 if ( data->port == NULL ) return;
|
Chris@1397
|
2827 jack_port_unregister( data->client, data->port );
|
Chris@1397
|
2828 data->port = NULL;
|
Chris@609
|
2829 }
|
Chris@609
|
2830
|
Chris@1397
|
2831 void MidiOutJack :: sendMessage( std::vector<unsigned char> *message )
|
Chris@609
|
2832 {
|
Chris@1397
|
2833 int nBytes = message->size();
|
Chris@1397
|
2834 JackMidiData *data = static_cast<JackMidiData *> (apiData_);
|
Chris@1397
|
2835
|
Chris@1397
|
2836 // Write full message to buffer
|
Chris@1397
|
2837 jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0],
|
Chris@1397
|
2838 message->size() );
|
Chris@1397
|
2839 jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) );
|
Chris@609
|
2840 }
|
Chris@609
|
2841
|
Chris@1397
|
2842 #endif // __UNIX_JACK__
|