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.
19 #include "coremidi_io.h"
20 #include <CoreAudio/HostTime.h>
22 static void notifyProc (const MIDINotification *message, void *refCon) {
23 CoreMidiIo *self = static_cast<CoreMidiIo*>(refCon);
24 self->notify_proc(message);
27 static void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
28 // TODO skip while freewheeling
29 RingBuffer<uint8_t> * rb = static_cast<RingBuffer < uint8_t > *> (srcRef);
31 for (UInt32 i = 0; i < list->numPackets; i++) {
32 const MIDIPacket *packet = &list->packet[i];
33 if (rb->write_space() < sizeof(MIDIPacket)) {
34 fprintf(stderr, "CoreMIDI: dropped MIDI event\n");
37 rb->write((uint8_t*)packet, sizeof(MIDIPacket));
42 CoreMidiIo::CoreMidiIo()
44 , _input_endpoints (0)
45 , _output_endpoints (0)
52 , _time_at_cycle_start (0)
54 , _changed_callback (0)
58 err = MIDIClientCreate(CFSTR("Ardour"), ¬ifyProc, this, &_midi_client);
60 fprintf(stderr, "Creating Midi Client failed\n");
65 CoreMidiIo::~CoreMidiIo()
68 MIDIClientDispose(_midi_client); _midi_client = 0;
75 for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
76 MIDIPortDispose(_input_ports[i]);
77 _input_queue[i].clear();
80 for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
81 MIDIPortDispose(_output_ports[i]);
84 free(_input_ports); _input_ports = 0;
85 free(_input_endpoints); _input_endpoints = 0;
86 free(_input_queue); _input_queue = 0;
87 free(_output_ports); _output_ports = 0;
88 free(_output_endpoints); _output_endpoints = 0;
96 CoreMidiIo::start_cycle()
98 _time_at_cycle_start = AudioGetCurrentHostTime();
102 CoreMidiIo::notify_proc(const MIDINotification *message)
104 switch(message->messageID) {
105 case kMIDIMsgSetupChanged:
106 /* this one catches all of the added/removed/changed below */
107 //printf("kMIDIMsgSetupChanged\n");
110 case kMIDIMsgObjectAdded:
112 //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
113 //printf("kMIDIMsgObjectAdded\n");
116 case kMIDIMsgObjectRemoved:
118 //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
119 //printf("kMIDIMsgObjectRemoved\n");
122 case kMIDIMsgPropertyChanged:
124 //const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
125 //printf("kMIDIMsgObjectRemoved\n");
128 case kMIDIMsgThruConnectionsChanged:
129 //printf("kMIDIMsgThruConnectionsChanged\n");
131 case kMIDIMsgSerialPortOwnerChanged:
132 //printf("kMIDIMsgSerialPortOwnerChanged\n");
134 case kMIDIMsgIOError:
135 fprintf(stderr, "kMIDIMsgIOError\n");
142 CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
144 if (!_active || _time_at_cycle_start == 0) {
147 assert(port < _n_midi_in);
149 while (_rb[port]->read_space() >= sizeof(MIDIPacket)) {
151 size_t rv = _rb[port]->read((uint8_t*)&packet, sizeof(MIDIPacket));
152 assert(rv == sizeof(MIDIPacket));
153 _input_queue[port].push_back(boost::shared_ptr<CoreMIDIPacket>(new _CoreMIDIPacket (&packet)));
156 UInt64 start = _time_at_cycle_start;
157 UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
159 for (CoreMIDIQueue::iterator it = _input_queue[port].begin (); it != _input_queue[port].end (); ) {
160 if ((*it)->timeStamp < end) {
161 if ((*it)->timeStamp < start) {
162 uint64_t dt = AudioConvertHostTimeToNanos(start - (*it)->timeStamp);
163 //printf("Stale Midi Event dt:%.2fms\n", dt * 1e-6);
164 if (dt > 1e-4) { // 100ms, maybe too large
165 it = _input_queue[port].erase(it);
170 time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
172 s = std::min(s, (size_t) (*it)->length);
174 memcpy(d, (*it)->data, s);
176 _input_queue[port].erase(it);
186 CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
188 if (!_active || _time_at_cycle_start == 0) {
192 assert(port < _n_midi_out);
193 UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
194 ts += reltime_us * 1e3;
196 // TODO use a single packet list.. queue all events first..
200 MIDIPacket *mp = &(pl.packet[0]);
202 mp->timeStamp = AudioConvertNanosToHostTime(ts);
205 memcpy(mp->data, d, s);
207 MIDISend(_output_ports[port], _output_endpoints[port], &pl);
212 CoreMidiIo::discover()
216 assert(!_active && _midi_client);
218 ItemCount srcCount = MIDIGetNumberOfSources();
219 ItemCount dstCount = MIDIGetNumberOfDestinations();
222 _input_ports = (MIDIPortRef *) malloc (srcCount * sizeof(MIDIPortRef));
223 _input_endpoints = (MIDIEndpointRef*) malloc (srcCount * sizeof(MIDIEndpointRef));
224 _input_queue = (CoreMIDIQueue*) calloc (srcCount, sizeof(CoreMIDIQueue));
225 _rb = (RingBuffer<uint8_t> **) malloc (srcCount * sizeof(RingBuffer<uint8_t>*));
228 _output_ports = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
229 _output_endpoints = (MIDIEndpointRef*) malloc (dstCount * sizeof(MIDIEndpointRef));
232 for (ItemCount i = 0; i < srcCount; i++) {
234 MIDIEndpointRef src = MIDIGetSource(i);
235 CFStringRef port_name;
236 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
238 err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
240 fprintf(stderr, "Cannot create Midi Output\n");
241 // TODO handle errors
244 // TODO get device name/ID
245 _rb[_n_midi_in] = new RingBuffer<uint8_t>(1024 * sizeof(MIDIPacket));
246 _input_queue[_n_midi_in] = CoreMIDIQueue();
247 MIDIPortConnectSource(_input_ports[_n_midi_in], src, (void*) _rb[_n_midi_in]);
248 CFRelease(port_name);
249 _input_endpoints[_n_midi_in] = src;
253 for (ItemCount i = 0; i < dstCount; i++) {
254 MIDIEndpointRef dst = MIDIGetDestination(i);
255 CFStringRef port_name;
256 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_playback_%lu"), i);
259 err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
261 fprintf(stderr, "Cannot create Midi Output\n");
262 // TODO handle errors
265 // TODO get device name/ID
266 MIDIPortConnectSource(_output_ports[_n_midi_out], dst, NULL);
267 CFRelease(port_name);
268 _output_endpoints[_n_midi_out] = dst;
272 if (_changed_callback) {
273 _changed_callback(_changed_arg);