• 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/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 
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 
StartInitialization()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 
DispatchSendMidiData(MidiManagerClient * client,uint32 port_index,const std::vector<uint8> & data,double timestamp)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 
~MidiManagerMac()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
ReadMidiDispatch(const MIDIPacketList * packet_list,void * read_proc_refcon,void * src_conn_refcon)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 
ReadMidi(MIDIEndpointRef source,const MIDIPacketList * packet_list)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 
SendMidiData(MidiManagerClient * client,uint32 port_index,const std::vector<uint8> & data,double timestamp)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
GetPortInfoFromEndpoint(MIDIEndpointRef endpoint)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
MIDITimeStampToSeconds(MIDITimeStamp timestamp)239 double MidiManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
240   UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
241   return static_cast<double>(nanoseconds) / 1.0e9;
242 }
243 
244 // static
SecondsToMIDITimeStamp(double seconds)245 MIDITimeStamp MidiManagerMac::SecondsToMIDITimeStamp(double seconds) {
246   UInt64 nanos = UInt64(seconds * 1.0e9);
247   return AudioConvertNanosToHostTime(nanos);
248 }
249 
250 }  // namespace media
251