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