1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
|
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/midi/midi_manager_mac.h"
#include <algorithm>
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include <CoreAudio/HostTime.h>
using base::IntToString;
using base::SysCFStringRefToUTF8;
using std::string;
// NB: System MIDI types are pointer types in 32-bit and integer types in
// 64-bit. Therefore, the initialization is the simplest one that satisfies both
// (if possible).
namespace media {
namespace midi {
namespace {
// Maximum buffer size that CoreMIDI can handle for MIDIPacketList.
const size_t kCoreMIDIMaxPacketListSize = 65536;
// Pessimistic estimation on available data size of MIDIPacketList.
const size_t kEstimatedMaxPacketDataSize = kCoreMIDIMaxPacketListSize / 2;
MidiPortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint) {
string manufacturer;
CFStringRef manufacturer_ref = NULL;
OSStatus result = MIDIObjectGetStringProperty(
endpoint, kMIDIPropertyManufacturer, &manufacturer_ref);
if (result == noErr) {
manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
} else {
// kMIDIPropertyManufacturer is not supported in IAC driver providing
// endpoints, and the result will be kMIDIUnknownProperty (-10835).
DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status "
<< result;
}
string name;
CFStringRef name_ref = NULL;
result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName,
&name_ref);
if (result == noErr) {
name = SysCFStringRefToUTF8(name_ref);
} else {
DLOG(WARNING) << "Failed to get kMIDIPropertyDisplayName with status "
<< result;
}
string version;
SInt32 version_number = 0;
result = MIDIObjectGetIntegerProperty(
endpoint, kMIDIPropertyDriverVersion, &version_number);
if (result == noErr) {
version = IntToString(version_number);
} else {
// kMIDIPropertyDriverVersion is not supported in IAC driver providing
// endpoints, and the result will be kMIDIUnknownProperty (-10835).
DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status "
<< result;
}
string id;
SInt32 id_number = 0;
result = MIDIObjectGetIntegerProperty(
endpoint, kMIDIPropertyUniqueID, &id_number);
if (result == noErr) {
id = IntToString(id_number);
} else {
// On connecting some devices, e.g., nano KONTROL2, unknown endpoints
// appear and disappear quickly and they fail on queries.
// Let's ignore such ghost devices.
// Same problems will happen if the device is disconnected before finishing
// all queries.
DLOG(WARNING) << "Failed to get kMIDIPropertyUniqueID with status "
<< result;
}
const MidiPortState state = MIDI_PORT_OPENED;
return MidiPortInfo(id, manufacturer, name, version, state);
}
double MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
return static_cast<double>(nanoseconds) / 1.0e9;
}
MIDITimeStamp SecondsToMIDITimeStamp(double seconds) {
UInt64 nanos = UInt64(seconds * 1.0e9);
return AudioConvertNanosToHostTime(nanos);
}
} // namespace
MidiManager* MidiManager::Create() {
return new MidiManagerMac();
}
MidiManagerMac::MidiManagerMac()
: midi_client_(0),
coremidi_input_(0),
coremidi_output_(0),
client_thread_("MidiClientThread"),
shutdown_(false) {
}
MidiManagerMac::~MidiManagerMac() {
// Wait for the termination of |client_thread_| before disposing MIDI ports.
shutdown_ = true;
client_thread_.Stop();
if (coremidi_input_)
MIDIPortDispose(coremidi_input_);
if (coremidi_output_)
MIDIPortDispose(coremidi_output_);
if (midi_client_)
MIDIClientDispose(midi_client_);
}
void MidiManagerMac::StartInitialization() {
// MIDIClient should be created on |client_thread_| to receive CoreMIDI event
// notifications.
RunOnClientThread(
base::Bind(&MidiManagerMac::InitializeCoreMIDI, base::Unretained(this)));
}
void MidiManagerMac::DispatchSendMidiData(MidiManagerClient* client,
uint32 port_index,
const std::vector<uint8>& data,
double timestamp) {
RunOnClientThread(
base::Bind(&MidiManagerMac::SendMidiData,
base::Unretained(this), client, port_index, data, timestamp));
}
void MidiManagerMac::RunOnClientThread(const base::Closure& closure) {
if (shutdown_)
return;
if (!client_thread_.IsRunning())
client_thread_.Start();
client_thread_.message_loop()->PostTask(FROM_HERE, closure);
}
void MidiManagerMac::InitializeCoreMIDI() {
DCHECK(client_thread_.task_runner()->BelongsToCurrentThread());
// CoreMIDI registration.
DCHECK_EQ(0u, midi_client_);
OSStatus result =
MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch, this,
&midi_client_);
if (result != noErr || midi_client_ == 0)
return CompleteInitialization(Result::INITIALIZATION_ERROR);
// Create input and output port.
DCHECK_EQ(0u, coremidi_input_);
result = MIDIInputPortCreate(
midi_client_,
CFSTR("MIDI Input"),
ReadMidiDispatch,
this,
&coremidi_input_);
if (result != noErr || coremidi_input_ == 0)
return CompleteInitialization(Result::INITIALIZATION_ERROR);
DCHECK_EQ(0u, coremidi_output_);
result = MIDIOutputPortCreate(
midi_client_,
CFSTR("MIDI Output"),
&coremidi_output_);
if (result != noErr || coremidi_output_ == 0)
return CompleteInitialization(Result::INITIALIZATION_ERROR);
// Following loop may miss some newly attached devices, but such device will
// be captured by ReceiveMidiNotifyDispatch callback.
uint32 destination_count = MIDIGetNumberOfDestinations();
destinations_.resize(destination_count);
for (uint32 i = 0; i < destination_count ; i++) {
MIDIEndpointRef destination = MIDIGetDestination(i);
if (destination == 0) {
// One ore more devices may be detached.
destinations_.resize(i);
break;
}
// Keep track of all destinations (known as outputs by the Web MIDI API).
// Cache to avoid any possible overhead in calling MIDIGetDestination().
destinations_[i] = destination;
MidiPortInfo info = GetPortInfoFromEndpoint(destination);
AddOutputPort(info);
}
// Open connections from all sources. This loop also may miss new devices.
uint32 source_count = MIDIGetNumberOfSources();
for (uint32 i = 0; i < source_count; ++i) {
// Receive from all sources.
MIDIEndpointRef source = MIDIGetSource(i);
if (source == 0)
break;
// Start listening.
MIDIPortConnectSource(
coremidi_input_, source, reinterpret_cast<void*>(source));
// Keep track of all sources (known as inputs in Web MIDI API terminology).
source_map_[source] = i;
MidiPortInfo info = GetPortInfoFromEndpoint(source);
AddInputPort(info);
}
// Allocate maximum size of buffer that CoreMIDI can handle.
midi_buffer_.resize(kCoreMIDIMaxPacketListSize);
CompleteInitialization(Result::OK);
}
// static
void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification* message,
void* refcon) {
// This callback function is invoked on |client_thread_|.
MidiManagerMac* manager = static_cast<MidiManagerMac*>(refcon);
manager->ReceiveMidiNotify(message);
}
void MidiManagerMac::ReceiveMidiNotify(const MIDINotification* message) {
DCHECK(client_thread_.task_runner()->BelongsToCurrentThread());
if (kMIDIMsgObjectAdded == message->messageID) {
// New device is going to be attached.
const MIDIObjectAddRemoveNotification* notification =
reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
MIDIEndpointRef endpoint =
static_cast<MIDIEndpointRef>(notification->child);
if (notification->childType == kMIDIObjectType_Source) {
// Attaching device is an input device.
auto it = source_map_.find(endpoint);
if (it == source_map_.end()) {
MidiPortInfo info = GetPortInfoFromEndpoint(endpoint);
// If the device disappears before finishing queries, MidiPortInfo
// becomes incomplete. Skip and do not cache such information here.
// On kMIDIMsgObjectRemoved, the entry will be ignored because it
// will not be found in the pool.
if (!info.id.empty()) {
uint32 index = source_map_.size();
source_map_[endpoint] = index;
AddInputPort(info);
MIDIPortConnectSource(
coremidi_input_, endpoint, reinterpret_cast<void*>(endpoint));
}
} else {
SetInputPortState(it->second, MIDI_PORT_OPENED);
}
} else if (notification->childType == kMIDIObjectType_Destination) {
// Attaching device is an output device.
auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
if (it == destinations_.end()) {
MidiPortInfo info = GetPortInfoFromEndpoint(endpoint);
// Skip cases that queries are not finished correctly.
if (!info.id.empty()) {
destinations_.push_back(endpoint);
AddOutputPort(info);
}
} else {
SetOutputPortState(it - destinations_.begin(), MIDI_PORT_OPENED);
}
}
} else if (kMIDIMsgObjectRemoved == message->messageID) {
// Existing device is going to be detached.
const MIDIObjectAddRemoveNotification* notification =
reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
MIDIEndpointRef endpoint =
static_cast<MIDIEndpointRef>(notification->child);
if (notification->childType == kMIDIObjectType_Source) {
// Detaching device is an input device.
auto it = source_map_.find(endpoint);
if (it != source_map_.end())
SetInputPortState(it->second, MIDI_PORT_DISCONNECTED);
} else if (notification->childType == kMIDIObjectType_Destination) {
// Detaching device is an output device.
auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
if (it != destinations_.end())
SetOutputPortState(it - destinations_.begin(), MIDI_PORT_DISCONNECTED);
}
}
}
// static
void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list,
void* read_proc_refcon,
void* src_conn_refcon) {
// This method is called on a separate high-priority thread owned by CoreMIDI.
MidiManagerMac* manager = static_cast<MidiManagerMac*>(read_proc_refcon);
#if __LP64__
MIDIEndpointRef source = reinterpret_cast<uintptr_t>(src_conn_refcon);
#else
MIDIEndpointRef source = static_cast<MIDIEndpointRef>(src_conn_refcon);
#endif
// Dispatch to class method.
manager->ReadMidi(source, packet_list);
}
void MidiManagerMac::ReadMidi(MIDIEndpointRef source,
const MIDIPacketList* packet_list) {
// This method is called from ReadMidiDispatch() and runs on a separate
// high-priority thread owned by CoreMIDI.
// Lookup the port index based on the source.
auto it = source_map_.find(source);
if (it == source_map_.end())
return;
// This is safe since MidiManagerMac does not remove any existing
// MIDIEndpointRef, and the order is saved.
uint32 port_index = it->second;
// Go through each packet and process separately.
const MIDIPacket* packet = &packet_list->packet[0];
for (size_t i = 0; i < packet_list->numPackets; i++) {
// Each packet contains MIDI data for one or more messages (like note-on).
double timestamp_seconds = MIDITimeStampToSeconds(packet->timeStamp);
ReceiveMidiData(
port_index,
packet->data,
packet->length,
timestamp_seconds);
packet = MIDIPacketNext(packet);
}
}
void MidiManagerMac::SendMidiData(MidiManagerClient* client,
uint32 port_index,
const std::vector<uint8>& data,
double timestamp) {
DCHECK(client_thread_.task_runner()->BelongsToCurrentThread());
// Lookup the destination based on the port index.
if (static_cast<size_t>(port_index) >= destinations_.size())
return;
MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp);
MIDIEndpointRef destination = destinations_[port_index];
size_t send_size;
for (size_t sent_size = 0; sent_size < data.size(); sent_size += send_size) {
MIDIPacketList* packet_list =
reinterpret_cast<MIDIPacketList*>(midi_buffer_.data());
MIDIPacket* midi_packet = MIDIPacketListInit(packet_list);
// Limit the maximum payload size to kEstimatedMaxPacketDataSize that is
// half of midi_buffer data size. MIDIPacketList and MIDIPacket consume
// extra buffer areas for meta information, and available size is smaller
// than buffer size. Here, we simply assume that at least half size is
// available for data payload.
send_size = std::min(data.size() - sent_size, kEstimatedMaxPacketDataSize);
midi_packet = MIDIPacketListAdd(
packet_list,
kCoreMIDIMaxPacketListSize,
midi_packet,
coremidi_timestamp,
send_size,
&data[sent_size]);
DCHECK(midi_packet);
MIDISend(coremidi_output_, destination, packet_list);
}
client->AccumulateMidiBytesSent(data.size());
}
} // namespace midi
} // namespace media
|