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
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
39void 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
102void 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
116MidiManagerMac::~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
127void 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
141void 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
163void 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
195MidiPortInfo 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
239double MidiManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
240  UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
241  return static_cast<double>(nanoseconds) / 1.0e9;
242}
243
244// static
245MIDITimeStamp MidiManagerMac::SecondsToMIDITimeStamp(double seconds) {
246  UInt64 nanos = UInt64(seconds * 1.0e9);
247  return AudioConvertNanosToHostTime(nanos);
248}
249
250}  // namespace media
251