a64fade8e33fcb6dbdebd6130312fbf7d9f8e057
[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 static void notifyProc (const MIDINotification *message, void *refCon) {
32         CoreMidiIo *self = static_cast<CoreMidiIo*>(refCon);
33         self->notify_proc(message);
34 }
35
36 #ifndef NDEBUG
37 static void print_packet (const MIDIPacket *p) {
38         fprintf (stderr, "CoreMIDI: Packet %d bytes [ ", p->length);
39         for (int bb = 0; bb < p->length; ++bb) {
40                 fprintf (stderr, "%02x ", ((uint8_t*)p->data)[bb]);
41         }
42         fprintf (stderr, "]\n");
43 }
44
45 static void dump_packet_list (const UInt32 numPackets, MIDIPacket const *p) {
46         for (UInt32 i = 0; i < numPackets; ++i) {
47                 print_packet (p);
48                 p = MIDIPacketNext (p);
49         }
50 }
51 #endif
52
53 static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
54         CoreMidiIo *self = static_cast<CoreMidiIo*> (procRef);
55         if (!self || !self->enabled()) {
56                 // skip while freewheeling
57 #ifndef NDEBUG
58                 if (_debug_mode & 2) {
59                         fprintf (stderr, "Ignored Midi Packet while freewheeling:\n");
60                         dump_packet_list (list->numPackets, &list->packet[0]);
61                 }
62 #endif
63                 return;
64         }
65         RingBuffer<uint8_t> * rb  = static_cast<RingBuffer < uint8_t > *> (srcRef);
66         if (!rb) {
67 #ifndef NDEBUG
68                 if (_debug_mode & 4) {
69                         fprintf (stderr, "Ignored Midi Packet - no ringbuffer:\n");
70                         dump_packet_list (list->numPackets, &list->packet[0]);
71                 }
72 #endif
73                 return;
74         }
75         MIDIPacket const *p = &list->packet[0];
76         for (UInt32 i = 0; i < list->numPackets; ++i) {
77                 uint32_t len = ((p->length + 3)&~3) + sizeof(MIDITimeStamp) + sizeof(UInt16);
78 #ifndef NDEBUG
79                 if (_debug_mode & 1) {
80                         print_packet (p);
81                 }
82 #endif
83                 if (rb->write_space() > sizeof(uint32_t) + len) {
84                         rb->write ((uint8_t*)&len, sizeof(uint32_t));
85                         rb->write ((uint8_t*)p, len);
86                 }
87 #ifndef NDEBUG
88                 else {
89                         fprintf (stderr, "CoreMIDI: dropped MIDI event\n");
90                 }
91 #endif
92                 p = MIDIPacketNext (p);
93         }
94 }
95
96 static std::string getPropertyString (MIDIObjectRef object, CFStringRef key)
97 {
98         CFStringRef name = NULL;
99         std::string rv = "";
100         if (noErr == MIDIObjectGetStringProperty(object, key, &name)) {
101                 const CFIndex size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(name), kCFStringEncodingUTF8);
102                 char *tmp = (char*) malloc(size);
103                 if (CFStringGetCString(name, tmp, size, kCFStringEncodingUTF8)) {
104                         rv = tmp;
105                 }
106                 free(tmp);
107                 CFRelease(name);
108         }
109         return rv;
110 }
111
112 static std::string getDisplayName (MIDIObjectRef object) {
113         return getPropertyString(object, kMIDIPropertyDisplayName);
114 }
115
116 CoreMidiIo::CoreMidiIo()
117         : _midi_client (0)
118         , _input_endpoints (0)
119         , _output_endpoints (0)
120         , _input_ports (0)
121         , _output_ports (0)
122         , _input_queue (0)
123         , _rb (0)
124         , _n_midi_in (0)
125         , _n_midi_out (0)
126         , _time_at_cycle_start (0)
127         , _active (false)
128         , _enabled (true)
129         , _run (false)
130         , _changed_callback (0)
131         , _changed_arg (0)
132 {
133         pthread_mutex_init (&_discovery_lock, 0);
134
135 #ifndef NDEBUG
136         const char *p = getenv ("COREMIDIDEBUG");
137         if (p && *p) _debug_mode = atoi (p);
138 #endif
139 }
140
141 CoreMidiIo::~CoreMidiIo()
142 {
143         pthread_mutex_lock (&_discovery_lock);
144         cleanup();
145         if (_midi_client) {
146                 MIDIClientDispose(_midi_client);
147                 _midi_client = 0;
148         }
149         pthread_mutex_unlock (&_discovery_lock);
150         pthread_mutex_destroy (&_discovery_lock);
151 }
152
153 void
154 CoreMidiIo::cleanup()
155 {
156         _active = false;
157         for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
158                 MIDIPortDispose(_input_ports[i]);
159                 _input_queue[i].clear();
160                 delete _rb[i];
161         }
162         for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
163                 MIDIPortDispose(_output_ports[i]);
164         }
165
166         free(_input_ports); _input_ports = 0;
167         free(_input_endpoints); _input_endpoints = 0;
168         free(_input_queue); _input_queue = 0;
169         free(_output_ports); _output_ports = 0;
170         free(_output_endpoints); _output_endpoints = 0;
171         free(_rb); _rb = 0;
172
173         _n_midi_in = 0;
174         _n_midi_out = 0;
175 }
176
177 void
178 CoreMidiIo::start_cycle()
179 {
180         _time_at_cycle_start = AudioGetCurrentHostTime();
181 }
182
183 void
184 CoreMidiIo::notify_proc(const MIDINotification *message)
185 {
186         switch(message->messageID) {
187                 case kMIDIMsgSetupChanged:
188                         /* this one catches all of the added/removed/changed below */
189                         //printf("kMIDIMsgSetupChanged\n");
190                         discover();
191                         break;
192                 case kMIDIMsgObjectAdded:
193                         {
194                         //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
195                         //printf("kMIDIMsgObjectAdded\n");
196                         }
197                         break;
198                 case kMIDIMsgObjectRemoved:
199                         {
200                         //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
201                         //printf("kMIDIMsgObjectRemoved\n");
202                         }
203                         break;
204                 case kMIDIMsgPropertyChanged:
205                         {
206                         //const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
207                         //printf("kMIDIMsgObjectRemoved\n");
208                         }
209                         break;
210                 case kMIDIMsgThruConnectionsChanged:
211                         //printf("kMIDIMsgThruConnectionsChanged\n");
212                         break;
213                 case kMIDIMsgSerialPortOwnerChanged:
214                         //printf("kMIDIMsgSerialPortOwnerChanged\n");
215                         break;
216                 case kMIDIMsgIOError:
217                         fprintf(stderr, "kMIDIMsgIOError\n");
218                         discover();
219                         break;
220         }
221 }
222
223 size_t
224 CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
225 {
226         if (!_active || _time_at_cycle_start == 0) {
227                 return 0;
228         }
229         assert(port < _n_midi_in);
230
231         const size_t minsize = 1 + sizeof(uint32_t) + sizeof(MIDITimeStamp) + sizeof(UInt16);
232
233         while (_rb[port]->read_space() > minsize) {
234                 MIDIPacket packet;
235                 size_t rv;
236                 uint32_t s = 0;
237                 rv = _rb[port]->read((uint8_t*)&s, sizeof(uint32_t));
238                 assert(rv == sizeof(uint32_t));
239                 rv = _rb[port]->read((uint8_t*)&packet, s);
240                 assert(rv == s);
241                 _input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
242         }
243
244         UInt64 start = _time_at_cycle_start;
245         UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
246
247         for (CoreMIDIQueue::iterator it = _input_queue[port].begin (); it != _input_queue[port].end (); ) {
248                 if ((*it)->timeStamp < end) {
249                         if ((*it)->timeStamp < start) {
250                                 uint64_t dt = AudioConvertHostTimeToNanos(start - (*it)->timeStamp);
251                                 if (dt > 1e7) { // 10ms,
252 #ifndef NDEBUG
253                                         printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
254 #endif
255                                         it = _input_queue[port].erase(it);
256                                         continue;
257                                 } else {
258 #if 0
259                                         printf("Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
260 #endif
261                                 }
262                                 time = 0;
263                         } else {
264                                 time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
265                         }
266                         s = std::min(s, (size_t) (*it)->length);
267                         if (s > 0) {
268                                 memcpy(d, (*it)->data, s);
269                         }
270                         _input_queue[port].erase(it);
271                         return s;
272                 }
273                 ++it;
274
275         }
276         return 0;
277 }
278
279 int
280 CoreMidiIo::send_events (uint32_t port, double timescale, const void *b)
281 {
282         if (!_active || _time_at_cycle_start == 0) {
283                 return 0;
284         }
285
286         assert(port < _n_midi_out);
287         const UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
288
289         const CoreMidiBuffer *src = static_cast<CoreMidiBuffer const *>(b);
290
291         int32_t bytes[8192]; // int for alignment
292         MIDIPacketList *mpl = (MIDIPacketList*)bytes;
293         MIDIPacket *cur = MIDIPacketListInit(mpl);
294
295         for (CoreMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
296                 assert((*mit)->size() < 256);
297                 cur = MIDIPacketListAdd(mpl, sizeof(bytes), cur,
298                                 AudioConvertNanosToHostTime(ts + (*mit)->timestamp() / timescale),
299                                 (*mit)->size(), (*mit)->data());
300                 if (!cur) {
301 #ifndef DEBUG
302                         printf("CoreMidi: Packet list overflow, dropped events\n");
303 #endif
304                         break;
305                 }
306         }
307         if (mpl->numPackets > 0) {
308                 MIDISend(_output_ports[port], _output_endpoints[port], mpl);
309         }
310         return 0;
311 }
312
313 int
314 CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
315 {
316         if (!_active || _time_at_cycle_start == 0) {
317                 return 0;
318         }
319
320         assert(port < _n_midi_out);
321         UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
322         ts += reltime_us * 1e3;
323
324         // TODO use a single packet list.. queue all events first..
325         MIDIPacketList pl;
326
327         pl.numPackets = 1;
328         MIDIPacket *mp = &(pl.packet[0]);
329
330         mp->timeStamp = AudioConvertNanosToHostTime(ts);
331         mp->length = s;
332         assert(s < 256);
333         memcpy(mp->data, d, s);
334
335         MIDISend(_output_ports[port], _output_endpoints[port], &pl);
336         return 0;
337 }
338
339
340 std::string
341 CoreMidiIo::port_id (uint32_t port, bool input)
342 {
343         std::stringstream ss;
344         if (input) {
345                 ss << "system:midi_capture_";
346                 SInt32 id;
347                 if (noErr == MIDIObjectGetIntegerProperty(_input_endpoints[port], kMIDIPropertyUniqueID, &id)) {
348                         ss << (unsigned int)id;
349                 } else {
350                         ss << port;
351                 }
352         } else {
353                 ss << "system:midi_playback_";
354                 SInt32 id;
355                 if (noErr == MIDIObjectGetIntegerProperty(_output_endpoints[port], kMIDIPropertyUniqueID, &id)) {
356                         ss << (unsigned int)id;
357                 } else {
358                         ss << port;
359                 }
360         }
361         return ss.str();
362 }
363
364 std::string
365 CoreMidiIo::port_name (uint32_t port, bool input)
366 {
367         if (input) {
368                 if (port < _n_midi_in) {
369                         return getDisplayName(_input_endpoints[port]);
370                 }
371         } else {
372                 if (port < _n_midi_out) {
373                         return getDisplayName(_output_endpoints[port]);
374                 }
375         }
376         return "";
377 }
378
379 void
380 CoreMidiIo::start () {
381         _run = true;
382         if (!_midi_client) {
383                 OSStatus err;
384                 err = MIDIClientCreate(CFSTR("Ardour"), &notifyProc, this, &_midi_client);
385                 if (noErr != err) {
386                         fprintf(stderr, "Creating Midi Client failed\n");
387                 }
388         }
389         discover();
390 }
391
392 void
393 CoreMidiIo::stop ()
394 {
395         _run = false;
396         pthread_mutex_lock (&_discovery_lock);
397         cleanup();
398         pthread_mutex_unlock (&_discovery_lock);
399 #if 0
400         if (_midi_client) {
401                 MIDIClientDispose(_midi_client);
402                 _midi_client = 0;
403         }
404 #endif
405 }
406
407 void
408 CoreMidiIo::discover()
409 {
410         if (!_run || !_midi_client) { return; }
411
412         if (pthread_mutex_trylock (&_discovery_lock)) {
413                 return;
414         }
415
416         cleanup();
417
418         ItemCount srcCount = MIDIGetNumberOfSources();
419         ItemCount dstCount = MIDIGetNumberOfDestinations();
420
421         if (srcCount > 0) {
422                 _input_ports = (MIDIPortRef *) malloc (srcCount * sizeof(MIDIPortRef));
423                 _input_endpoints = (MIDIEndpointRef*) malloc (srcCount * sizeof(MIDIEndpointRef));
424                 _input_queue = (CoreMIDIQueue*) calloc (srcCount, sizeof(CoreMIDIQueue));
425                 _rb = (RingBuffer<uint8_t> **) malloc (srcCount * sizeof(RingBuffer<uint8_t>*));
426         }
427         if (dstCount > 0) {
428                 _output_ports = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
429                 _output_endpoints = (MIDIEndpointRef*) malloc (dstCount * sizeof(MIDIEndpointRef));
430         }
431
432         for (ItemCount i = 0; i < srcCount; i++) {
433                 OSStatus err;
434                 MIDIEndpointRef src = MIDIGetSource(i);
435                 if (!src) continue;
436 #ifndef NDEBUG
437                 printf("MIDI IN DEVICE: %s\n", getDisplayName(src).c_str());
438 #endif
439
440                 CFStringRef port_name;
441                 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
442
443                 err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
444                 if (noErr != err) {
445                         fprintf(stderr, "Cannot create Midi Output\n");
446                         continue;
447                 }
448                 _rb[_n_midi_in] = new RingBuffer<uint8_t>(32768);
449                 _input_queue[_n_midi_in] = CoreMIDIQueue();
450                 MIDIPortConnectSource(_input_ports[_n_midi_in], src, (void*) _rb[_n_midi_in]);
451                 CFRelease(port_name);
452                 _input_endpoints[_n_midi_in] = src;
453                 ++_n_midi_in;
454         }
455
456         for (ItemCount i = 0; i < dstCount; i++) {
457                 MIDIEndpointRef dst = MIDIGetDestination(i);
458                 CFStringRef port_name;
459                 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_playback_%lu"), i);
460
461                 OSStatus err;
462                 err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
463                 if (noErr != err) {
464                         fprintf(stderr, "Cannot create Midi Output\n");
465                         continue;
466                 }
467
468 #ifndef NDEBUG
469                 printf("MIDI OUT DEVICE: %s\n", getDisplayName(dst).c_str());
470 #endif
471
472                 MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
473                 CFRelease(port_name);
474                 _output_endpoints[_n_midi_out] = dst;
475                 ++_n_midi_out;
476         }
477
478         if (_changed_callback) {
479                 _changed_callback(_changed_arg);
480         }
481
482         _active = true;
483         pthread_mutex_unlock (&_discovery_lock);
484 }