get started on coreaudio/midi backend
[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         : _midiClient (0)
44         , _inputEndPoints (0)
45         , _outputEndPoints (0)
46         , _inputPorts (0)
47         , _outputPorts (0)
48         , _inputQueue (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, &_midiClient);
59         if (noErr != err) {
60                 fprintf(stderr, "Creating Midi Client failed\n");
61         }
62
63 }
64
65 CoreMidiIo::~CoreMidiIo() 
66 {
67         cleanup();
68         MIDIClientDispose(_midiClient); _midiClient = 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(_inputPorts[i]);
77                 _inputQueue[i].clear();
78                 delete _rb[i];
79         }
80         for (uint32_t i = 0 ; i < _n_midi_out ; ++i) {
81                 MIDIPortDispose(_outputPorts[i]);
82         }
83
84         free(_inputPorts); _inputPorts = 0;
85         free(_inputEndPoints); _inputEndPoints = 0;
86         free(_inputQueue); _inputQueue = 0;
87         free(_outputPorts); _outputPorts = 0;
88         free(_outputEndPoints); _outputEndPoints = 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                         printf("kMIDIMsgSetupChanged\n");
107                         discover();
108                         break;
109                 case kMIDIMsgObjectAdded:
110                         {
111                         const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
112                         printf("kMIDIMsgObjectAdded\n");
113                         }
114                         break;
115                 case kMIDIMsgObjectRemoved:
116                         {
117                         const MIDIObjectAddRemoveNotification *n = (const MIDIObjectAddRemoveNotification*) message;
118                         printf("kMIDIMsgObjectRemoved\n");
119                         }
120                         break;
121                 case kMIDIMsgPropertyChanged:
122                         {
123                         const MIDIObjectPropertyChangeNotification *n = (const MIDIObjectPropertyChangeNotification*) message;
124                         printf("kMIDIMsgObjectRemoved\n");
125                         }
126                         break;
127                 case kMIDIMsgThruConnectionsChanged:
128                         printf("kMIDIMsgThruConnectionsChanged\n");
129                         break;
130                 case kMIDIMsgSerialPortOwnerChanged:
131                         printf("kMIDIMsgSerialPortOwnerChanged\n");
132                         break;
133                 case kMIDIMsgIOError:
134                         printf("kMIDIMsgIOError\n");
135                         cleanup();
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                 _inputQueue[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 = _inputQueue[port].begin (); it != _inputQueue[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 = _inputQueue[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                         _inputQueue[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(_outputPorts[port], _outputEndPoints[port], &pl);
208         return 0;
209 }
210
211 void
212 CoreMidiIo::discover() 
213 {
214         cleanup();
215
216         assert(!_active && _midiClient);
217
218         ItemCount srcCount = MIDIGetNumberOfSources();
219         ItemCount dstCount = MIDIGetNumberOfDestinations();
220
221         if (srcCount > 0) {
222                 _inputPorts = (MIDIPortRef *) malloc (srcCount * sizeof(MIDIPortRef));
223                 _inputEndPoints = (MIDIEndpointRef*) malloc (srcCount * sizeof(MIDIEndpointRef));
224                 _inputQueue = (CoreMIDIQueue*) calloc (srcCount, sizeof(CoreMIDIQueue));
225                 _rb = (RingBuffer<uint8_t> **) malloc (srcCount * sizeof(RingBuffer<uint8_t>*));
226         }
227         if (dstCount > 0) {
228                 _outputPorts = (MIDIPortRef *) malloc (dstCount * sizeof(MIDIPortRef));
229                 _outputEndPoints = (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 (_midiClient, port_name, midiInputCallback, this, &_inputPorts[_n_midi_in]);
239                 if (noErr != err) {
240                         fprintf(stderr, "Cannot create Midi Output\n");
241                         // TODO  handle errors
242                         continue;
243                 }
244                 _rb[_n_midi_in] = new RingBuffer<uint8_t>(1024 * sizeof(MIDIPacket));
245                 _inputQueue[_n_midi_in] = CoreMIDIQueue();
246                 MIDIPortConnectSource(_inputPorts[_n_midi_in], src, (void*) _rb[_n_midi_in]);
247                 CFRelease(port_name);
248                 _inputEndPoints[_n_midi_in] = src;
249                 ++_n_midi_in;
250         }
251
252         for (ItemCount i = 0; i < dstCount; i++) {
253                 MIDIEndpointRef dst = MIDIGetDestination(i);
254                 CFStringRef port_name;
255                 port_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("midi_playback_%lu"), i);
256
257                 OSStatus err;
258                 err = MIDIOutputPortCreate (_midiClient, port_name, &_outputPorts[_n_midi_out]);
259                 if (noErr != err) {
260                         fprintf(stderr, "Cannot create Midi Output\n");
261                         // TODO  handle errors
262                         continue;
263                 }
264                 MIDIPortConnectSource(_outputPorts[_n_midi_out], dst, NULL);
265                 CFRelease(port_name);
266                 _outputEndPoints[_n_midi_out] = dst;
267                 ++_n_midi_out;
268         }
269
270         if (_changed_callback) {
271                 _changed_callback(_changed_arg);
272         }
273
274         _active = true;
275 }