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"
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/sys_string_conversions.h"
15 #include <CoreAudio/HostTime.h>
17 using base::IntToString
;
18 using base::SysCFStringRefToUTF8
;
21 // NB: System MIDI types are pointer types in 32-bit and integer types in
22 // 64-bit. Therefore, the initialization is the simplest one that satisfies both
30 // Maximum buffer size that CoreMIDI can handle for MIDIPacketList.
31 const size_t kCoreMIDIMaxPacketListSize
= 65536;
32 // Pessimistic estimation on available data size of MIDIPacketList.
33 const size_t kEstimatedMaxPacketDataSize
= kCoreMIDIMaxPacketListSize
/ 2;
35 MidiPortInfo
GetPortInfoFromEndpoint(MIDIEndpointRef endpoint
) {
37 CFStringRef manufacturer_ref
= NULL
;
38 OSStatus result
= MIDIObjectGetStringProperty(
39 endpoint
, kMIDIPropertyManufacturer
, &manufacturer_ref
);
40 if (result
== noErr
) {
41 manufacturer
= SysCFStringRefToUTF8(manufacturer_ref
);
43 // kMIDIPropertyManufacturer is not supported in IAC driver providing
44 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
45 DLOG(WARNING
) << "Failed to get kMIDIPropertyManufacturer with status "
50 CFStringRef name_ref
= NULL
;
51 result
= MIDIObjectGetStringProperty(endpoint
, kMIDIPropertyDisplayName
,
53 if (result
== noErr
) {
54 name
= SysCFStringRefToUTF8(name_ref
);
56 DLOG(WARNING
) << "Failed to get kMIDIPropertyDisplayName with status "
61 SInt32 version_number
= 0;
62 result
= MIDIObjectGetIntegerProperty(
63 endpoint
, kMIDIPropertyDriverVersion
, &version_number
);
64 if (result
== noErr
) {
65 version
= IntToString(version_number
);
67 // kMIDIPropertyDriverVersion is not supported in IAC driver providing
68 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
69 DLOG(WARNING
) << "Failed to get kMIDIPropertyDriverVersion with status "
75 result
= MIDIObjectGetIntegerProperty(
76 endpoint
, kMIDIPropertyUniqueID
, &id_number
);
77 if (result
== noErr
) {
78 id
= IntToString(id_number
);
80 // On connecting some devices, e.g., nano KONTROL2, unknown endpoints
81 // appear and disappear quickly and they fail on queries.
82 // Let's ignore such ghost devices.
83 // Same problems will happen if the device is disconnected before finishing
85 DLOG(WARNING
) << "Failed to get kMIDIPropertyUniqueID with status "
89 const MidiPortState state
= MIDI_PORT_OPENED
;
90 return MidiPortInfo(id
, manufacturer
, name
, version
, state
);
93 double MIDITimeStampToSeconds(MIDITimeStamp timestamp
) {
94 UInt64 nanoseconds
= AudioConvertHostTimeToNanos(timestamp
);
95 return static_cast<double>(nanoseconds
) / 1.0e9
;
98 MIDITimeStamp
SecondsToMIDITimeStamp(double seconds
) {
99 UInt64 nanos
= UInt64(seconds
* 1.0e9
);
100 return AudioConvertNanosToHostTime(nanos
);
105 MidiManager
* MidiManager::Create() {
106 return new MidiManagerMac();
109 MidiManagerMac::MidiManagerMac()
113 client_thread_("MidiClientThread"),
117 MidiManagerMac::~MidiManagerMac() {
118 // Wait for the termination of |client_thread_| before disposing MIDI ports.
120 client_thread_
.Stop();
123 MIDIPortDispose(coremidi_input_
);
124 if (coremidi_output_
)
125 MIDIPortDispose(coremidi_output_
);
127 MIDIClientDispose(midi_client_
);
130 void MidiManagerMac::StartInitialization() {
131 // MIDIClient should be created on |client_thread_| to receive CoreMIDI event
134 base::Bind(&MidiManagerMac::InitializeCoreMIDI
, base::Unretained(this)));
137 void MidiManagerMac::DispatchSendMidiData(MidiManagerClient
* client
,
139 const std::vector
<uint8
>& data
,
142 base::Bind(&MidiManagerMac::SendMidiData
,
143 base::Unretained(this), client
, port_index
, data
, timestamp
));
146 void MidiManagerMac::RunOnClientThread(const base::Closure
& closure
) {
150 if (!client_thread_
.IsRunning())
151 client_thread_
.Start();
153 client_thread_
.message_loop()->PostTask(FROM_HERE
, closure
);
156 void MidiManagerMac::InitializeCoreMIDI() {
157 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
159 // CoreMIDI registration.
160 DCHECK_EQ(0u, midi_client_
);
162 MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch
, this,
164 if (result
!= noErr
|| midi_client_
== 0)
165 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
167 // Create input and output port.
168 DCHECK_EQ(0u, coremidi_input_
);
169 result
= MIDIInputPortCreate(
175 if (result
!= noErr
|| coremidi_input_
== 0)
176 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
178 DCHECK_EQ(0u, coremidi_output_
);
179 result
= MIDIOutputPortCreate(
181 CFSTR("MIDI Output"),
183 if (result
!= noErr
|| coremidi_output_
== 0)
184 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
186 // Following loop may miss some newly attached devices, but such device will
187 // be captured by ReceiveMidiNotifyDispatch callback.
188 uint32 destination_count
= MIDIGetNumberOfDestinations();
189 destinations_
.resize(destination_count
);
190 for (uint32 i
= 0; i
< destination_count
; i
++) {
191 MIDIEndpointRef destination
= MIDIGetDestination(i
);
192 if (destination
== 0) {
193 // One ore more devices may be detached.
194 destinations_
.resize(i
);
198 // Keep track of all destinations (known as outputs by the Web MIDI API).
199 // Cache to avoid any possible overhead in calling MIDIGetDestination().
200 destinations_
[i
] = destination
;
202 MidiPortInfo info
= GetPortInfoFromEndpoint(destination
);
206 // Open connections from all sources. This loop also may miss new devices.
207 uint32 source_count
= MIDIGetNumberOfSources();
208 for (uint32 i
= 0; i
< source_count
; ++i
) {
209 // Receive from all sources.
210 MIDIEndpointRef source
= MIDIGetSource(i
);
215 MIDIPortConnectSource(
216 coremidi_input_
, source
, reinterpret_cast<void*>(source
));
218 // Keep track of all sources (known as inputs in Web MIDI API terminology).
219 source_map_
[source
] = i
;
221 MidiPortInfo info
= GetPortInfoFromEndpoint(source
);
225 // Allocate maximum size of buffer that CoreMIDI can handle.
226 midi_buffer_
.resize(kCoreMIDIMaxPacketListSize
);
228 CompleteInitialization(MIDI_OK
);
232 void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification
* message
,
234 // This callback function is invoked on |client_thread_|.
235 MidiManagerMac
* manager
= static_cast<MidiManagerMac
*>(refcon
);
236 manager
->ReceiveMidiNotify(message
);
239 void MidiManagerMac::ReceiveMidiNotify(const MIDINotification
* message
) {
240 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
242 if (kMIDIMsgObjectAdded
== message
->messageID
) {
243 // New device is going to be attached.
244 const MIDIObjectAddRemoveNotification
* notification
=
245 reinterpret_cast<const MIDIObjectAddRemoveNotification
*>(message
);
246 MIDIEndpointRef endpoint
=
247 static_cast<MIDIEndpointRef
>(notification
->child
);
248 if (notification
->childType
== kMIDIObjectType_Source
) {
249 // Attaching device is an input device.
250 auto it
= source_map_
.find(endpoint
);
251 if (it
== source_map_
.end()) {
252 MidiPortInfo info
= GetPortInfoFromEndpoint(endpoint
);
253 // If the device disappears before finishing queries, MidiPortInfo
254 // becomes incomplete. Skip and do not cache such information here.
255 // On kMIDIMsgObjectRemoved, the entry will be ignored because it
256 // will not be found in the pool.
257 if (!info
.id
.empty()) {
258 uint32 index
= source_map_
.size();
259 source_map_
[endpoint
] = index
;
261 MIDIPortConnectSource(
262 coremidi_input_
, endpoint
, reinterpret_cast<void*>(endpoint
));
265 SetInputPortState(it
->second
, MIDI_PORT_OPENED
);
267 } else if (notification
->childType
== kMIDIObjectType_Destination
) {
268 // Attaching device is an output device.
269 auto it
= std::find(destinations_
.begin(), destinations_
.end(), endpoint
);
270 if (it
== destinations_
.end()) {
271 MidiPortInfo info
= GetPortInfoFromEndpoint(endpoint
);
272 // Skip cases that queries are not finished correctly.
273 if (!info
.id
.empty()) {
274 destinations_
.push_back(endpoint
);
278 SetOutputPortState(it
- destinations_
.begin(), MIDI_PORT_OPENED
);
281 } else if (kMIDIMsgObjectRemoved
== message
->messageID
) {
282 // Existing device is going to be detached.
283 const MIDIObjectAddRemoveNotification
* notification
=
284 reinterpret_cast<const MIDIObjectAddRemoveNotification
*>(message
);
285 MIDIEndpointRef endpoint
=
286 static_cast<MIDIEndpointRef
>(notification
->child
);
287 if (notification
->childType
== kMIDIObjectType_Source
) {
288 // Detaching device is an input device.
289 auto it
= source_map_
.find(endpoint
);
290 if (it
!= source_map_
.end())
291 SetInputPortState(it
->second
, MIDI_PORT_DISCONNECTED
);
292 } else if (notification
->childType
== kMIDIObjectType_Destination
) {
293 // Detaching device is an output device.
294 auto it
= std::find(destinations_
.begin(), destinations_
.end(), endpoint
);
295 if (it
!= destinations_
.end())
296 SetOutputPortState(it
- destinations_
.begin(), MIDI_PORT_DISCONNECTED
);
302 void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList
* packet_list
,
303 void* read_proc_refcon
,
304 void* src_conn_refcon
) {
305 // This method is called on a separate high-priority thread owned by CoreMIDI.
307 MidiManagerMac
* manager
= static_cast<MidiManagerMac
*>(read_proc_refcon
);
309 MIDIEndpointRef source
= reinterpret_cast<uintptr_t>(src_conn_refcon
);
311 MIDIEndpointRef source
= static_cast<MIDIEndpointRef
>(src_conn_refcon
);
314 // Dispatch to class method.
315 manager
->ReadMidi(source
, packet_list
);
318 void MidiManagerMac::ReadMidi(MIDIEndpointRef source
,
319 const MIDIPacketList
* packet_list
) {
320 // This method is called from ReadMidiDispatch() and runs on a separate
321 // high-priority thread owned by CoreMIDI.
323 // Lookup the port index based on the source.
324 auto it
= source_map_
.find(source
);
325 if (it
== source_map_
.end())
327 // This is safe since MidiManagerMac does not remove any existing
328 // MIDIEndpointRef, and the order is saved.
329 uint32 port_index
= it
->second
;
331 // Go through each packet and process separately.
332 const MIDIPacket
* packet
= &packet_list
->packet
[0];
333 for (size_t i
= 0; i
< packet_list
->numPackets
; i
++) {
334 // Each packet contains MIDI data for one or more messages (like note-on).
335 double timestamp_seconds
= MIDITimeStampToSeconds(packet
->timeStamp
);
343 packet
= MIDIPacketNext(packet
);
347 void MidiManagerMac::SendMidiData(MidiManagerClient
* client
,
349 const std::vector
<uint8
>& data
,
351 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
353 // Lookup the destination based on the port index.
354 if (static_cast<size_t>(port_index
) >= destinations_
.size())
357 MIDITimeStamp coremidi_timestamp
= SecondsToMIDITimeStamp(timestamp
);
358 MIDIEndpointRef destination
= destinations_
[port_index
];
361 for (size_t sent_size
= 0; sent_size
< data
.size(); sent_size
+= send_size
) {
362 MIDIPacketList
* packet_list
=
363 reinterpret_cast<MIDIPacketList
*>(midi_buffer_
.data());
364 MIDIPacket
* midi_packet
= MIDIPacketListInit(packet_list
);
365 // Limit the maximum payload size to kEstimatedMaxPacketDataSize that is
366 // half of midi_buffer data size. MIDIPacketList and MIDIPacket consume
367 // extra buffer areas for meta information, and available size is smaller
368 // than buffer size. Here, we simply assume that at least half size is
369 // available for data payload.
370 send_size
= std::min(data
.size() - sent_size
, kEstimatedMaxPacketDataSize
);
371 midi_packet
= MIDIPacketListAdd(
373 kCoreMIDIMaxPacketListSize
,
380 MIDISend(coremidi_output_
, destination
, packet_list
);
383 client
->AccumulateMidiBytesSent(data
.size());