750fd76e55bf5ef84b85d91038229ab75033fe44
[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 "coremidi_io.h"
20 #include <CoreAudio/HostTime.h>
21
22 static void notifyProc (const MIDINotification *message, void *refCon) {
23         CoreMidiIo *self = static_cast<CoreMidiIo*>(refCon);
24         self->notify_proc(message);
25 }
26
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);
30         if (!rb) return;
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");
35                         continue;
36                 }
37                 rb->write((uint8_t*)packet, sizeof(MIDIPacket));
38         }
39 }
40
41
42 CoreMidiIo::CoreMidiIo() 
43         : _midi_client (0)
44         , _input_endpoints (0)
45         , _output_endpoints (0)
46         , _input_ports (0)
47         , _output_ports (0)
48         , _input_queue (0)
49         , _rb (0)
50         , _n_midi_in (0)
51         , _n_midi_out (0)
52         , _time_at_cycle_start (0)
53         , _active (false)
54         , _changed_callback (0)
55         , _changed_arg (0)
56 {
57         OSStatus err;
58         err = MIDIClientCreate(CFSTR("Ardour"), &notifyProc, this, &_midi_client);
59         if (noErr != err) {
60                 fprintf(stderr, "Creating Midi Client failed\n");
61         }
62
63 }
64
65 CoreMidiIo::~CoreMidiIo() 
66 {
67         cleanup();
68         MIDIClientDispose(_midi_client); _midi_client = 0;
69 }
70
71 void
72 CoreMidiIo::cleanup() 
73 {
74         _active = false;
75         for (uint32_t i = 0 ; i < _n_midi_in ; ++i) {
76                 MIDIPortDispose(_input_ports[i]);
77                 _input_queue[i].clear();
78                 delete _rb[i];
79         }
80         for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
81                 MIDIPortDispose(_output_ports[i]);
82         }
83
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;
89         free(_rb); _rb = 0;
90
91         _n_midi_in = 0;
92         _n_midi_out = 0;
93 }
94
95 void
96 CoreMidiIo::start_cycle() 
97 {
98         _time_at_cycle_start = AudioGetCurrentHostTime();
99 }
100
101 void
102 CoreMidiIo::notify_proc(const MIDINotification *message) 
103 {
104         switch(message->messageID) {
105                 case kMIDIMsgSetupChanged:
106                         /* this one catches all of the added/removed/changed below */
107                         //printf("kMIDIMsgSetupChanged\n");
108                         discover();
109                         break;
110                 case kMIDIMsgObjectAdded:
111                         {
112                         //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
113                         //printf("kMIDIMsgObjectAdded\n");
114                         }
115                         break;
116                 case kMIDIMsgObjectRemoved:
117                         {
118                         //const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
119                         //printf("kMIDIMsgObjectRemoved\n");
120                         }
121                         break;
122                 case kMIDIMsgPropertyChanged:
123                         {
124                         //const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
125                         //printf("kMIDIMsgObjectRemoved\n");
126                         }
127                         break;
128                 case kMIDIMsgThruConnectionsChanged:
129                         //printf("kMIDIMsgThruConnectionsChanged\n");
130                         break;
131                 case kMIDIMsgSerialPortOwnerChanged:
132                         //printf("kMIDIMsgSerialPortOwnerChanged\n");
133                         break;
134                 case kMIDIMsgIOError:
135                         fprintf(stderr, "kMIDIMsgIOError\n");
136                         discover();
137                         break;
138         }
139 }
140
141 size_t
142 CoreMidiIo::recv_event (uint32_t port, double cycle_time_us, uint64_t &time, uint8_t *d, size_t &s)
143 {
144         if (!_active || _time_at_cycle_start == 0) {
145                 return 0;
146         }
147         assert(port < _n_midi_in);
148
149         while (_rb[port]->read_space() >= sizeof(MIDIPacket)) {
150                 MIDIPacket packet;
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))); 
154         }
155
156         UInt64 start = _time_at_cycle_start;
157         UInt64 end = AudioConvertNanosToHostTime(AudioConvertHostTimeToNanos(_time_at_cycle_start) + cycle_time_us * 1e3);
158
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);
166                                         continue;
167                                 }
168                                 time = 0;
169                         } else {
170                                 time = AudioConvertHostTimeToNanos((*it)->timeStamp - start);
171                         }
172                         s = std::min(s, (size_t) (*it)->length);
173                         if (s > 0) {
174                                 memcpy(d, (*it)->data, s);
175                         }
176                         _input_queue[port].erase(it);
177                         return s;
178                 }
179                 ++it;
180
181         }
182         return 0;
183 }
184
185 int
186 CoreMidiIo::send_event (uint32_t port, double reltime_us, const uint8_t *d, const size_t s)
187 {
188         if (!_active || _time_at_cycle_start == 0) {
189                 return 0;
190         }
191
192         assert(port < _n_midi_out);
193         UInt64 ts = AudioConvertHostTimeToNanos(_time_at_cycle_start);
194         ts += reltime_us * 1e3;
195
196         // TODO use a single packet list.. queue all events first..
197         MIDIPacketList pl;
198
199         pl.numPackets = 1;
200         MIDIPacket *mp = &(pl.packet[0]);
201
202         mp->timeStamp = AudioConvertNanosToHostTime(ts);
203         mp->length = s;
204         assert(s < 256);
205         memcpy(mp->data, d, s);
206
207         MIDISend(_output_ports[port], _output_endpoints[port], &pl);
208         return 0;
209 }
210
211 void
212 CoreMidiIo::discover() 
213 {
214         cleanup();
215
216         assert(!_active && _midi_client);
217
218         ItemCount srcCount = MIDIGetNumberOfSources();
219         ItemCount dstCount = MIDIGetNumberOfDestinations();
220
221         if (srcCount > 0) {
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>*));
226         }
227         if (dstCount > 0) {
228                 _output_ports = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
229                 _output_endpoints = (MIDIEndpointRef*) malloc (dstCount * sizeof(MIDIEndpointRef));
230         }
231
232         for (ItemCount i = 0; i < srcCount; i++) {
233                 OSStatus err;
234                 MIDIEndpointRef src = MIDIGetSource(i);
235                 CFStringRef port_name;
236                 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_capture_%lu"), i);
237
238                 err = MIDIInputPortCreate (_midi_client, port_name, midiInputCallback, this, &_input_ports[_n_midi_in]);
239                 if (noErr != err) {
240                         fprintf(stderr, "Cannot create Midi Output\n");
241                         // TODO  handle errors
242                         continue;
243                 }
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;
250                 ++_n_midi_in;
251         }
252
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);
257
258                 OSStatus err;
259                 err = MIDIOutputPortCreate (_midi_client, port_name, &_output_ports[_n_midi_out]);
260                 if (noErr != err) {
261                         fprintf(stderr, "Cannot create Midi Output\n");
262                         // TODO  handle errors
263                         continue;
264                 }
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;
269                 ++_n_midi_out;
270         }
271
272         if (_changed_callback) {
273                 _changed_callback(_changed_arg);
274         }
275
276         _active = true;
277 }