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