From 3940e87eaee6fb59800cffba475a294e3fa5c72c Mon Sep 17 00:00:00 2001
From: "crogers@google.com"
 <crogers@google.com@0039d316-1c4b-4281-b951-d872f2087c98>
Date: Sat, 22 Jun 2013 00:55:52 +0000
Subject: Implement Web MIDI API back-end

This involves browser-side support and IPC for sending and receiving
MIDI messages.  Initially support for OSX is included.

BUG=163795
R=palmer@chromium.org, piman@chromium.org, scherkus@chromium.org

Review URL: https://codereview.chromium.org/16025005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@207983 0039d316-1c4b-4281-b951-d872f2087c98
---
 media/midi/midi_manager.cc     |  67 ++++++++++++++
 media/midi/midi_manager.h      | 109 ++++++++++++++++++++++
 media/midi/midi_manager_mac.cc | 205 +++++++++++++++++++++++++++++++++++++++++
 media/midi/midi_manager_mac.h  |  69 ++++++++++++++
 media/midi/midi_port_info.cc   |  32 +++++++
 media/midi/midi_port_info.h    |  36 ++++++++
 6 files changed, 518 insertions(+)
 create mode 100644 media/midi/midi_manager.cc
 create mode 100644 media/midi/midi_manager.h
 create mode 100644 media/midi/midi_manager_mac.cc
 create mode 100644 media/midi/midi_manager_mac.h
 create mode 100644 media/midi/midi_port_info.cc
 create mode 100644 media/midi/midi_port_info.h

(limited to 'media/midi')

