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
29 MidiPortInfo
GetPortInfoFromEndpoint(MIDIEndpointRef endpoint
) {
31 MIDIObjectGetIntegerProperty(endpoint
, kMIDIPropertyUniqueID
, &id_number
);
32 string id
= IntToString(id_number
);
35 CFStringRef manufacturer_ref
= NULL
;
36 OSStatus result
= MIDIObjectGetStringProperty(
37 endpoint
, kMIDIPropertyManufacturer
, &manufacturer_ref
);
38 if (result
== noErr
) {
39 manufacturer
= SysCFStringRefToUTF8(manufacturer_ref
);
41 // kMIDIPropertyManufacturer is not supported in IAC driver providing
42 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
43 DLOG(WARNING
) << "Failed to get kMIDIPropertyManufacturer with status "
48 CFStringRef name_ref
= NULL
;
49 result
= MIDIObjectGetStringProperty(endpoint
, kMIDIPropertyName
, &name_ref
);
51 name
= SysCFStringRefToUTF8(name_ref
);
53 DLOG(WARNING
) << "Failed to get kMIDIPropertyName with status " << result
;
56 SInt32 version_number
= 0;
57 result
= MIDIObjectGetIntegerProperty(
58 endpoint
, kMIDIPropertyDriverVersion
, &version_number
);
59 if (result
== noErr
) {
60 version
= IntToString(version_number
);
62 // kMIDIPropertyDriverVersion is not supported in IAC driver providing
63 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
64 DLOG(WARNING
) << "Failed to get kMIDIPropertyDriverVersion with status "
68 const MidiPortState state
= MIDI_PORT_OPENED
;
69 return MidiPortInfo(id
, manufacturer
, name
, version
, state
);
72 double MIDITimeStampToSeconds(MIDITimeStamp timestamp
) {
73 UInt64 nanoseconds
= AudioConvertHostTimeToNanos(timestamp
);
74 return static_cast<double>(nanoseconds
) / 1.0e9
;
77 MIDITimeStamp
SecondsToMIDITimeStamp(double seconds
) {
78 UInt64 nanos
= UInt64(seconds
* 1.0e9
);
79 return AudioConvertNanosToHostTime(nanos
);
84 MidiManager
* MidiManager::Create() {
85 return new MidiManagerMac();
88 MidiManagerMac::MidiManagerMac()
94 client_thread_("MidiClientThread"),
98 MidiManagerMac::~MidiManagerMac() {
99 // Wait for the termination of |client_thread_| before disposing MIDI ports.
101 client_thread_
.Stop();
104 MIDIPortDispose(coremidi_input_
);
105 if (coremidi_output_
)
106 MIDIPortDispose(coremidi_output_
);
108 MIDIClientDispose(midi_client_
);
111 void MidiManagerMac::StartInitialization() {
112 // MIDIClient should be created on |client_thread_| to receive CoreMIDI event
115 base::Bind(&MidiManagerMac::InitializeCoreMIDI
, base::Unretained(this)));
118 void MidiManagerMac::DispatchSendMidiData(MidiManagerClient
* client
,
120 const std::vector
<uint8
>& data
,
123 base::Bind(&MidiManagerMac::SendMidiData
,
124 base::Unretained(this), client
, port_index
, data
, timestamp
));
127 void MidiManagerMac::RunOnClientThread(const base::Closure
& closure
) {
131 if (!client_thread_
.IsRunning())
132 client_thread_
.Start();
134 client_thread_
.message_loop()->PostTask(FROM_HERE
, closure
);
137 void MidiManagerMac::InitializeCoreMIDI() {
138 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
140 // CoreMIDI registration.
143 MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch
, this,
147 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
151 // Create input and output port.
152 result
= MIDIInputPortCreate(
159 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
161 result
= MIDIOutputPortCreate(
163 CFSTR("MIDI Output"),
166 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
168 uint32 destination_count
= MIDIGetNumberOfDestinations();
169 destinations_
.resize(destination_count
);
171 for (uint32 i
= 0; i
< destination_count
; i
++) {
172 MIDIEndpointRef destination
= MIDIGetDestination(i
);
174 // Keep track of all destinations (known as outputs by the Web MIDI API).
175 // Cache to avoid any possible overhead in calling MIDIGetDestination().
176 destinations_
[i
] = destination
;
178 MidiPortInfo info
= GetPortInfoFromEndpoint(destination
);
182 // Open connections from all sources.
183 uint32 source_count
= MIDIGetNumberOfSources();
185 for (uint32 i
= 0; i
< source_count
; ++i
) {
186 // Receive from all sources.
187 MIDIEndpointRef src
= MIDIGetSource(i
);
188 MIDIPortConnectSource(coremidi_input_
, src
, reinterpret_cast<void*>(src
));
190 // Keep track of all sources (known as inputs in Web MIDI API terminology).
191 source_map_
[src
] = i
;
193 MidiPortInfo info
= GetPortInfoFromEndpoint(src
);
197 packet_list_
= reinterpret_cast<MIDIPacketList
*>(midi_buffer_
);
198 midi_packet_
= MIDIPacketListInit(packet_list_
);
200 CompleteInitialization(MIDI_OK
);
204 void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification
* message
,
206 MidiManagerMac
* manager
= static_cast<MidiManagerMac
*>(refcon
);
207 manager
->ReceiveMidiNotify(message
);
210 void MidiManagerMac::ReceiveMidiNotify(const MIDINotification
* message
) {
211 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
213 if (kMIDIMsgObjectAdded
== message
->messageID
) {
214 const MIDIObjectAddRemoveNotification
* notification
=
215 reinterpret_cast<const MIDIObjectAddRemoveNotification
*>(message
);
216 MIDIEndpointRef endpoint
=
217 static_cast<MIDIEndpointRef
>(notification
->child
);
218 if (notification
->childType
== kMIDIObjectType_Source
) {
219 SourceMap::iterator it
= source_map_
.find(endpoint
);
220 if (it
== source_map_
.end()) {
221 uint32 index
= source_map_
.size();
222 source_map_
[endpoint
] = index
;
223 MidiPortInfo info
= GetPortInfoFromEndpoint(endpoint
);
225 MIDIPortConnectSource(
226 coremidi_input_
, endpoint
, reinterpret_cast<void*>(endpoint
));
228 uint32 index
= it
->second
;
229 SetInputPortState(index
, MIDI_PORT_OPENED
);
231 } else if (notification
->childType
== kMIDIObjectType_Destination
) {
232 auto i
= std::find(destinations_
.begin(), destinations_
.end(), endpoint
);
233 if (i
!= destinations_
.end()) {
234 SetOutputPortState(i
- destinations_
.begin(), MIDI_PORT_OPENED
);
236 destinations_
.push_back(endpoint
);
237 MidiPortInfo info
= GetPortInfoFromEndpoint(endpoint
);
241 } else if (kMIDIMsgObjectRemoved
== message
->messageID
) {
242 const MIDIObjectAddRemoveNotification
* notification
=
243 reinterpret_cast<const MIDIObjectAddRemoveNotification
*>(message
);
244 MIDIEndpointRef endpoint
=
245 static_cast<MIDIEndpointRef
>(notification
->child
);
246 if (notification
->childType
== kMIDIObjectType_Source
) {
247 SourceMap::iterator it
= source_map_
.find(endpoint
);
248 if (it
!= source_map_
.end()) {
249 uint32 index
= it
->second
;
250 SetInputPortState(index
, MIDI_PORT_DISCONNECTED
);
252 } else if (notification
->childType
== kMIDIObjectType_Destination
) {
253 auto i
= std::find(destinations_
.begin(), destinations_
.end(), endpoint
);
254 if (i
!= destinations_
.end())
255 SetOutputPortState(i
- destinations_
.begin(), MIDI_PORT_DISCONNECTED
);
261 void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList
* packet_list
,
262 void* read_proc_refcon
,
263 void* src_conn_refcon
) {
264 // This method is called on a separate high-priority thread owned by CoreMIDI.
266 MidiManagerMac
* manager
= static_cast<MidiManagerMac
*>(read_proc_refcon
);
268 MIDIEndpointRef source
= reinterpret_cast<uintptr_t>(src_conn_refcon
);
270 MIDIEndpointRef source
= static_cast<MIDIEndpointRef
>(src_conn_refcon
);
273 // Dispatch to class method.
274 manager
->ReadMidi(source
, packet_list
);
277 void MidiManagerMac::ReadMidi(MIDIEndpointRef source
,
278 const MIDIPacketList
* packet_list
) {
279 // This method is called from ReadMidiDispatch() and runs on a separate
280 // high-priority thread owned by CoreMIDI.
282 // Lookup the port index based on the source.
283 SourceMap::iterator j
= source_map_
.find(source
);
284 if (j
== source_map_
.end())
286 // This is safe since MidiManagerMac does not remove any existing
287 // MIDIEndpointRef, and the order is saved.
288 uint32 port_index
= source_map_
[source
];
290 // Go through each packet and process separately.
291 const MIDIPacket
* packet
= &packet_list
->packet
[0];
292 for (size_t i
= 0; i
< packet_list
->numPackets
; i
++) {
293 // Each packet contains MIDI data for one or more messages (like note-on).
294 double timestamp_seconds
= MIDITimeStampToSeconds(packet
->timeStamp
);
302 packet
= MIDIPacketNext(packet
);
306 void MidiManagerMac::SendMidiData(MidiManagerClient
* client
,
308 const std::vector
<uint8
>& data
,
310 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
312 // System Exclusive has already been filtered.
313 MIDITimeStamp coremidi_timestamp
= SecondsToMIDITimeStamp(timestamp
);
315 midi_packet_
= MIDIPacketListAdd(
323 // Lookup the destination based on the port index.
324 if (static_cast<size_t>(port_index
) >= destinations_
.size())
327 MIDIEndpointRef destination
= destinations_
[port_index
];
329 MIDISend(coremidi_output_
, destination
, packet_list_
);
331 // Re-initialize for next time.
332 midi_packet_
= MIDIPacketListInit(packet_list_
);
334 client
->AccumulateMidiBytesSent(data
.size());