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"
9 #include "base/debug/trace_event.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
26 MIDIManager
* MIDIManager::Create() {
27 return new MIDIManagerMac();
30 MIDIManagerMac::MIDIManagerMac()
36 send_thread_("MIDISendThread") {
39 bool MIDIManagerMac::Initialize() {
40 TRACE_EVENT0("midi", "MIDIManagerMac::Initialize");
42 // CoreMIDI registration.
44 OSStatus result
= MIDIClientCreate(
45 CFSTR("Google Chrome"),
55 // Create input and output port.
56 result
= MIDIInputPortCreate(
65 result
= MIDIOutputPortCreate(
72 uint32 destination_count
= MIDIGetNumberOfDestinations();
73 destinations_
.resize(destination_count
);
75 for (uint32 i
= 0; i
< destination_count
; i
++) {
76 MIDIEndpointRef destination
= MIDIGetDestination(i
);
78 // Keep track of all destinations (known as outputs by the Web MIDI API).
79 // Cache to avoid any possible overhead in calling MIDIGetDestination().
80 destinations_
[i
] = destination
;
82 MIDIPortInfo info
= GetPortInfoFromEndpoint(destination
);
86 // Open connections from all sources.
87 uint32 source_count
= MIDIGetNumberOfSources();
89 for (uint32 i
= 0; i
< source_count
; ++i
) {
90 // Receive from all sources.
91 MIDIEndpointRef src
= MIDIGetSource(i
);
92 MIDIPortConnectSource(coremidi_input_
, src
, reinterpret_cast<void*>(src
));
94 // Keep track of all sources (known as inputs in Web MIDI API terminology).
97 MIDIPortInfo info
= GetPortInfoFromEndpoint(src
);
101 // TODO(crogers): Fix the memory management here!
102 packet_list_
= reinterpret_cast<MIDIPacketList
*>(midi_buffer_
);
103 midi_packet_
= MIDIPacketListInit(packet_list_
);
108 void MIDIManagerMac::DispatchSendMIDIData(MIDIManagerClient
* client
,
110 const std::vector
<uint8
>& data
,
112 if (!send_thread_
.IsRunning())
113 send_thread_
.Start();
115 // OK to use base::Unretained(this) since we join to thread in dtor().
116 send_thread_
.message_loop()->PostTask(
118 base::Bind(&MIDIManagerMac::SendMIDIData
, base::Unretained(this),
119 client
, port_index
, data
, timestamp
));
122 MIDIManagerMac::~MIDIManagerMac() {
123 // Wait for the termination of |send_thread_| before disposing MIDI ports.
127 MIDIPortDispose(coremidi_input_
);
128 if (coremidi_output_
)
129 MIDIPortDispose(coremidi_output_
);
133 void MIDIManagerMac::ReadMIDIDispatch(const MIDIPacketList
* packet_list
,
134 void* read_proc_refcon
,
135 void* src_conn_refcon
) {
136 MIDIManagerMac
* manager
= static_cast<MIDIManagerMac
*>(read_proc_refcon
);
138 MIDIEndpointRef source
= reinterpret_cast<uintptr_t>(src_conn_refcon
);
140 MIDIEndpointRef source
= static_cast<MIDIEndpointRef
>(src_conn_refcon
);
143 // Dispatch to class method.
144 manager
->ReadMIDI(source
, packet_list
);
147 void MIDIManagerMac::ReadMIDI(MIDIEndpointRef source
,
148 const MIDIPacketList
* packet_list
) {
149 // Lookup the port index based on the source.
150 SourceMap::iterator j
= source_map_
.find(source
);
151 if (j
== source_map_
.end())
153 uint32 port_index
= source_map_
[source
];
155 // Go through each packet and process separately.
156 for (size_t i
= 0; i
< packet_list
->numPackets
; i
++) {
157 // Each packet contains MIDI data for one or more messages (like note-on).
158 const MIDIPacket
&packet
= packet_list
->packet
[i
];
159 double timestamp_seconds
= MIDITimeStampToSeconds(packet
.timeStamp
);
169 void MIDIManagerMac::SendMIDIData(MIDIManagerClient
* client
,
171 const std::vector
<uint8
>& data
,
173 DCHECK(send_thread_
.message_loop_proxy()->BelongsToCurrentThread());
175 // System Exclusive has already been filtered.
176 MIDITimeStamp coremidi_timestamp
= SecondsToMIDITimeStamp(timestamp
);
178 midi_packet_
= MIDIPacketListAdd(
186 // Lookup the destination based on the port index.
187 if (static_cast<size_t>(port_index
) >= destinations_
.size())
190 MIDIEndpointRef destination
= destinations_
[port_index
];
192 MIDISend(coremidi_output_
, destination
, packet_list_
);
194 // Re-initialize for next time.
195 midi_packet_
= MIDIPacketListInit(packet_list_
);
197 client
->AccumulateMIDIBytesSent(data
.size());
201 MIDIPortInfo
MIDIManagerMac::GetPortInfoFromEndpoint(
202 MIDIEndpointRef endpoint
) {
203 SInt32 id_number
= 0;
204 MIDIObjectGetIntegerProperty(endpoint
, kMIDIPropertyUniqueID
, &id_number
);
205 string id
= IntToString(id_number
);
208 CFStringRef manufacturer_ref
= NULL
;
209 OSStatus result
= MIDIObjectGetStringProperty(
210 endpoint
, kMIDIPropertyManufacturer
, &manufacturer_ref
);
211 if (result
== noErr
) {
212 manufacturer
= SysCFStringRefToUTF8(manufacturer_ref
);
214 // kMIDIPropertyManufacturer is not supported in IAC driver providing
215 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
216 DLOG(WARNING
) << "Failed to get kMIDIPropertyManufacturer with status "
221 CFStringRef name_ref
= NULL
;
222 result
= MIDIObjectGetStringProperty(endpoint
, kMIDIPropertyName
, &name_ref
);
224 name
= SysCFStringRefToUTF8(name_ref
);
226 DLOG(WARNING
) << "Failed to get kMIDIPropertyName with status " << result
;
229 SInt32 version_number
= 0;
230 result
= MIDIObjectGetIntegerProperty(
231 endpoint
, kMIDIPropertyDriverVersion
, &version_number
);
232 if (result
== noErr
) {
233 version
= IntToString(version_number
);
235 // kMIDIPropertyDriverVersion is not supported in IAC driver providing
236 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
237 DLOG(WARNING
) << "Failed to get kMIDIPropertyDriverVersion with status "
241 return MIDIPortInfo(id
, manufacturer
, name
, version
);
245 double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp
) {
246 UInt64 nanoseconds
= AudioConvertHostTimeToNanos(timestamp
);
247 return static_cast<double>(nanoseconds
) / 1.0e9
;
251 MIDITimeStamp
MIDIManagerMac::SecondsToMIDITimeStamp(double seconds
) {
252 UInt64 nanos
= UInt64(seconds
* 1.0e9
);
253 return AudioConvertNanosToHostTime(nanos
);