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/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
;
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
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
) {
36 CFStringRef manufacturer_ref
= NULL
;
37 OSStatus result
= MIDIObjectGetStringProperty(
38 endpoint
, kMIDIPropertyManufacturer
, &manufacturer_ref
);
39 if (result
== noErr
) {
40 manufacturer
= SysCFStringRefToUTF8(manufacturer_ref
);
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 "
49 CFStringRef name_ref
= NULL
;
50 result
= MIDIObjectGetStringProperty(endpoint
, kMIDIPropertyDisplayName
,
52 if (result
== noErr
) {
53 name
= SysCFStringRefToUTF8(name_ref
);
55 DLOG(WARNING
) << "Failed to get kMIDIPropertyDisplayName with status "
60 SInt32 version_number
= 0;
61 result
= MIDIObjectGetIntegerProperty(
62 endpoint
, kMIDIPropertyDriverVersion
, &version_number
);
63 if (result
== noErr
) {
64 version
= IntToString(version_number
);
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 "
74 result
= MIDIObjectGetIntegerProperty(
75 endpoint
, kMIDIPropertyUniqueID
, &id_number
);
76 if (result
== noErr
) {
77 id
= IntToString(id_number
);
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
84 DLOG(WARNING
) << "Failed to get kMIDIPropertyUniqueID with status "
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
);
104 MidiManager
* MidiManager::Create() {
105 return new MidiManagerMac();
108 MidiManagerMac::MidiManagerMac()
112 client_thread_("MidiClientThread"),
116 MidiManagerMac::~MidiManagerMac() {
117 // Wait for the termination of |client_thread_| before disposing MIDI ports.
119 client_thread_
.Stop();
122 MIDIPortDispose(coremidi_input_
);
123 if (coremidi_output_
)
124 MIDIPortDispose(coremidi_output_
);
126 MIDIClientDispose(midi_client_
);
129 void MidiManagerMac::StartInitialization() {
130 // MIDIClient should be created on |client_thread_| to receive CoreMIDI event
133 base::Bind(&MidiManagerMac::InitializeCoreMIDI
, base::Unretained(this)));
136 void MidiManagerMac::DispatchSendMidiData(MidiManagerClient
* client
,
138 const std::vector
<uint8
>& data
,
141 base::Bind(&MidiManagerMac::SendMidiData
,
142 base::Unretained(this), client
, port_index
, data
, timestamp
));
145 void MidiManagerMac::RunOnClientThread(const base::Closure
& closure
) {
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_
);
161 MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch
, this,
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(
174 if (result
!= noErr
|| coremidi_input_
== 0)
175 return CompleteInitialization(Result::INITIALIZATION_ERROR
);
177 DCHECK_EQ(0u, coremidi_output_
);
178 result
= MIDIOutputPortCreate(
180 CFSTR("MIDI 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
);
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
);
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
);
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
);
224 // Allocate maximum size of buffer that CoreMIDI can handle.
225 midi_buffer_
.resize(kCoreMIDIMaxPacketListSize
);
227 CompleteInitialization(Result::OK
);
231 void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification
* message
,
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
;
260 MIDIPortConnectSource(
261 coremidi_input_
, endpoint
, reinterpret_cast<void*>(endpoint
));
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
);
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
);
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
);
308 MIDIEndpointRef source
= reinterpret_cast<uintptr_t>(src_conn_refcon
);
310 MIDIEndpointRef source
= static_cast<MIDIEndpointRef
>(src_conn_refcon
);
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())
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
);
342 packet
= MIDIPacketNext(packet
);
346 void MidiManagerMac::SendMidiData(MidiManagerClient
* client
,
348 const std::vector
<uint8
>& data
,
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())
356 MIDITimeStamp coremidi_timestamp
= SecondsToMIDITimeStamp(timestamp
);
357 MIDIEndpointRef destination
= destinations_
[port_index
];
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(
372 kCoreMIDIMaxPacketListSize
,
379 MIDISend(coremidi_output_
, destination
, packet_list
);
382 client
->AccumulateMidiBytesSent(data
.size());