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