Only build one version of the Portaudio backend that supports both blocking and callb...
[ardour.git] / libs / backends / coreaudio / coremidi_io.cc
1 /*
2  * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <sstream>
20 #include <CoreAudio/HostTime.h>
21
22 #include "coremidi_io.h"
23 #include "coreaudio_backend.h"
24
25 using namespace ARDOUR;
26
27 #ifndef NDEBUG
28 static int _debug_mode = 0;
29 #endif
30
31
32 /** 
33  * MIDI Data flow
34  * 
35  * (A) INPUT (incoming from outside the application)
36  * 
37  *    - midiInputCallback (in its own thread, async WRT process callback):
38  *       takes OS X MIDIPacket, copies into lock-free ringbuffer
39  *
40  *    - processCallback (in its own thread):
41  *
42  *   (1) loop on all input ports:
43  *       1A) call recv_event() to read from ringbuffer into stack buffer, also assign-timestamp, 
44  *       1B) call parse_events() using stack buffer, when appropriate
45  *          pushes CoreMidiEvent into std::vector<CoreMidiEvent>
46  *
47  *   (2) in MidiPort::cycle_start() (also part of the process callback call tree), MidiPort::get_midi_buffer()
48  *       calls CoreAudioBackend::midi_event_get () returns a pointer to the  data of the specified CoreMidiEvent
49  */
50
51 static void notifyProc (const MIDINotification *message, void *refCon) {
52         CoreMidiIo *self = static_cast<CoreMidiIo*>(refCon);
53         self->notify_proc(message);
54 }
55
56 #ifndef NDEBUG
57 static void print_packet (const MIDIPacket *p) {
58         fprintf (stderr, "CoreMIDI: Packet %d bytes [ ", p->length);
59         for (int bb = 0; bb < p->length; ++bb) {
60                 fprintf (stderr, "%02x ", ((uint8_t*)p->data)[bb]);
61         }
62         fprintf (stderr, "]\n");
63 }
64
65 static void dump_packet_list (const UInt32 numPackets, MIDIPacket const *p) {
66         for (UInt32 i = 0; i < numPackets; ++i) {
67                 print_packet (p);
68                 p = MIDIPacketNext (p);
69         }
70 }
71 #endif
72
73 static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
74         CoreMidiIo *self = static_cast<CoreMidiIo*> (procRef);
75         if (!self || !self->enabled()) {
76                 // skip while freewheeling
77 #ifndef NDEBUG
78                 if (_debug_mode & 2) {
79                         fprintf (stderr, "Ignored Midi Packet while freewheeling:\n");
80                         dump_packet_list (list->numPackets, &list->packet[0]);
81                 }
82 #endif
83                 return;
84         }
85         RingBuffer<uint8_t> * rb  = static_cast<RingBuffer < uint8_t > *> (srcRef);
86         if (!rb) {
87 #ifndef NDEBUG
88                 if (_debug_mode & 4) {
89                         fprintf (stderr, "Ignored Midi Packet - no ringbuffer:\n");
90                         dump_packet_list (list->numPackets, &list->packet[0]);
91                 }
92 #endif
93                 return;
94         }
95         MIDIPacket const *p = &list->packet[0];
96         for (UInt32 i = 0; i < list->numPackets; ++i) {
97                 uint32_t len = ((p->length + 3)&~3) + sizeof(MIDITimeStamp) + sizeof(UInt16);
98 #ifndef NDEBUG
99                 if (_debug_mode & 1) {
100                         print_packet (p);
101                 }
102 #endif
103                 if (rb->write_space() > sizeof(uint32_t) + len) {
104                         rb->write ((uint8_t*)&len, sizeof(uint32_t));
105                         rb->write ((uint8_t*)p, len);
106                 }
107 #ifndef NDEBUG
108                 else {
109                         fprintf (stderr, "CoreMIDI: dropped MIDI event\n");
110                 }
111 #endif
112                 p = MIDIPacketNext (p);
113         }
114 }
115
116 static std::string getPropertyString (MIDIObjectRef object, CFStringRef key)
117 {
118         CFStringRef name = NULL;
119         std::string rv = "";
120         if (noErr == MIDIObjectGetStringProperty(object, key, &name)) {
121                 const CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(name), kCFStringEncodingUTF8);
122                 char *tmp = (char*) malloc(size);
123                 if (CFStringGetCString(name, tmp, size, kCFStringEncodingUTF8)) {
124                         rv = tmp;
125                 }
126                 free(tmp);
127                 CFRelease(name);
128         }
129         return rv;
130 }
131
132 static std::string getDisplayName (MIDIObjectRef object) {
133         return getPropertyString(object, kMIDIPropertyDisplayName);
134 }
135
136 CoreMidiIo::CoreMidiIo()
137         : _midi_client (0)
138         , _input_endpoints (0)
139         , _output_endpoints (0)
140         , _input_ports (0)
141         , _output_ports (0)
142         , _input_queue (0)
143         , _rb (0)
144         , _n_midi_in (0)
145         , _n_midi_out (0)
146         , _time_at_cycle_start (0)
147         , _active (false)
148         , _enabled (true)
149         , _run (false)
150         , _changed_callback (0)
151         , _changed_arg (0)
152 {
153         pthread_mutex_init (&_discovery_lock, 0);
154
155 #ifndef NDEBUG
156         const char *p = getenv ("COREMIDIDEBUG");
157         if (p && *p) _debug_mode = atoi (p);
158 #endif
159 }
160
161 CoreMidiIo::~CoreMidiIo()
162 {
163         pthread_mutex_lock (&_discovery_lock);
164         cleanup();
165         if (_midi_client) {
166                 MIDIClientDispose(_midi_client);
167                 _midi_client = 0;
168         }
169         pthread_mutex_unlock (&_discovery_lock);
170         pthread_mutex_destroy (&_discovery_lock);
171 }
172
173 void
174 CoreMidiIo::cleanup()
175 {
176         _active = false;
177         for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
178                 MIDIPortDispose(_input_ports[i]);
179                 _input_queue[i].clear();
180                 delete _rb[i];
181         }
182         for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
183                 MIDIPortDispose(_output_ports[i]);
184         }
185
186         free(_input_ports); _input_ports = 0;
187         free(_input_endpoints); _input_endpoints = 0;
188         free(_input_queue); _input_queue = 0;
189         free(_output_ports); _output_ports = 0;
190         free(_output_endpoints); _output_endpoints = 0;
191         free(_rb); _rb = 0;
192
193         _n_midi_in = 0;
194         _n_midi_out = 0;
195 }
196
197 void
198 CoreMidiIo::start_cycle()
199 {
200         _time_at_cycle_start = AudioGetCurrentHostTime();
201 }
202
203 void
204 CoreMidiIo::notify_proc(const MIDINotification *message)
205 {
206         switch(message->messageID) {
207                 case kMIDIMsgSetupChanged:
208                         /* this one catches all of the added/removed/changed below */
209                         //printf("kMIDIMsgSetupChanged\n");
210                         discover();
211                         break;
212                 case kMIDIMsgObjectAdded:
213                         {
214                         //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
215                         //printf("kMIDIMsgObjectAdded\n");
216                         }
217                         break;
218                 case kMIDIMsgObjectRemoved:
219                         {
220                         //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
221                         //printf("kMIDIMsgObjectRemoved\n");
222                         }
223                         break;
224                 case kMIDIMsgPropertyChanged:
225                         {
226                         //const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
227                         //printf("kMIDIMsgObjectRemoved\n");
228                         }
229                         break;
230                 case kMIDIMsgThruConnectionsChanged:
231                         //printf("kMIDIMsgThruConnectionsChanged\n");
232                         break;
233                 case kMIDIMsgSerialPortOwnerChanged:
234                         //printf("kMIDIMsgSerialPortOwnerChanged\n");
235                         break;
236                 case kMIDIMsgIOError:
237                         fprintf(stderr, "kMIDIMsgIOError\n");
238                         discover();
239                         break;
240         }
241 }
242
243 size_t
244 CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
245 {
246         if (!_active || _time_at_cycle_start == 0) {
247                 return 0;
248         }
249         assert(port < _n_midi_in);
250
251         const size_t minsize = 1 + sizeof(uint32_t) + sizeof(MIDITimeStamp) + sizeof(UInt16);
252
253         while (_rb[port]->read_space() > minsize) {
254                 MIDIPacket packet;
255                 size_t rv;
256                 uint32_t s = 0;
257                 rv = _rb[port]->read((uint8_t*)&s, sizeof(uint32_t));
258                 assert(rv == sizeof(uint32_t));
259                 rv = _rb[port]->read((uint8_t*)&packet, s);
260                 assert(rv == s);
261                 _input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
262         }
263
264         UInt64 start = _time_at_cycle_start;
265         UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
266
267         for (CoreMIDIQueue::iterator it = _input_queue[port].begin (); it != _input_queue[port].end (); ) {
268                 if ((*it)->timeStamp < end) {
269                         if ((*it)->timeStamp < start) {
270                                 uint64_t dt = AudioConvertHostTimeToNanos(start - (*it)->timeStamp);
271                                 if (dt > 1e7) { // 10ms,
272 #ifndef NDEBUG
273                                         printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
274 #endif
275                                         it = _input_queue[port].erase(it);
276                                         continue;
277                                 } else {
278 #if 0
279                                         printf("Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
280 #endif
281                                 }
282                                 time = 0;
283                         } else {
284                                 time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
285                         }
286                         s = std::min(s, (size_t) (*it)->length);
287                         if (s > 0) {
288                                 memcpy(d, (*it)->data, s);
289                         }
290                         _input_queue[port].erase(it);
291                         return s;
292                 }
293                 ++it;
294
295         }
296         return 0;
297 }
298
299 int
300 CoreMidiIo::send_events (uint32_t port, double timescale, const void *b)
301 {
302         if (!_active || _time_at_cycle_start == 0) {
303                 return 0;
304         }
305
306         assert(port < _n_midi_out);
307         const UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
308
309         const CoreMidiBuffer *src = static_cast<CoreMidiBuffer const *>(b);
310
311         int32_t bytes[8192]; // int for alignment
312         MIDIPacketList *mpl = (MIDIPacketList*)bytes;
313         MIDIPacket *cur = MIDIPacketListInit(mpl);
314
315         for (CoreMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
316                 assert((*mit)->size() < 256);
317                 cur = MIDIPacketListAdd(mpl, sizeof(bytes), cur,
318                                 AudioConvertNanosToHostTime(ts + (*mit)->timestamp() / timescale),
319                                 (*mit)->size(), (*mit)->data());
320                 if (!cur) {
321 #ifndef DEBUG
322                         printf("CoreMidi: Packet list overflow, dropped events\n");
323 #endif
324                         break;
325                 }
326         }
327         if (mpl->numPackets > 0) {
328                 MIDISend(_output_ports[port], _output_endpoints[port], mpl);
329         }
330         return 0;
331 }
332
333 int
334 CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
335 {
336         if (!_active || _time_at_cycle_start == 0) {
337                 return 0;
338         }
339
340         assert(port < _n_midi_out);
341         UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
342         ts += reltime_us * 1e3;
343
344         // TODO use a single packet list.. queue all events first..
345         MIDIPacketList pl;
346
347         pl.numPackets = 1;
348         MIDIPacket *mp = &(pl.packet[0]);
349
350         mp->timeStamp = AudioConvertNanosToHostTime(ts);
351         mp->length = s;
352         assert(s < 256);
353         memcpy(mp->data, d, s);
354
355         MIDISend(_output_ports[port], _output_endpoints[port], &pl);
356         return 0;
357 }
358
359
360 std::string
361 CoreMidiIo::port_id (uint32_t port, bool input)
362 {
363         std::stringstream ss;
364         if (input) {
365                 ss << "system:midi_capture_";
366                 SInt32 id;
367                 if (noErr == MIDIObjectGetIntegerProperty(_input_endpoints[port], kMIDIPropertyUniqueID, &id)) {
368                         ss << (unsigned int)id;
369                 } else {
370                         ss << port;
371                 }
372         } else {
373                 ss << "system:midi_playback_";
374                 SInt32 id;
375                 if (noErr == MIDIObjectGetIntegerProperty(_output_endpoints[port], kMIDIPropertyUniqueID, &id)) {
376                         ss << (unsigned int)id;
377                 } else {
378                         ss << port;
379                 }
380         }
381         return ss.str();
382 }
383
384 std::string
385 CoreMidiIo::port_name (uint32_t port, bool input)
386 {
387         if (input) {
388                 if (port < _n_midi_in) {
389                         return getDisplayName(_input_endpoints[port]);
390                 }
391         } else {
392                 if (port < _n_midi_out) {
393                         return getDisplayName(_output_endpoints[port]);
394                 }
395         }
396         return "";
397 }
398
399 void
400 CoreMidiIo::start () {
401         _run = true;
402         if (!_midi_client) {
403                 OSStatus err;
404                 err = MIDIClientCreate(CFSTR("Ardour"), &notifyProc, this, &_midi_client);
405                 if (noErr != err) {
406                         fprintf(stderr, "Creating Midi Client failed\n");
407                 }
408         }
409         discover();
410 }
411
412 void
413 CoreMidiIo::stop ()
414 {
415         _run = false;
416         pthread_mutex_lock (&_discovery_lock);
417         cleanup();
418         pthread_mutex_unlock (&_discovery_lock);
419 #if 0
420         if (_midi_client) {
421                 MIDIClientDispose(_midi_client);
422                 _midi_client = 0;
423         }
424 #endif
425 }
426
427 void
428 CoreMidiIo::discover()
429 {
430         if (!_run || !_midi_client) { return; }
431
432         if (pthread_mutex_trylock (&_discovery_lock)) {
433                 return;
434         }
435
436         cleanup();
437
438         ItemCount srcCount = MIDIGetNumberOfSources();
439         ItemCount dstCount = MIDIGetNumberOfDestinations();
440
441         if (srcCount > 0) {
442                 _input_ports = (MIDIPortRef *) malloc (srcCount * sizeof(MIDIPortRef));
443                 _input_endpoints = (MIDIEndpointRef*) malloc (srcCount * sizeof(MIDIEndpointRef));
444                 _input_queue = (CoreMIDIQueue*) calloc (srcCount, sizeof(CoreMIDIQueue));
445                 _rb = (RingBuffer<uint8_t> **) malloc (srcCount * sizeof(RingBuffer<uint8_t>*));
446         }
447         if (dstCount > 0) {
448                 _output_ports = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
449                 _output_endpoints = (MIDIEndpointRef*) malloc (dstCount * sizeof(MIDIEndpointRef));
450         }
451
452         for (ItemCount i = 0; i < srcCount; i++) {
453                 OSStatus err;
454                 MIDIEndpointRef src = MIDIGetSource(i);
455                 if (!src) continue;
456 #ifndef NDEBUG
457                 printf("MIDI IN DEVICE: %s\n", getDisplayName(src).c_str());
458 #endif
459
460                 CFStringRef port_name;
461                 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
462
463                 err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
464                 if (noErr != err) {
465                         fprintf(stderr, "Cannot create Midi Output\n");
466                         continue;
467                 }
468                 _rb[_n_midi_in] = new RingBuffer<uint8_t>(32768);
469                 _input_queue[_n_midi_in] = CoreMIDIQueue();
470                 MIDIPortConnectSource(_input_ports[_n_midi_in], src, (void*) _rb[_n_midi_in]);
471                 CFRelease(port_name);
472                 _input_endpoints[_n_midi_in] = src;
473                 ++_n_midi_in;
474         }
475
476         for (ItemCount i = 0; i < dstCount; i++) {
477                 MIDIEndpointRef dst = MIDIGetDestination(i);
478                 CFStringRef port_name;
479                 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_playback_%lu"), i);
480
481                 OSStatus err;
482                 err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
483                 if (noErr != err) {
484                         fprintf(stderr, "Cannot create Midi Output\n");
485                         continue;
486                 }
487
488 #ifndef NDEBUG
489                 printf("MIDI OUT DEVICE: %s\n", getDisplayName(dst).c_str());
490 #endif
491
492                 MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
493                 CFRelease(port_name);
494                 _output_endpoints[_n_midi_out] = dst;
495                 ++_n_midi_out;
496         }
497
498         if (_changed_callback) {
499                 _changed_callback(_changed_arg);
500         }
501
502         _active = true;
503         pthread_mutex_unlock (&_discovery_lock);
504 }