• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 
Create()26 MIDIManager* MIDIManager::Create() {
27   return new MIDIManagerMac();
28 }
29 
MIDIManagerMac()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 
Initialize()39 bool 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 
DispatchSendMIDIData(MIDIManagerClient * client,uint32 port_index,const std::vector<uint8> & data,double timestamp)108 void 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 
~MIDIManagerMac()122 MIDIManagerMac::~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
ReadMIDIDispatch(const MIDIPacketList * packet_list,void * read_proc_refcon,void * src_conn_refcon)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);
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 
ReadMIDI(MIDIEndpointRef source,const MIDIPacketList * 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())
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 
SendMIDIData(MIDIManagerClient * client,uint32 port_index,const std::vector<uint8> & data,double timestamp)169 void 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
GetPortInfoFromEndpoint(MIDIEndpointRef endpoint)201 MIDIPortInfo 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
MIDITimeStampToSeconds(MIDITimeStamp timestamp)245 double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
246   UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
247   return static_cast<double>(nanoseconds) / 1.0e9;
248 }
249 
250 // static
SecondsToMIDITimeStamp(double seconds)251 MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) {
252   UInt64 nanos = UInt64(seconds * 1.0e9);
253   return AudioConvertNanosToHostTime(nanos);
254 }
255 
256 }  // namespace media
257