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;
35 * (A) INPUT (incoming from outside the application)
37 * - midiInputCallback (in its own thread, async WRT process callback):
38 * takes OS X MIDIPacket, copies into lock-free ringbuffer
40 * - processCallback (in its own thread):
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>
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
51 static void notifyProc (const MIDINotification *message, void *refCon) {
52 CoreMidiIo *self = static_cast<CoreMidiIo*>(refCon);
53 self->notify_proc(message);
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]);
62 fprintf (stderr, "]\n");
65 static void dump_packet_list (const UInt32 numPackets, MIDIPacket const *p) {
66 for (UInt32 i = 0; i < numPackets; ++i) {
68 p = MIDIPacketNext (p);
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
78 if (_debug_mode & 2) {
79 fprintf (stderr, "Ignored Midi Packet while freewheeling:\n");
80 dump_packet_list (list->numPackets, &list->packet[0]);
85 RingBuffer<uint8_t> * rb = static_cast<RingBuffer < uint8_t > *> (srcRef);
88 if (_debug_mode & 4) {
89 fprintf (stderr, "Ignored Midi Packet - no ringbuffer:\n");
90 dump_packet_list (list->numPackets, &list->packet[0]);
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);
99 if (_debug_mode & 1) {
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);
109 fprintf (stderr, "CoreMIDI: dropped MIDI event\n");
112 p = MIDIPacketNext (p);
116 static std::string getPropertyString (MIDIObjectRef object, CFStringRef key)
118 CFStringRef name = NULL;
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)) {
132 static std::string getDisplayName (MIDIObjectRef object) {
133 return getPropertyString(object, kMIDIPropertyDisplayName);
136 CoreMidiIo::CoreMidiIo()
138 , _input_endpoints (0)
139 , _output_endpoints (0)
146 , _time_at_cycle_start (0)
150 , _changed_callback (0)
153 pthread_mutex_init (&_discovery_lock, 0);
156 const char *p = getenv ("COREMIDIDEBUG");
157 if (p && *p) _debug_mode = atoi (p);
161 CoreMidiIo::~CoreMidiIo()
163 pthread_mutex_lock (&_discovery_lock);
166 MIDIClientDispose(_midi_client);
169 pthread_mutex_unlock (&_discovery_lock);
170 pthread_mutex_destroy (&_discovery_lock);
174 CoreMidiIo::cleanup()
177 for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
178 MIDIPortDispose(_input_ports[i]);
179 _input_queue[i].clear();
182 for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
183 MIDIPortDispose(_output_ports[i]);
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;
198 CoreMidiIo::start_cycle()
200 _time_at_cycle_start = AudioGetCurrentHostTime();
204 CoreMidiIo::notify_proc(const MIDINotification *message)
206 switch(message->messageID) {
207 case kMIDIMsgSetupChanged:
208 /* this one catches all of the added/removed/changed below */
209 //printf("kMIDIMsgSetupChanged\n");
212 case kMIDIMsgObjectAdded:
214 //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
215 //printf("kMIDIMsgObjectAdded\n");
218 case kMIDIMsgObjectRemoved:
220 //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
221 //printf("kMIDIMsgObjectRemoved\n");
224 case kMIDIMsgPropertyChanged:
226 //const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
227 //printf("kMIDIMsgObjectRemoved\n");
230 case kMIDIMsgThruConnectionsChanged:
231 //printf("kMIDIMsgThruConnectionsChanged\n");
233 case kMIDIMsgSerialPortOwnerChanged:
234 //printf("kMIDIMsgSerialPortOwnerChanged\n");
236 case kMIDIMsgIOError:
237 fprintf(stderr, "kMIDIMsgIOError\n");
244 CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
246 if (!_active || _time_at_cycle_start == 0) {
249 assert(port < _n_midi_in);
251 const size_t minsize = 1 + sizeof(uint32_t) + sizeof(MIDITimeStamp) + sizeof(UInt16);
253 while (_rb[port]->read_space() > minsize) {
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);
261 _input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
264 UInt64 start = _time_at_cycle_start;
265 UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
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,
273 printf("Dropped Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
275 it = _input_queue[port].erase(it);
279 printf("Stale Midi Event. dt:%.2fms\n", dt * 1e-6);
284 time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
286 s = std::min(s, (size_t) (*it)->length);
288 memcpy(d, (*it)->data, s);
290 _input_queue[port].erase(it);
300 CoreMidiIo::send_events (uint32_t port, double timescale, const void *b)
302 if (!_active || _time_at_cycle_start == 0) {
306 assert(port < _n_midi_out);
307 const UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
309 const CoreMidiBuffer *src = static_cast<CoreMidiBuffer const *>(b);
311 int32_t bytes[8192]; // int for alignment
312 MIDIPacketList *mpl = (MIDIPacketList*)bytes;
313 MIDIPacket *cur = MIDIPacketListInit(mpl);
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());
322 printf("CoreMidi: Packet list overflow, dropped events\n");
327 if (mpl->numPackets > 0) {
328 MIDISend(_output_ports[port], _output_endpoints[port], mpl);
334 CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
336 if (!_active || _time_at_cycle_start == 0) {
340 assert(port < _n_midi_out);
341 UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
342 ts += reltime_us * 1e3;
344 // TODO use a single packet list.. queue all events first..
348 MIDIPacket *mp = &(pl.packet[0]);
350 mp->timeStamp = AudioConvertNanosToHostTime(ts);
353 memcpy(mp->data, d, s);
355 MIDISend(_output_ports[port], _output_endpoints[port], &pl);
361 CoreMidiIo::port_id (uint32_t port, bool input)
363 std::stringstream ss;
365 ss << "system:midi_capture_";
367 if (noErr == MIDIObjectGetIntegerProperty(_input_endpoints[port], kMIDIPropertyUniqueID, &id)) {
368 ss << (unsigned int)id;
373 ss << "system:midi_playback_";
375 if (noErr == MIDIObjectGetIntegerProperty(_output_endpoints[port], kMIDIPropertyUniqueID, &id)) {
376 ss << (unsigned int)id;
385 CoreMidiIo::port_name (uint32_t port, bool input)
388 if (port < _n_midi_in) {
389 return getDisplayName(_input_endpoints[port]);
392 if (port < _n_midi_out) {
393 return getDisplayName(_output_endpoints[port]);
400 CoreMidiIo::start () {
404 err = MIDIClientCreate(CFSTR("Ardour"), ¬ifyProc, this, &_midi_client);
406 fprintf(stderr, "Creating Midi Client failed\n");
416 pthread_mutex_lock (&_discovery_lock);
418 pthread_mutex_unlock (&_discovery_lock);
421 MIDIClientDispose(_midi_client);
428 CoreMidiIo::discover()
430 if (!_run || !_midi_client) { return; }
432 if (pthread_mutex_trylock (&_discovery_lock)) {
438 ItemCount srcCount = MIDIGetNumberOfSources();
439 ItemCount dstCount = MIDIGetNumberOfDestinations();
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>*));
448 _output_ports = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
449 _output_endpoints = (MIDIEndpointRef*) malloc (dstCount * sizeof(MIDIEndpointRef));
452 for (ItemCount i = 0; i < srcCount; i++) {
454 MIDIEndpointRef src = MIDIGetSource(i);
457 printf("MIDI IN DEVICE: %s\n", getDisplayName(src).c_str());
460 CFStringRef port_name;
461 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
463 err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
465 fprintf(stderr, "Cannot create Midi Output\n");
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;
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);
482 err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
484 fprintf(stderr, "Cannot create Midi Output\n");
489 printf("MIDI OUT DEVICE: %s\n", getDisplayName(dst).c_str());
492 MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
493 CFRelease(port_name);
494 _output_endpoints[_n_midi_out] = dst;
498 if (_changed_callback) {
499 _changed_callback(_changed_arg);
503 pthread_mutex_unlock (&_discovery_lock);