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 <iostream> 8 #include <string> 9 10 #include "base/debug/trace_event.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/sys_string_conversions.h" 13 #include <CoreAudio/HostTime.h> 14 15 using base::IntToString; 16 using base::SysCFStringRefToUTF8; 17 using std::string; 18 19 // NB: System MIDI types are pointer types in 32-bit and integer types in 20 // 64-bit. Therefore, the initialization is the simplest one that satisfies both 21 // (if possible). 22 23 namespace media { 24 25 MIDIManager* MIDIManager::Create() { 26 return new MIDIManagerMac(); 27 } 28 29 MIDIManagerMac::MIDIManagerMac() 30 : midi_client_(0), 31 coremidi_input_(0), 32 coremidi_output_(0), 33 packet_list_(NULL), 34 midi_packet_(NULL) { 35 } 36 37 bool MIDIManagerMac::Initialize() { 38 TRACE_EVENT0("midi", "MIDIManagerMac::Initialize"); 39 40 // CoreMIDI registration. 41 midi_client_ = 0; 42 OSStatus result = MIDIClientCreate( 43 CFSTR("Google Chrome"), 44 NULL, 45 NULL, 46 &midi_client_); 47 48 if (result != noErr) 49 return false; 50 51 coremidi_input_ = 0; 52 53 // Create input and output port. 54 result = MIDIInputPortCreate( 55 midi_client_, 56 CFSTR("MIDI Input"), 57 ReadMidiDispatch, 58 this, 59 &coremidi_input_); 60 if (result != noErr) 61 return false; 62 63 result = MIDIOutputPortCreate( 64 midi_client_, 65 CFSTR("MIDI Output"), 66 &coremidi_output_); 67 if (result != noErr) 68 return false; 69 70 int destination_count = MIDIGetNumberOfDestinations(); 71 destinations_.reserve(destination_count); 72 73 for (int i = 0; i < destination_count ; i++) { 74 MIDIEndpointRef destination = MIDIGetDestination(i); 75 76 // Keep track of all destinations (known as outputs by the Web MIDI API). 77 // Cache to avoid any possible overhead in calling MIDIGetDestination(). 78 destinations_[i] = destination; 79 80 MIDIPortInfo info = GetPortInfoFromEndpoint(destination); 81 AddOutputPort(info); 82 } 83 84 // Open connections from all sources. 85 int source_count = MIDIGetNumberOfSources(); 86 87 for (int i = 0; i < source_count; ++i) { 88 // Receive from all sources. 89 MIDIEndpointRef src = MIDIGetSource(i); 90 MIDIPortConnectSource(coremidi_input_, src, reinterpret_cast<void*>(src)); 91 92 // Keep track of all sources (known as inputs in Web MIDI API terminology). 93 source_map_[src] = i; 94 95 MIDIPortInfo info = GetPortInfoFromEndpoint(src); 96 AddInputPort(info); 97 } 98 99 // TODO(crogers): Fix the memory management here! 100 packet_list_ = reinterpret_cast<MIDIPacketList*>(midi_buffer_); 101 midi_packet_ = MIDIPacketListInit(packet_list_); 102 103 return true; 104 } 105 106 MIDIManagerMac::~MIDIManagerMac() { 107 if (coremidi_input_) 108 MIDIPortDispose(coremidi_input_); 109 if (coremidi_output_) 110 MIDIPortDispose(coremidi_output_); 111 } 112 113 void MIDIManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list, 114 void* read_proc_refcon, 115 void* src_conn_refcon) { 116 MIDIManagerMac* manager = static_cast<MIDIManagerMac*>(read_proc_refcon); 117 #if __LP64__ 118 MIDIEndpointRef source = reinterpret_cast<uintptr_t>(src_conn_refcon); 119 #else 120 MIDIEndpointRef source = static_cast<MIDIEndpointRef>(src_conn_refcon); 121 #endif 122 123 // Dispatch to class method. 124 manager->ReadMidi(source, packet_list); 125 } 126 127 void MIDIManagerMac::ReadMidi(MIDIEndpointRef source, 128 const MIDIPacketList* packet_list) { 129 // Lookup the port index based on the source. 130 SourceMap::iterator j = source_map_.find(source); 131 if (j == source_map_.end()) 132 return; 133 int port_index = source_map_[source]; 134 135 // Go through each packet and process separately. 136 for(size_t i = 0; i < packet_list->numPackets; i++) { 137 // Each packet contains MIDI data for one or more messages (like note-on). 138 const MIDIPacket &packet = packet_list->packet[i]; 139 double timestamp_seconds = MIDITimeStampToSeconds(packet.timeStamp); 140 141 ReceiveMIDIData( 142 port_index, 143 packet.data, 144 packet.length, 145 timestamp_seconds); 146 } 147 } 148 149 void MIDIManagerMac::SendMIDIData(MIDIManagerClient* client, 150 int port_index, 151 const uint8* data, 152 size_t length, 153 double timestamp) { 154 // System Exclusive has already been filtered. 155 MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp); 156 157 midi_packet_ = MIDIPacketListAdd( 158 packet_list_, 159 kMaxPacketListSize, 160 midi_packet_, 161 coremidi_timestamp, 162 length, 163 data); 164 165 // Lookup the destination based on the port index. 166 // TODO(crogers): re-factor |port_index| to use unsigned 167 // to avoid the need for this check. 168 if (port_index < 0 || 169 static_cast<size_t>(port_index) >= destinations_.size()) 170 return; 171 172 MIDIEndpointRef destination = destinations_[port_index]; 173 174 MIDISend(coremidi_output_, destination, packet_list_); 175 176 // Re-initialize for next time. 177 midi_packet_ = MIDIPacketListInit(packet_list_); 178 179 client->AccumulateMIDIBytesSent(length); 180 } 181 182 MIDIPortInfo MIDIManagerMac::GetPortInfoFromEndpoint( 183 MIDIEndpointRef endpoint) { 184 SInt32 id_number = 0; 185 MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyUniqueID, &id_number); 186 string id = IntToString(id_number); 187 188 CFStringRef manufacturer_ref = NULL; 189 MIDIObjectGetStringProperty( 190 endpoint, kMIDIPropertyManufacturer, &manufacturer_ref); 191 string manufacturer = SysCFStringRefToUTF8(manufacturer_ref); 192 193 CFStringRef name_ref = NULL; 194 MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref); 195 string name = SysCFStringRefToUTF8(name_ref); 196 197 SInt32 version_number = 0; 198 MIDIObjectGetIntegerProperty( 199 endpoint, kMIDIPropertyDriverVersion, &version_number); 200 string version = IntToString(version_number); 201 202 return MIDIPortInfo(id, manufacturer, name, version); 203 } 204 205 double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) { 206 UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp); 207 return static_cast<double>(nanoseconds) / 1.0e9; 208 } 209 210 MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) { 211 UInt64 nanos = UInt64(seconds * 1.0e9); 212 return AudioConvertNanosToHostTime(nanos); 213 } 214 215 } // namespace media 216