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