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
28 MidiPortInfo
GetPortInfoFromEndpoint(
29 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 return MidiPortInfo(id
, manufacturer
, name
, version
);
71 double MIDITimeStampToSeconds(MIDITimeStamp timestamp
) {
72 UInt64 nanoseconds
= AudioConvertHostTimeToNanos(timestamp
);
73 return static_cast<double>(nanoseconds
) / 1.0e9
;
76 MIDITimeStamp
SecondsToMIDITimeStamp(double seconds
) {
77 UInt64 nanos
= UInt64(seconds
* 1.0e9
);
78 return AudioConvertNanosToHostTime(nanos
);
83 MidiManager
* MidiManager::Create() {
84 return new MidiManagerMac();
87 MidiManagerMac::MidiManagerMac()
93 client_thread_("MidiClientThread"),
97 MidiManagerMac::~MidiManagerMac() {
98 // Wait for the termination of |client_thread_| before disposing MIDI ports.
100 client_thread_
.Stop();
103 MIDIPortDispose(coremidi_input_
);
104 if (coremidi_output_
)
105 MIDIPortDispose(coremidi_output_
);
107 MIDIClientDispose(midi_client_
);
110 void MidiManagerMac::StartInitialization() {
111 // MIDIClient should be created on |client_thread_| to receive CoreMIDI event
114 base::Bind(&MidiManagerMac::InitializeCoreMIDI
, base::Unretained(this)));
117 void MidiManagerMac::DispatchSendMidiData(MidiManagerClient
* client
,
119 const std::vector
<uint8
>& data
,
122 base::Bind(&MidiManagerMac::SendMidiData
,
123 base::Unretained(this), client
, port_index
, data
, timestamp
));
126 void MidiManagerMac::RunOnClientThread(const base::Closure
& closure
) {
130 if (!client_thread_
.IsRunning())
131 client_thread_
.Start();
133 client_thread_
.message_loop()->PostTask(FROM_HERE
, closure
);
136 void MidiManagerMac::InitializeCoreMIDI() {
137 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
139 // CoreMIDI registration.
140 // TODO(toyoshim): Set MIDINotifyProc to receive CoreMIDI event notifications.
143 MIDIClientCreate(CFSTR("Chrome"), NULL
, NULL
, &midi_client_
);
146 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
150 // Create input and output port.
151 result
= MIDIInputPortCreate(
158 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
160 result
= MIDIOutputPortCreate(
162 CFSTR("MIDI Output"),
165 return CompleteInitialization(MIDI_INITIALIZATION_ERROR
);
167 uint32 destination_count
= MIDIGetNumberOfDestinations();
168 destinations_
.resize(destination_count
);
170 for (uint32 i
= 0; i
< destination_count
; i
++) {
171 MIDIEndpointRef destination
= MIDIGetDestination(i
);
173 // Keep track of all destinations (known as outputs by the Web MIDI API).
174 // Cache to avoid any possible overhead in calling MIDIGetDestination().
175 destinations_
[i
] = destination
;
177 MidiPortInfo info
= GetPortInfoFromEndpoint(destination
);
181 // Open connections from all sources.
182 uint32 source_count
= MIDIGetNumberOfSources();
184 for (uint32 i
= 0; i
< source_count
; ++i
) {
185 // Receive from all sources.
186 MIDIEndpointRef src
= MIDIGetSource(i
);
187 MIDIPortConnectSource(coremidi_input_
, src
, reinterpret_cast<void*>(src
));
189 // Keep track of all sources (known as inputs in Web MIDI API terminology).
190 source_map_
[src
] = i
;
192 MidiPortInfo info
= GetPortInfoFromEndpoint(src
);
196 packet_list_
= reinterpret_cast<MIDIPacketList
*>(midi_buffer_
);
197 midi_packet_
= MIDIPacketListInit(packet_list_
);
199 CompleteInitialization(MIDI_OK
);
203 void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList
* packet_list
,
204 void* read_proc_refcon
,
205 void* src_conn_refcon
) {
206 // This method is called on a separate high-priority thread owned by CoreMIDI.
208 MidiManagerMac
* manager
= static_cast<MidiManagerMac
*>(read_proc_refcon
);
210 MIDIEndpointRef source
= reinterpret_cast<uintptr_t>(src_conn_refcon
);
212 MIDIEndpointRef source
= static_cast<MIDIEndpointRef
>(src_conn_refcon
);
215 // Dispatch to class method.
216 manager
->ReadMidi(source
, packet_list
);
219 void MidiManagerMac::ReadMidi(MIDIEndpointRef source
,
220 const MIDIPacketList
* packet_list
) {
221 // This method is called from ReadMidiDispatch() and runs on a separate
222 // high-priority thread owned by CoreMIDI.
224 // Lookup the port index based on the source.
225 SourceMap::iterator j
= source_map_
.find(source
);
226 if (j
== source_map_
.end())
228 // This is safe since MidiManagerMac does not remove any existing
229 // MIDIEndpointRef, and the order is saved.
230 uint32 port_index
= source_map_
[source
];
232 // Go through each packet and process separately.
233 for (size_t i
= 0; i
< packet_list
->numPackets
; i
++) {
234 // Each packet contains MIDI data for one or more messages (like note-on).
235 const MIDIPacket
&packet
= packet_list
->packet
[i
];
236 double timestamp_seconds
= MIDITimeStampToSeconds(packet
.timeStamp
);
246 void MidiManagerMac::SendMidiData(MidiManagerClient
* client
,
248 const std::vector
<uint8
>& data
,
250 DCHECK(client_thread_
.message_loop_proxy()->BelongsToCurrentThread());
252 // System Exclusive has already been filtered.
253 MIDITimeStamp coremidi_timestamp
= SecondsToMIDITimeStamp(timestamp
);
255 midi_packet_
= MIDIPacketListAdd(
263 // Lookup the destination based on the port index.
264 if (static_cast<size_t>(port_index
) >= destinations_
.size())
267 MIDIEndpointRef destination
= destinations_
[port_index
];
269 MIDISend(coremidi_output_
, destination
, packet_list_
);
271 // Re-initialize for next time.
272 midi_packet_
= MIDIPacketListInit(packet_list_
);
274 client
->AccumulateMidiBytesSent(data
.size());