2 * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
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.
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.
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.
20 #include <CoreAudio/HostTime.h>
22 #include "coremidi_io.h"
23 #include "coreaudio_backend.h"
25 using namespace ARDOUR;
28 static int _debug_mode = 0;
31 static void notifyProc (const MIDINotification *message, void *refCon) {
32 CoreMidiIo *self = static_cast<CoreMidiIo*>(refCon);
33 self->notify_proc(message);
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]);
42 fprintf (stderr, "]\n");
45 static void dump_packet_list (const UInt32 numPackets, MIDIPacket const *p) {
46 for (UInt32 i = 0; i < numPackets; ++i) {
48 p = MIDIPacketNext (p);
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
58 if (_debug_mode & 2) {
59 fprintf (stderr, "Ignored Midi Packet while freewheeling:\n");
60 dump_packet_list (list->numPackets, &list->packet[0]);
65 RingBuffer<uint8_t> * rb = static_cast<RingBuffer < uint8_t > *> (srcRef);
68 if (_debug_mode & 4) {
69 fprintf (stderr, "Ignored Midi Packet - no ringbuffer:\n");
70 dump_packet_list (list->numPackets, &list->packet[0]);
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);
79 if (_debug_mode & 1) {
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);
89 fprintf (stderr, "CoreMIDI: dropped MIDI event\n");
92 p = MIDIPacketNext (p);
96 static std::string getPropertyString (MIDIObjectRef object, CFStringRef key)
98 CFStringRef name = NULL;
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)) {
112 static std::string getDisplayName (MIDIObjectRef object) {
113 return getPropertyString(object, kMIDIPropertyDisplayName);
116 CoreMidiIo::CoreMidiIo()
118 , _input_endpoints (0)
119 , _output_endpoints (0)
126 , _time_at_cycle_start (0)
130 , _changed_callback (0)
133 pthread_mutex_init (&_discovery_lock, 0);
136 const char *p = getenv ("COREMIDIDEBUG");
137 if (p && *p) _debug_mode = atoi (p);
141 CoreMidiIo::~CoreMidiIo()
143 pthread_mutex_lock (&_discovery_lock);
146 MIDIClientDispose(_midi_client);
149 pthread_mutex_unlock (&_discovery_lock);
150 pthread_mutex_destroy (&_discovery_lock);
154 CoreMidiIo::cleanup()
157 for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
158 MIDIPortDispose(_input_ports[i]);
159 _input_queue[i].clear();
162 for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
163 MIDIPortDispose(_output_ports[i]);
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;
178 CoreMidiIo::start_cycle()
180 _time_at_cycle_start = AudioGetCurrentHostTime();
184 CoreMidiIo::notify_proc(const MIDINotification *message)
186 switch(message->messageID) {
187 case kMIDIMsgSetupChanged:
188 /* this one catches all of the added/removed/changed below */
189 //printf("kMIDIMsgSetupChanged\n");
192 case kMIDIMsgObjectAdded:
194 //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
195 //printf("kMIDIMsgObjectAdded\n");
198 case kMIDIMsgObjectRemoved:
200 //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
201 //printf("kMIDIMsgObjectRemoved\n");
204 case kMIDIMsgPropertyChanged:
206 //const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
207 //printf("kMIDIMsgObjectRemoved\n");
210 case kMIDIMsgThruConnectionsChanged:
211 //printf("kMIDIMsgThruConnectionsChanged\n");
213 case kMIDIMsgSerialPortOwnerChanged:
214 //printf("kMIDIMsgSerialPortOwnerChanged\n");
216 case kMIDIMsgIOError:
217 fprintf(stderr, "kMIDIMsgIOError\n");
224 CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
226 if (!_active || _time_at_cycle_start == 0) {
229 assert(port < _n_midi_in);
231 const size_t minsize = 1 + sizeof(uint32_t) + sizeof(MIDITimeStamp) + sizeof(UInt16);
233 while (_rb[port]->read_space() > minsize) {
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);
241 _input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
244 UInt64 start = _time_at_cycle_start;
245 UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
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,
253 printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
255 it = _input_queue[port].erase(it);
259 printf("Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
264 time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
266 s = std::min(s, (size_t) (*it)->length);
268 memcpy(d, (*it)->data, s);
270 _input_queue[port].erase(it);
280 CoreMidiIo::send_events (uint32_t port, double timescale, const void *b)
282 if (!_active || _time_at_cycle_start == 0) {
286 assert(port < _n_midi_out);
287 const UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
289 const CoreMidiBuffer *src = static_cast<CoreMidiBuffer const *>(b);
291 int32_t bytes[8192]; // int for alignment
292 MIDIPacketList *mpl = (MIDIPacketList*)bytes;
293 MIDIPacket *cur = MIDIPacketListInit(mpl);
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());
302 printf("CoreMidi: Packet list overflow, dropped events\n");
307 if (mpl->numPackets > 0) {
308 MIDISend(_output_ports[port], _output_endpoints[port], mpl);
314 CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
316 if (!_active || _time_at_cycle_start == 0) {
320 assert(port < _n_midi_out);
321 UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
322 ts += reltime_us * 1e3;
324 // TODO use a single packet list.. queue all events first..
328 MIDIPacket *mp = &(pl.packet[0]);
330 mp->timeStamp = AudioConvertNanosToHostTime(ts);
333 memcpy(mp->data, d, s);
335 MIDISend(_output_ports[port], _output_endpoints[port], &pl);
341 CoreMidiIo::port_id (uint32_t port, bool input)
343 std::stringstream ss;
345 ss << "system:midi_capture_";
347 if (noErr == MIDIObjectGetIntegerProperty(_input_endpoints[port], kMIDIPropertyUniqueID, &id)) {
348 ss << (unsigned int)id;
353 ss << "system:midi_playback_";
355 if (noErr == MIDIObjectGetIntegerProperty(_output_endpoints[port], kMIDIPropertyUniqueID, &id)) {
356 ss << (unsigned int)id;
365 CoreMidiIo::port_name (uint32_t port, bool input)
368 if (port < _n_midi_in) {
369 return getDisplayName(_input_endpoints[port]);
372 if (port < _n_midi_out) {
373 return getDisplayName(_output_endpoints[port]);
380 CoreMidiIo::start () {
384 err = MIDIClientCreate(CFSTR("Ardour"), ¬ifyProc, this, &_midi_client);
386 fprintf(stderr, "Creating Midi Client failed\n");
396 pthread_mutex_lock (&_discovery_lock);
398 pthread_mutex_unlock (&_discovery_lock);
401 MIDIClientDispose(_midi_client);
408 CoreMidiIo::discover()
410 if (!_run || !_midi_client) { return; }
412 if (pthread_mutex_trylock (&_discovery_lock)) {
418 ItemCount srcCount = MIDIGetNumberOfSources();
419 ItemCount dstCount = MIDIGetNumberOfDestinations();
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>*));
428 _output_ports = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
429 _output_endpoints = (MIDIEndpointRef*) malloc (dstCount * sizeof(MIDIEndpointRef));
432 for (ItemCount i = 0; i < srcCount; i++) {
434 MIDIEndpointRef src = MIDIGetSource(i);
437 printf("MIDI IN DEVICE: %s\n", getDisplayName(src).c_str());
440 CFStringRef port_name;
441 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
443 err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
445 fprintf(stderr, "Cannot create Midi Output\n");
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;
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);
462 err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
464 fprintf(stderr, "Cannot create Midi Output\n");
469 printf("MIDI OUT DEVICE: %s\n", getDisplayName(dst).c_str());
472 MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
473 CFRelease(port_name);
474 _output_endpoints[_n_midi_out] = dst;
478 if (_changed_callback) {
479 _changed_callback(_changed_arg);
483 pthread_mutex_unlock (&_discovery_lock);