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. 4 5#include "media/midi/midi_manager_mac.h" 6 7#include <string> 8 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" 13 14#include <CoreAudio/HostTime.h> 15 16using base::IntToString; 17using base::SysCFStringRefToUTF8; 18using std::string; 19 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 22// (if possible). 23 24namespace media { 25 26MIDIManager* MIDIManager::Create() { 27 return new MIDIManagerMac(); 28} 29 30MIDIManagerMac::MIDIManagerMac() 31 : midi_client_(0), 32 coremidi_input_(0), 33 coremidi_output_(0), 34 packet_list_(NULL), 35 midi_packet_(NULL), 36 send_thread_("MIDISendThread") { 37} 38 39bool MIDIManagerMac::Initialize() { 40 TRACE_EVENT0("midi", "MIDIManagerMac::Initialize"); 41 42 // CoreMIDI registration. 43 midi_client_ = 0; 44 OSStatus result = MIDIClientCreate( 45 CFSTR("Google Chrome"), 46 NULL, 47 NULL, 48 &midi_client_); 49 50 if (result != noErr) 51 return false; 52 53 coremidi_input_ = 0; 54 55 // Create input and output port. 56 result = MIDIInputPortCreate( 57 midi_client_, 58 CFSTR("MIDI Input"), 59 ReadMIDIDispatch, 60 this, 61 &coremidi_input_); 62 if (result != noErr) 63 return false; 64 65 result = MIDIOutputPortCreate( 66 midi_client_, 67 CFSTR("MIDI Output"), 68 &coremidi_output_); 69 if (result != noErr) 70 return false; 71 72 uint32 destination_count = MIDIGetNumberOfDestinations(); 73 destinations_.resize(destination_count); 74 75 for (uint32 i = 0; i < destination_count ; i++) { 76 MIDIEndpointRef destination = MIDIGetDestination(i); 77 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; 81 82 MIDIPortInfo info = GetPortInfoFromEndpoint(destination); 83 AddOutputPort(info); 84 } 85 86 // Open connections from all sources. 87 uint32 source_count = MIDIGetNumberOfSources(); 88 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)); 93 94 // Keep track of all sources (known as inputs in Web MIDI API terminology). 95 source_map_[src] = i; 96 97 MIDIPortInfo info = GetPortInfoFromEndpoint(src); 98 AddInputPort(info); 99 } 100 101 // TODO(crogers): Fix the memory management here! 102 packet_list_ = reinterpret_cast<MIDIPacketList*>(midi_buffer_); 103 midi_packet_ = MIDIPacketListInit(packet_list_); 104 105 return true; 106} 107 108void MIDIManagerMac::DispatchSendMIDIData(MIDIManagerClient* client, 109 uint32 port_index, 110 const std::vector<uint8>& data, 111 double timestamp) { 112 if (!send_thread_.IsRunning()) 113 send_thread_.Start(); 114 115 // OK to use base::Unretained(this) since we join to thread in dtor(). 116 send_thread_.message_loop()->PostTask( 117 FROM_HERE, 118 base::Bind(&MIDIManagerMac::SendMIDIData, base::Unretained(this), 119 client, port_index, data, timestamp)); 120} 121 122MIDIManagerMac::~MIDIManagerMac() { 123 // Wait for the termination of |send_thread_| before disposing MIDI ports. 124 send_thread_.Stop(); 125 126 if (coremidi_input_) 127 MIDIPortDispose(coremidi_input_); 128 if (coremidi_output_) 129 MIDIPortDispose(coremidi_output_); 130} 131 132// static 133void 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); 137#if __LP64__ 138 MIDIEndpointRef source = reinterpret_cast<uintptr_t>(src_conn_refcon); 139#else 140 MIDIEndpointRef source = static_cast<MIDIEndpointRef>(src_conn_refcon); 141#endif 142 143 // Dispatch to class method. 144 manager->ReadMIDI(source, packet_list); 145} 146 147void 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()) 152 return; 153 uint32 port_index = source_map_[source]; 154 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); 160 161 ReceiveMIDIData( 162 port_index, 163 packet.data, 164 packet.length, 165 timestamp_seconds); 166 } 167} 168 169void MIDIManagerMac::SendMIDIData(MIDIManagerClient* client, 170 uint32 port_index, 171 const std::vector<uint8>& data, 172 double timestamp) { 173 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); 174 175 // System Exclusive has already been filtered. 176 MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp); 177 178 midi_packet_ = MIDIPacketListAdd( 179 packet_list_, 180 kMaxPacketListSize, 181 midi_packet_, 182 coremidi_timestamp, 183 data.size(), 184 &data[0]); 185 186 // Lookup the destination based on the port index. 187 if (static_cast<size_t>(port_index) >= destinations_.size()) 188 return; 189 190 MIDIEndpointRef destination = destinations_[port_index]; 191 192 MIDISend(coremidi_output_, destination, packet_list_); 193 194 // Re-initialize for next time. 195 midi_packet_ = MIDIPacketListInit(packet_list_); 196 197 client->AccumulateMIDIBytesSent(data.size()); 198} 199 200// static 201MIDIPortInfo MIDIManagerMac::GetPortInfoFromEndpoint( 202 MIDIEndpointRef endpoint) { 203 SInt32 id_number = 0; 204 MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyUniqueID, &id_number); 205 string id = IntToString(id_number); 206 207 string manufacturer; 208 CFStringRef manufacturer_ref = NULL; 209 OSStatus result = MIDIObjectGetStringProperty( 210 endpoint, kMIDIPropertyManufacturer, &manufacturer_ref); 211 if (result == noErr) { 212 manufacturer = SysCFStringRefToUTF8(manufacturer_ref); 213 } else { 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 " 217 << result; 218 } 219 220 string name; 221 CFStringRef name_ref = NULL; 222 result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref); 223 if (result == noErr) 224 name = SysCFStringRefToUTF8(name_ref); 225 else 226 DLOG(WARNING) << "Failed to get kMIDIPropertyName with status " << result; 227 228 string version; 229 SInt32 version_number = 0; 230 result = MIDIObjectGetIntegerProperty( 231 endpoint, kMIDIPropertyDriverVersion, &version_number); 232 if (result == noErr) { 233 version = IntToString(version_number); 234 } else { 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 " 238 << result; 239 } 240 241 return MIDIPortInfo(id, manufacturer, name, version); 242} 243 244// static 245double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) { 246 UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp); 247 return static_cast<double>(nanoseconds) / 1.0e9; 248} 249 250// static 251MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) { 252 UInt64 nanos = UInt64(seconds * 1.0e9); 253 return AudioConvertNanosToHostTime(nanos); 254} 255 256} // namespace media 257