diff --git a/media/midi/midi_manager.cc b/media/midi/midi_manager.cc
new file mode 100644
index 0000000..f991865
--- /dev/null
+++ b/media/midi/midi_manager.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/midi/midi_manager.h"
+
+namespace media {
+
+#if !defined(OS_MACOSX)
+// TODO(crogers): implement MIDIManager for other platforms.
+MIDIManager* MIDIManager::Create() {
+  return NULL;
+}
+#endif
+
+MIDIManager::MIDIManager()
+    : initialized_(false) {
+}
+
+MIDIManager::~MIDIManager() {}
+
+bool MIDIManager::RequestAccess(MIDIManagerClient* client, int access) {
+  // TODO(crogers): determine if user prompt is necessary here.
+  // For now, simply don't allow sysex.
+  if (access != kNoSystemExclusive)
+    return false;
+
+  // Lazily initialize the MIDI back-end.
+  if (!initialized_)
+    initialized_ = Initialize();
+
+  if (initialized_) {
+    base::AutoLock auto_lock(clients_lock_);
+    clients_.insert(client);
+  }
+
+  return initialized_;
+}
+
+void MIDIManager::ReleaseAccess(MIDIManagerClient* client) {
+  base::AutoLock auto_lock(clients_lock_);
+  ClientList::iterator i = clients_.find(client);
+  if (i != clients_.end())
+    clients_.erase(i);
+}
+
+void MIDIManager::AddInputPort(const MIDIPortInfo& info) {
+  input_ports_.push_back(info);
+}
+
+void MIDIManager::AddOutputPort(const MIDIPortInfo& info) {
+  output_ports_.push_back(info);
+}
+
+void MIDIManager::ReceiveMIDIData(
+    int port_index,
+    const uint8* data,
+    size_t length,
+    double timestamp) {
+  base::AutoLock auto_lock(clients_lock_);
+
+  // TODO(crogers): Filter out sysex.
+  for (ClientList::iterator i = clients_.begin(); i != clients_.end(); ++i)
+    (*i)->ReceiveMIDIData(port_index, data, length, timestamp);
+};
+
+}  // namespace media
diff --git a/media/midi/midi_manager.h b/media/midi/midi_manager.h
new file mode 100644
index 0000000..1df444f
--- /dev/null
+++ b/media/midi/midi_manager.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MIDI_MIDI_MANAGER_H_
+#define MEDIA_MIDI_MIDI_MANAGER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/synchronization/lock.h"
+#include "media/base/media_export.h"
+#include "media/midi/midi_port_info.h"
+
+namespace media {
+
+// A MIDIManagerClient registers with the MIDIManager to receive MIDI data.
+// See MIDIManager::RequestAccess() and MIDIManager::ReleaseAccess()
+// for details.
+class MEDIA_EXPORT MIDIManagerClient {
+ public:
+   virtual ~MIDIManagerClient() {}
+
+  // ReceiveMIDIData() is called when MIDI data has been received from the
+  // MIDI system.
+  // |port_index| represents the specific input port from input_ports().
+  // |data| represents a series of bytes encoding one or more MIDI messages.
+  // |length| is the number of bytes in |data|.
+  // |timestamp| is the time the data was received, in seconds.
+  virtual void ReceiveMIDIData(int port_index,
+                               const uint8* data,
+                               size_t length,
+                               double timestamp) = 0;
+};
+
+// Manages access to all MIDI hardware.
+class MEDIA_EXPORT MIDIManager {
+ public:
+  enum AccessType {
+    kNoSystemExclusive,
+    kSystemExclusive
+  };
+
+  static MIDIManager* Create();
+
+  MIDIManager();
+  virtual ~MIDIManager();
+
+  // A client calls RequestAccess() to receive and send MIDI data.
+  // If access is approved, the MIDI system is lazily initialized
+  // and the client is registered to receive MIDI data.
+  // Returns |true| if access is approved.
+  bool RequestAccess(MIDIManagerClient* client, int access);
+
+  // A client calls ReleaseAccess() to stop receiving MIDI data.
+  void ReleaseAccess(MIDIManagerClient* client);
+
+  // SendMIDIData() sends one or more messages at the given time.
+  // |port_index| represents the specific output port from output_ports().
+  // |data| represents a series of bytes encoding one or more MIDI messages.
+  // |length| is the number of bytes in |data|.
+  // |timestamp| is the time to send the data, in seconds.
+  virtual void SendMIDIData(int port_index,
+                            const uint8* data,
+                            size_t length,
+                            double timestamp) = 0;
+
+  // input_ports() is a list of MIDI ports for receiving MIDI data.
+  // Each individual port in this list can be identified by its
+  // integer index into this list.
+  const MIDIPortInfoList& input_ports() { return input_ports_; }
+
+  // output_ports() is a list of MIDI ports for sending MIDI data.
+  // Each individual port in this list can be identified by its
+  // integer index into this list.
+  const MIDIPortInfoList& output_ports() { return output_ports_; }
+
+ protected:
+  // Initializes the MIDI system, returning |true| on success.
+  virtual bool Initialize() = 0;
+
+  void AddInputPort(const MIDIPortInfo& info);
+  void AddOutputPort(const MIDIPortInfo& info);
+
+  // Dispatches to all clients.
+  void ReceiveMIDIData(
+      int port_index,
+      const uint8* data,
+      size_t length,
+      double timestamp);
+
+  bool initialized_;
+
+  // Keeps track of all clients who wish to receive MIDI data.
+  typedef std::set<MIDIManagerClient*> ClientList;
+  ClientList clients_;
+
+  // Protects access to our clients.
+  base::Lock clients_lock_;
+
+  MIDIPortInfoList input_ports_;
+  MIDIPortInfoList output_ports_;
+
+  DISALLOW_COPY_AND_ASSIGN(MIDIManager);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_MIDI_MIDI_MANAGER_H_
diff --git a/media/midi/midi_manager_mac.cc b/media/midi/midi_manager_mac.cc
new file mode 100644
index 0000000..73df1fe
--- /dev/null
+++ b/media/midi/midi_manager_mac.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/midi/midi_manager_mac.h"
+
+#include <iostream>
+#include <string>
+
+#include "base/debug/trace_event.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/sys_string_conversions.h"
+#include <CoreAudio/HostTime.h>
+
+using base::IntToString;
+using base::SysCFStringRefToUTF8;
+using std::string;
+
+namespace media {
+
+MIDIManager* MIDIManager::Create() {
+  return new MIDIManagerMac();
+}
+
+MIDIManagerMac::MIDIManagerMac()
+    : midi_client_(NULL),
+      coremidi_input_(NULL),
+      coremidi_output_(NULL),
+      packet_list_(NULL),
+      midi_packet_(NULL) {
+}
+
+bool MIDIManagerMac::Initialize() {
+  TRACE_EVENT0("midi", "MIDIManagerMac::Initialize");
+
+  // CoreMIDI registration.
+  midi_client_ = NULL;
+  OSStatus result = MIDIClientCreate(
+      CFSTR("Google Chrome"),
+      NULL,
+      NULL,
+      &midi_client_);
+
+  if (result != noErr)
+    return false;
+
+  coremidi_input_ = NULL;
+
+  // Create input and output port.
+  result = MIDIInputPortCreate(
+      midi_client_,
+      CFSTR("MIDI Input"),
+      ReadMidiDispatch,
+      this,
+      &coremidi_input_);
+  if (result != noErr)
+    return false;
+
+  result = MIDIOutputPortCreate(
+      midi_client_,
+      CFSTR("MIDI Output"),
+      &coremidi_output_);
+  if (result != noErr)
+    return false;
+
+  int destination_count = MIDIGetNumberOfDestinations();
+  destinations_.reserve(destination_count);
+
+  for (int i = 0; i < destination_count ; i++) {
+    MIDIEndpointRef destination = MIDIGetDestination(i);
+
+    // Keep track of all destinations (known as outputs by the Web MIDI API).
+    // Cache to avoid any possible overhead in calling MIDIGetDestination().
+    destinations_[i] = destination;
+
+    MIDIPortInfo info = GetPortInfoFromEndpoint(destination);
+    AddOutputPort(info);
+  }
+
+  // Open connections from all sources.
+  int source_count = MIDIGetNumberOfSources();
+
+  for (int i = 0; i < source_count; ++i)  {
+    // Receive from all sources.
+    MIDIEndpointRef src = MIDIGetSource(i);
+    MIDIPortConnectSource(coremidi_input_, src, src);
+
+    // Keep track of all sources (known as inputs in Web MIDI API terminology).
+    source_map_[src] = i;
+
+    MIDIPortInfo info = GetPortInfoFromEndpoint(src);
+    AddInputPort(info);
+  }
+
+  // TODO(crogers): Fix the memory management here!
+  packet_list_ = reinterpret_cast<MIDIPacketList*>(midi_buffer_);
+  midi_packet_ = MIDIPacketListInit(packet_list_);
+
+  return true;
+}
+
+MIDIManagerMac::~MIDIManagerMac() {
+  if (coremidi_input_)
+    MIDIPortDispose(coremidi_input_);
+  if (coremidi_output_)
+    MIDIPortDispose(coremidi_output_);
+}
+
+void MIDIManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list,
+                                      void* read_proc_refcon,
+                                      void* src_conn_refcon) {
+  MIDIManagerMac* manager = static_cast<MIDIManagerMac*>(read_proc_refcon);
+  MIDIEndpointRef source = static_cast<MIDIEndpointRef>(src_conn_refcon);
+
+  // Dispatch to class method.
+  manager->ReadMidi(source, packet_list);
+}
+
+void MIDIManagerMac::ReadMidi(MIDIEndpointRef source,
+                              const MIDIPacketList* packet_list) {
+  // Lookup the port index based on the source.
+  SourceMap::iterator j = source_map_.find(source);
+  if (j == source_map_.end())
+    return;
+  int port_index = source_map_[source];
+
+  // Go through each packet and process separately.
+  for(size_t i = 0; i < packet_list->numPackets; i++) {
+    // Each packet contains MIDI data for one or more messages (like note-on).
+    const MIDIPacket &packet = packet_list->packet[i];
+    double timestamp_seconds = MIDITimeStampToSeconds(packet.timeStamp);
+
+    ReceiveMIDIData(
+        port_index,
+        packet.data,
+        packet.length,
+        timestamp_seconds);
+  }
+}
+
+void MIDIManagerMac::SendMIDIData(int port_index,
+                                  const uint8* data,
+                                  size_t length,
+                                  double timestamp) {
+  // TODO(crogers): Filter out sysex.
+
+  MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp);
+
+  midi_packet_ = MIDIPacketListAdd(
+      packet_list_,
+      kMaxPacketListSize,
+      midi_packet_,
+      coremidi_timestamp,
+      length,
+      data);
+
+  // Lookup the destination based on the port index.
+  // TODO(crogers): re-factor |port_index| to use unsigned
+  // to avoid the need for this check.
+  if (port_index < 0 ||
+      static_cast<size_t>(port_index) >= destinations_.size())
+    return;
+
+  MIDIEndpointRef destination = destinations_[port_index];
+
+  MIDISend(coremidi_output_, destination, packet_list_);
+
+  // Re-initialize for next time.
+  midi_packet_ = MIDIPacketListInit(packet_list_);
+}
+
+MIDIPortInfo MIDIManagerMac::GetPortInfoFromEndpoint(
+    MIDIEndpointRef endpoint) {
+  SInt32 id_number = 0;
+  MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyUniqueID, &id_number);
+  string id = IntToString(id_number);
+
+  CFStringRef manufacturer_ref = NULL;
+  MIDIObjectGetStringProperty(
+      endpoint, kMIDIPropertyManufacturer, &manufacturer_ref);
+  string manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
+
+  CFStringRef name_ref = NULL;
+  MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref);
+  string name = SysCFStringRefToUTF8(name_ref);
+
+  SInt32 version_number = 0;
+  MIDIObjectGetIntegerProperty(
+      endpoint, kMIDIPropertyDriverVersion, &version_number);
+  string version = IntToString(version_number);
+
+  return MIDIPortInfo(id, manufacturer, name, version);
+}
+
+double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
+  UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
+  return static_cast<double>(nanoseconds) / 1.0e9;
+}
+
+MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) {
+  UInt64 nanos = UInt64(seconds * 1.0e9);
+  return AudioConvertNanosToHostTime(nanos);
+}
+
+}  // namespace media
diff --git a/media/midi/midi_manager_mac.h b/media/midi/midi_manager_mac.h
new file mode 100644
index 0000000..f513e11
--- /dev/null
+++ b/media/midi/midi_manager_mac.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MIDI_MIDI_MANAGER_MAC_H_
+#define MEDIA_MIDI_MIDI_MANAGER_MAC_H_
+
+#include <CoreMIDI/MIDIServices.h>
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/midi/midi_manager.h"
+#include "media/midi/midi_port_info.h"
+
+namespace media {
+
+class MEDIA_EXPORT MIDIManagerMac : public MIDIManager {
+ public:
+  MIDIManagerMac();
+  virtual ~MIDIManagerMac();
+
+  // MIDIManager implementation.
+  virtual bool Initialize() OVERRIDE;
+  virtual void SendMIDIData(int port_index,
+                            const uint8* data,
+                            size_t length,
+                            double timestamp) OVERRIDE;
+
+ private:
+  // CoreMIDI callback for MIDI data.
+  // Each callback can contain multiple packets, each of which can contain
+  // multiple MIDI messages.
+  static void ReadMidiDispatch(
+      const MIDIPacketList *pktlist,
+      void *read_proc_refcon,
+      void *src_conn_refcon);
+  virtual void ReadMidi(MIDIEndpointRef source, const MIDIPacketList *pktlist);
+
+  // Helper
+  static media::MIDIPortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint);
+  static double MIDITimeStampToSeconds(MIDITimeStamp timestamp);
+  static MIDITimeStamp SecondsToMIDITimeStamp(double seconds);
+
+  // CoreMIDI
+  MIDIClientRef midi_client_;
+  MIDIPortRef coremidi_input_;
+  MIDIPortRef coremidi_output_;
+
+  enum{ kMaxPacketListSize = 512 };
+  char midi_buffer_[kMaxPacketListSize];
+  MIDIPacketList* packet_list_;
+  MIDIPacket* midi_packet_;
+
+  typedef std::map<MIDIEndpointRef, int> SourceMap;
+
+  // Keeps track of the index (0-based) for each of our sources.
+  SourceMap source_map_;
+
+  // Keeps track of all destinations.
+  std::vector<MIDIEndpointRef> destinations_;
+
+  DISALLOW_COPY_AND_ASSIGN(MIDIManagerMac);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_MIDI_MIDI_MANAGER_MAC_H_
diff --git a/media/midi/midi_port_info.cc b/media/midi/midi_port_info.cc
new file mode 100644
index 0000000..37d077d
--- /dev/null
+++ b/media/midi/midi_port_info.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/midi/midi_port_info.h"
+
+#include <iostream>
+
+using std::cout;
+
+namespace media {
+
+MIDIPortInfo::MIDIPortInfo() {}
+
+MIDIPortInfo::MIDIPortInfo(const std::string& in_id,
+                           const std::string& in_manufacturer,
+                           const std::string& in_name,
+                           const std::string& in_version)
+    : id(in_id),
+      manufacturer(in_manufacturer),
+      name(in_name),
+      version(in_version) {}
+
+MIDIPortInfo::~MIDIPortInfo() {}
+
+MIDIPortInfo::MIDIPortInfo(const MIDIPortInfo& info)
+    : id(info.id),
+      manufacturer(info.manufacturer),
+      name(info.name),
+      version(info.version) {}
+
+}  // namespace media
diff --git a/media/midi/midi_port_info.h b/media/midi/midi_port_info.h
new file mode 100644
index 0000000..f4afb49
--- /dev/null
+++ b/media/midi/midi_port_info.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_MIDI_MIDI_PORT_INFO_H_
+#define MEDIA_MIDI_MIDI_PORT_INFO_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+struct MEDIA_EXPORT MIDIPortInfo {
+  MIDIPortInfo();
+  MIDIPortInfo(const std::string& in_id,
+               const std::string& in_manufacturer,
+               const std::string& in_name,
+               const std::string& in_version);
+
+  MIDIPortInfo(const MIDIPortInfo& info);
+  ~MIDIPortInfo();
+
+  std::string id;
+  std::string manufacturer;
+  std::string name;
+  std::string version;
+};
+
+typedef std::vector<MIDIPortInfo> MIDIPortInfoList;
+
+}  // namespace media
+
+#endif  // MEDIA_MIDI_MIDI_PORT_INFO_H_
-- 
cgit v1.1