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/bind.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 16 using base::IntToString; 17 using base::SysCFStringRefToUTF8; 18 using 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 24 namespace media { 25 26 MidiManager* MidiManager::Create() { 27 return new MidiManagerMac(); 28 } 29 30 MidiManagerMac::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 39 void MidiManagerMac::StartInitialization() { 40 // CoreMIDI registration. 41 midi_client_ = 0; 42 OSStatus result = 43 MIDIClientCreate(CFSTR("Chrome"), NULL, NULL, &midi_client_); 44 45 if (result != noErr) 46 return CompleteInitialization(MIDI_INITIALIZATION_ERROR); 47 48 coremidi_input_ = 0; 49 50 // Create input and output port. 51 result = MIDIInputPortCreate( 52 midi_client_, 53 CFSTR("MIDI Input"), 54 ReadMidiDispatch, 55 this, 56 &coremidi_input_); 57 if (result != noErr) 58 return CompleteInitialization(MIDI_INITIALIZATION_ERROR); 59 60 result = MIDIOutputPortCreate( 61 midi_client_, 62 CFSTR("MIDI Output"), 63 &coremidi_output_); 64 if (result != noErr) 65 return CompleteInitialization(MIDI_INITIALIZATION_ERROR); 66 67 uint32 destination_count = MIDIGetNumberOfDestinations(); 68 destinations_.resize(destination_count); 69 70 for (uint32 i = 0; i < destination_count ; i++) { 71 MIDIEndpointRef destination = MIDIGetDestination(i); 72 73 // Keep track of all destinations (known as outputs by the Web MIDI API). 74 // Cache to avoid any possible overhead in calling MIDIGetDestination(). 75 destinations_[i] = destination; 76 77 MidiPortInfo info = GetPortInfoFromEndpoint(destination); 78 AddOutputPort(info); 79 } 80 81 // Open connections from all sources. 82 uint32 source_count = MIDIGetNumberOfSources(); 83 84 for (uint32 i = 0; i < source_count; ++i) { 85 // Receive from all sources. 86 MIDIEndpointRef src = MIDIGetSource(i); 87 MIDIPortConnectSource(coremidi_input_, src, reinterpret_cast<void*>(src)); 88 89 // Keep track of all sources (known as inputs in Web MIDI API terminology). 90 source_map_[src] = i; 91 92 MidiPortInfo info = GetPortInfoFromEndpoint(src); 93 AddInputPort(info); 94 } 95 96 packet_list_ = reinterpret_cast<MIDIPacketList*>(midi_buffer_); 97 midi_packet_ = MIDIPacketListInit(packet_list_); 98 99 CompleteInitialization(MIDI_OK); 100 } 101 102 void MidiManagerMac::DispatchSendMidiData(MidiManagerClient* client, 103 uint32 port_index, 104 const std::vector<uint8>& data, 105 double timestamp) { 106 if (!send_thread_.IsRunning()) 107 send_thread_.Start(); 108 109 // OK to use base::Unretained(this) since we join to thread in dtor(). 110 send_thread_.message_loop()->PostTask( 111 FROM_HERE, 112 base::Bind(&MidiManagerMac::SendMidiData, base::Unretained(this), 113 client, port_index, data, timestamp)); 114 } 115 116 MidiManagerMac::~MidiManagerMac() { 117 // Wait for the termination of |send_thread_| before disposing MIDI ports. 118 send_thread_.Stop(); 119 120 if (coremidi_input_) 121 MIDIPortDispose(coremidi_input_); 122 if (coremidi_output_) 123 MIDIPortDispose(coremidi_output_); 124 } 125 126 // static 127 void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list, 128 void* read_proc_refcon, 129 void* src_conn_refcon) { 130 MidiManagerMac* manager = static_cast<MidiManagerMac*>(read_proc_refcon); 131 #if __LP64__ 132 MIDIEndpointRef source = reinterpret_cast<uintptr_t>(src_conn_refcon); 133 #else 134 MIDIEndpointRef source = static_cast<MIDIEndpointRef>(src_conn_refcon); 135 #endif 136 137 // Dispatch to class method. 138 manager->ReadMidi(source, packet_list); 139 } 140 141 void MidiManagerMac::ReadMidi(MIDIEndpointRef source, 142 const MIDIPacketList* packet_list) { 143 // Lookup the port index based on the source. 144 SourceMap::iterator j = source_map_.find(source); 145 if (j == source_map_.end()) 146 return; 147 uint32 port_index = source_map_[source]; 148 149 // Go through each packet and process separately. 150 for (size_t i = 0; i < packet_list->numPackets; i++) { 151 // Each packet contains MIDI data for one or more messages (like note-on). 152 const MIDIPacket &packet = packet_list->packet[i]; 153 double timestamp_seconds = MIDITimeStampToSeconds(packet.timeStamp); 154 155 ReceiveMidiData( 156 port_index, 157 packet.data, 158 packet.length, 159 timestamp_seconds); 160 } 161 } 162 163 void MidiManagerMac::SendMidiData(MidiManagerClient* client, 164 uint32 port_index, 165 const std::vector<uint8>& data, 166 double timestamp) { 167 DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); 168 169 // System Exclusive has already been filtered. 170 MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp); 171 172 midi_packet_ = MIDIPacketListAdd( 173 packet_list_, 174 kMaxPacketListSize, 175 midi_packet_, 176 coremidi_timestamp, 177 data.size(), 178 &data[0]); 179 180 // Lookup the destination based on the port index. 181 if (static_cast<size_t>(port_index) >= destinations_.size()) 182 return; 183 184 MIDIEndpointRef destination = destinations_[port_index]; 185 186 MIDISend(coremidi_output_, destination, packet_list_); 187 188 // Re-initialize for next time. 189 midi_packet_ = MIDIPacketListInit(packet_list_); 190 191 client->AccumulateMidiBytesSent(data.size()); 192 } 193 194 // static 195 MidiPortInfo MidiManagerMac::GetPortInfoFromEndpoint( 196 MIDIEndpointRef endpoint) { 197 SInt32 id_number = 0; 198 MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyUniqueID, &id_number); 199 string id = IntToString(id_number); 200 201 string manufacturer; 202 CFStringRef manufacturer_ref = NULL; 203 OSStatus result = MIDIObjectGetStringProperty( 204 endpoint, kMIDIPropertyManufacturer, &manufacturer_ref); 205 if (result == noErr) { 206 manufacturer = SysCFStringRefToUTF8(manufacturer_ref); 207 } else { 208 // kMIDIPropertyManufacturer is not supported in IAC driver providing 209 // endpoints, and the result will be kMIDIUnknownProperty (-10835). 210 DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status " 211 << result; 212 } 213 214 string name; 215 CFStringRef name_ref = NULL; 216 result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref); 217 if (result == noErr) 218 name = SysCFStringRefToUTF8(name_ref); 219 else 220 DLOG(WARNING) << "Failed to get kMIDIPropertyName with status " << result; 221 222 string version; 223 SInt32 version_number = 0; 224 result = MIDIObjectGetIntegerProperty( 225 endpoint, kMIDIPropertyDriverVersion, &version_number); 226 if (result == noErr) { 227 version = IntToString(version_number); 228 } else { 229 // kMIDIPropertyDriverVersion is not supported in IAC driver providing 230 // endpoints, and the result will be kMIDIUnknownProperty (-10835). 231 DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status " 232 << result; 233 } 234 235 return MidiPortInfo(id, manufacturer, name, version); 236 } 237 238 // static 239 double MidiManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) { 240 UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp); 241 return static_cast<double>(nanoseconds) / 1.0e9; 242 } 243 244 // static 245 MIDITimeStamp MidiManagerMac::SecondsToMIDITimeStamp(double seconds) { 246 UInt64 nanos = UInt64(seconds * 1.0e9); 247 return AudioConvertNanosToHostTime(nanos); 248 } 249 250 } // namespace media 251