Home | History | Annotate | Download | only in midi
      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 
     26 MIDIManager* MIDIManager::Create() {
     27   return new MIDIManagerMac();
     28 }
     29 
     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 
     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 
    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 
    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
    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 
    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 
    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
    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
    245 double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
    246   UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
    247   return static_cast<double>(nanoseconds) / 1.0e9;
    248 }
    249 
    250 // static
    251 MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) {
    252   UInt64 nanos = UInt64(seconds * 1.0e9);
    253   return AudioConvertNanosToHostTime(nanos);
    254 }
    255 
    256 }  // namespace media
    257