Skip to content

Instantly share code, notes, and snippets.

@atsushieno
Last active December 26, 2015 20:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atsushieno/7211044 to your computer and use it in GitHub Desktop.
Save atsushieno/7211044 to your computer and use it in GitHub Desktop.
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 600ad7b..7550de6 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1666,7 +1666,7 @@ const Experiment kExperiments[] = {
"enable-web-midi",
IDS_FLAGS_ENABLE_WEB_MIDI_NAME,
IDS_FLAGS_ENABLE_WEB_MIDI_DESCRIPTION,
- kOsMac,
+ kOsMac | kOsLinux,
SINGLE_VALUE_TYPE(switches::kEnableWebMIDI)
},
{
diff --git a/media/media.gyp b/media/media.gyp
index ee42d7c..e91db53 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -553,6 +553,30 @@
'-lasound',
],
},
+ 'defines': [
+ 'PMALSA'
+ ],
+ 'include_dirs': [
+ '../third_party/portmidi/pm_common',
+ '../third_party/portmidi/porttime',
+ ],
+ 'sources': [
+ 'midi/midi_manager_linux.cc',
+ 'midi/midi_manager_linux.h',
+ '../third_party/portmidi/pm_common/pminternal.h',
+ '../third_party/portmidi/pm_common/pmutil.c',
+ '../third_party/portmidi/pm_common/pmutil.h',
+ '../third_party/portmidi/pm_common/portmidi.c',
+ '../third_party/portmidi/pm_common/portmidi.h',
+ '../third_party/portmidi/pm_linux/finddefault.c',
+ '../third_party/portmidi/pm_linux/pmlinux.c',
+ '../third_party/portmidi/pm_linux/pmlinux.h',
+ '../third_party/portmidi/pm_linux/pmlinuxalsa.c',
+ '../third_party/portmidi/pm_linux/pmlinuxalsa.h',
+ '../third_party/portmidi/porttime/porttime.c',
+ '../third_party/portmidi/porttime/porttime.h',
+ '../third_party/portmidi/porttime/ptlinux.c',
+ ],
}, { # use_alsa==0
'sources/': [ ['exclude', '/alsa_'],
['exclude', '/audio_manager_linux'] ],
diff --git a/media/midi/midi_manager.cc b/media/midi/midi_manager.cc
index b3262e4..a64571f 100644
--- a/media/midi/midi_manager.cc
+++ b/media/midi/midi_manager.cc
@@ -11,7 +11,7 @@
namespace media {
-#if !defined(OS_MACOSX)
+#if !defined(OS_MACOSX) && !defined(OS_LINUX)
// TODO(crogers): implement MIDIManager for other platforms.
MIDIManager* MIDIManager::Create() {
return NULL;
diff --git a/media/midi/midi_manager_linux.cc b/media/midi/midi_manager_linux.cc
new file mode 100644
index 0000000..2077ee7
--- /dev/null
+++ b/media/midi/midi_manager_linux.cc
@@ -0,0 +1,287 @@
+// 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_linux.h"
+
+#include <string>
+
+#include "base/debug/trace_event.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/sys_string_conversions.h"
+
+// NB: System MIDI types are pointer types in 32-bit and integer types in
+// 64-bit. Therefore, the initialization is the simplest one that satisfies both
+// (if possible).
+
+namespace media {
+
+PortMidiDeviceMap::PortMidiDeviceMap(PmDeviceID pmIndex, int mmIndex, bool isOutput)
+ : portmidi_index (pmIndex),
+ midi_manager_index (mmIndex),
+ is_midi_manager_index_output (isOutput)
+{
+}
+
+MIDIManager* MIDIManager::Create() {
+ return new MIDIManagerLinux();
+}
+
+MIDIManagerLinux::MIDIManagerLinux()
+ : cached_out_port_index(-1)
+{
+}
+
+bool MIDIManagerLinux::Initialize() {
+ TRACE_EVENT0("midi", "MIDIManagerLinux::Initialize");
+
+ Pm_Initialize();
+
+ SetupPollingThread();
+
+ in_streams = new std::map<int,PortMidiStream*>();
+ out_streams = new std::map<int,PortMidiStream*>();
+
+ int nDevices = Pm_CountDevices();
+ device_map = new std::vector<PortMidiDeviceMap*>();
+ int nInputs = 0, nOutputs = 0;
+ for (int i = 0; i < nDevices; i++) {
+ const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo (i); // device info memory is managed by portmidi, so do not release.
+ if (deviceInfo->input) {
+ PortMidiStream *stream;
+ PmError e = Pm_OpenInput (&stream, (PmDeviceID) i, NULL, portmidi_input_buffer_size, NULL, NULL);
+ if (e != pmNoError)
+ continue; // failed to open this device/port, skip it.
+ in_streams->insert(std::pair<int,PortMidiStream*> (i, stream));
+ AddInputPort(MIDIPortInfo("input", "-", deviceInfo->name, "-"));
+ device_map->push_back(new PortMidiDeviceMap((PmDeviceID) i, nInputs++, false));
+ }
+ if (deviceInfo->output) {
+ AddOutputPort(MIDIPortInfo("output", "-", deviceInfo->name, "-"));
+ device_map->push_back(new PortMidiDeviceMap((PmDeviceID) i, nOutputs++, true));
+ }
+ }
+
+ return true;
+}
+
+MIDIManagerLinux::~MIDIManagerLinux() {
+
+ midi_in_read_working = false;
+
+ for (std::map<int,PortMidiStream*>::iterator iter = in_streams->begin();
+ iter != in_streams->end(); iter++)
+ if (iter->second)
+ Pm_Close(iter->second);
+ for (std::map<int,PortMidiStream*>::iterator iter = out_streams->begin();
+ iter != out_streams->end(); iter++)
+ if (iter->second)
+ Pm_Close(iter->second);
+ delete in_streams;
+ delete out_streams;
+
+ int size = device_map->size();
+ for (int i = 0; i < size; i++)
+ delete device_map->at(i);
+ delete device_map;
+
+ Pm_Terminate();
+}
+
+
+void MIDIManagerLinux::SendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) {
+ DCHECK(CurrentlyOnMIDISendThread());
+
+ PmDeviceID pmDevice = GetPortmidiDeviceIndex(true, port_index);
+ if (pmDevice == pmNoDevice)
+ return; // invalid device ID
+
+ PortMidiStream *out;
+
+ if (port_index != cached_out_port_index) {
+ std::map<int,PortMidiStream*>::iterator iter = out_streams->find(port_index);
+ if (iter == out_streams->end()) {
+ PmError e = Pm_OpenOutput(&out, pmDevice, NULL, output_buffer_size, NULL, NULL, output_latency);
+ if (e != pmNoError)
+ return; // cannot open specified device
+ out_streams->insert(std::pair<int,PortMidiStream*> (port_index, out));
+ }
+ else
+ out = iter->second;
+ cached_out_port_index = port_index;
+ cached_out = out;
+ }
+ else
+ out = cached_out;
+
+ // FIXME:
+ // it is not very efficient. Add custom function that accepts char*.
+ //
+ // PortMIDI API is also looking *terrible*, by that it only either
+ // accepts 3-bytes-aligned messages or explicit SysEx sequence.
+ // It actually has some internal sysex state check (and hopefully not
+ // buggy!) so optimistically we can assume that it handles messages
+ // as expected in this form.
+ //
+ // IF a message is incompletely sent from the caller, there is
+ // (currently) no way to handle remaining bytes accordingly.
+ // I hope (and assume) that wouldn't happen by any sane MIDI output
+ // messaging by host.
+ int size = data.size();
+ PmTimestamp pts = (PmTimestamp) (timestamp / 1000.0);
+ for (int pos = 0; pos + 2 < size; pos += 3) {
+ long msg = Pm_Message(data[pos], data[pos + 1], data[pos + 2]);
+ Pm_WriteShort(out, pts, msg);
+ }
+}
+
+PmDeviceID MIDIManagerLinux::GetPortmidiDeviceIndex(bool isOutput, int midiManagerIndex)
+{
+ int size = device_map->size();
+ for (int i = 0; i < size; i++) {
+ PortMidiDeviceMap *map = device_map->at(i);
+ if (map->is_midi_manager_index_output == isOutput && map->midi_manager_index == midiManagerIndex)
+ return map->portmidi_index;
+ }
+ return pmNoDevice;
+}
+
+// This code is mostly custom port of porttime (from portmidi)
+void MIDIManagerLinux::MidiInReadCallback()
+{
+ for (std::map<PmDeviceID,PortMidiStream*>::iterator iter = in_streams->begin();
+ iter != in_streams->end(); iter++) {
+ while (Pm_Poll(iter->second) == TRUE) {
+ int nEvents = Pm_Read(iter->second, portmidi_in_buffer, midi_in_event_buffer_size);
+ PmTimestamp timestamp_cache = (PmTimestamp) 0;
+ if (nEvents == pmBufferOverflow)
+ // event buffer overflow, process as much message as possible (seealso portmidi.h)
+ nEvents = midi_in_event_buffer_size;
+ else if (nEvents < 0)
+ continue; // read error, skip this port
+
+ int pos = 0;
+ for (int i = 0; i < nEvents && pos < portmidi_input_buffer_size; i++) {
+ PmEvent e = portmidi_in_buffer[i];
+ if (e.timestamp != timestamp_cache) {
+ if (pos > 0)
+ ProcessReceivedMidiMessage(iter->first, pos, timestamp_cache);
+ pos = 0;
+ timestamp_cache = e.timestamp;
+ }
+ if (Pm_MessageStatus(e.message) != 0xFF) {
+ if (pos + 3 >= portmidi_input_buffer_size) {
+ ProcessReceivedMidiMessage(iter->first, pos, timestamp_cache);
+ pos = 0;
+ }
+ manager_midi_in_buffer[pos++] = Pm_MessageStatus(e.message);
+ manager_midi_in_buffer[pos++] = Pm_MessageData1(e.message);
+ manager_midi_in_buffer[pos++] = Pm_MessageData2(e.message);
+ } else {
+ // EOX might appear at any part of PmMessage...
+ int eoxPos = -1;
+ uint8 c;
+ for (int j = 0; j < nEvents; j++) {
+ if (pos == portmidi_input_buffer_size) {
+ eoxPos = -2;
+ break;
+ }
+ c = Pm_MessageStatus(portmidi_in_buffer[j].message);
+ if (c == 0xF7)
+ eoxPos = 0;
+ else {
+ manager_midi_in_buffer[pos++] = c;
+ if (pos == portmidi_input_buffer_size) {
+ eoxPos = -2;
+ break;
+ }
+ c = Pm_MessageData1(portmidi_in_buffer[j].message);
+ if (c == 0xF7)
+ eoxPos = 1;
+ else {
+ manager_midi_in_buffer[pos++] = c;
+ if (pos == portmidi_input_buffer_size) {
+ eoxPos = -2;
+ break;
+ }
+ c = Pm_MessageData2(portmidi_in_buffer[j].message);
+ if (c == 0xF7)
+ eoxPos = 2;
+ else
+ manager_midi_in_buffer [pos++] = c;
+ }
+ }
+ if (eoxPos == -2) {
+ ProcessReceivedMidiMessage(iter->first, pos, timestamp_cache);
+ pos = 0;
+ }
+ }
+ if (eoxPos >= 0)
+ manager_midi_in_buffer [pos++] = 0xF7;
+ }
+ }
+ if (pos > 0)
+ ProcessReceivedMidiMessage(iter->first, pos, timestamp_cache);
+ }
+ }
+}
+
+void MIDIManagerLinux::ProcessReceivedMidiMessage (int midiInIndex, int length, long timestamp)
+{
+ // FIXME: make use of timestamps, if applicable.
+ ReceiveMIDIData(midiInIndex, manager_midi_in_buffer, length, 0.0);
+}
+
+void *ReadPollLoopGlobal(void *ptr)
+{
+ ((MIDIManagerLinux*) ptr)->ReadPollLoop();
+ return NULL;
+}
+
+void MIDIManagerLinux::ReadPollLoop()
+{
+ midi_in_read_working = true;
+ int mytime = 1;
+
+ ftime(&read_timer_since);
+
+ while (midi_in_read_working) {
+ struct timeval timeout;
+ int delay = mytime++ * midi_in_timer_resolution - GetTimestamp(read_timer_since);
+ if (delay < 0) delay = 0;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = delay * 1000;
+ select(0, NULL, NULL, NULL, &timeout);
+ if (midi_in_read_working)
+ MidiInReadCallback();
+ }
+
+ pthread_exit(NULL);
+}
+
+long MIDIManagerLinux::GetTimestamp(struct timeb comparedTime)
+{
+ long seconds, milliseconds;
+ struct timeb now;
+ ftime(&now);
+ seconds = now.time - comparedTime.time;
+ milliseconds = now.millitm - comparedTime.millitm;
+ return seconds * 1000 + milliseconds;
+}
+
+
+int MIDIManagerLinux::SetupPollingThread()
+{
+ int ret = pthread_create(&read_poll_thread, NULL, ReadPollLoopGlobal, (void*) this);
+ if (ret) // error on creating polling thread.
+ return ret;
+
+
+
+ return ret;
+}
+
+} // namespace media
diff --git a/media/midi/midi_manager_linux.h b/media/midi/midi_manager_linux.h
new file mode 100644
index 0000000..62b9b21
--- /dev/null
+++ b/media/midi/midi_manager_linux.h
@@ -0,0 +1,78 @@
+// 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_LINUX_H_
+#define MEDIA_MIDI_MIDI_MANAGER_LINUX_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "media/midi/midi_manager.h"
+#include "media/midi/midi_port_info.h"
+
+#include "sys/timeb.h"
+#include "pthread.h"
+
+#include "media/midi/portmidi.h"
+#include "media/midi/porttime.h"
+
+namespace media {
+
+class PortMidiDeviceMap
+{
+ public:
+ PortMidiDeviceMap(PmDeviceID pmIndex, int mmIndex, bool isOutput);
+ PmDeviceID portmidi_index;
+ int midi_manager_index;
+ bool is_midi_manager_index_output;
+};
+
+class MEDIA_EXPORT MIDIManagerLinux : public MIDIManager {
+ public:
+ MIDIManagerLinux();
+ virtual ~MIDIManagerLinux();
+
+ // MIDIManager implementation.
+ virtual bool Initialize() OVERRIDE;
+ virtual void SendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) OVERRIDE;
+
+ void ReadPollLoop();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MIDIManagerLinux);
+
+ static const int output_buffer_size = 0x1000;
+ static const int output_latency = 1000;
+
+ std::vector<PortMidiDeviceMap*> *device_map;
+ std::map<int,PortMidiStream*> *in_streams;
+ std::map<int,PortMidiStream*> *out_streams;
+ uint32 cached_out_port_index;
+ PortMidiStream* cached_out;
+ PmDeviceID GetPortmidiDeviceIndex(bool isOutput, int midiManagerIndex);
+
+ void MidiInReadCallback();
+ static const int portmidi_input_buffer_size = 0x1024;
+ uint8 manager_midi_in_buffer[portmidi_input_buffer_size];
+ static const int midi_in_event_buffer_size = 0x400;
+ PmEvent portmidi_in_buffer[midi_in_event_buffer_size];
+ bool midi_in_read_working;
+ pthread_t read_poll_thread;
+ static const int midi_in_timer_resolution = 50;
+ struct timeb read_timer_since;
+ int SetupPollingThread();
+ long GetTimestamp(struct timeb comparedTime);
+ void ProcessReceivedMidiMessage (int midiInIndex, int length, long timestamp);
+
+};
+
+} // namespace media
+
+#endif // MEDIA_MIDI_MIDI_MANAGER_LINUX_H_
@atsushieno
Copy link
Author

Patch::

chromium/src$ cd third_party && git clone https://github.com:atsushieno/portmidi.git

chromium/src$ cd patch -p1 this.patch

Build::

chromium$ cat buildrc
export PATH=pwd/depot_tools:$PATH

chromium$ cat chromium.gyp_env
{
'GYP_DEFINES': 'component=shared_library disable_nacl=1 disable_pnacl=1',
'GYP_GENERATORS': 'ninja',
}

chromium$ . buildrc

chromium/src$ git runhooks

Run::

chromium/src$ out/Debug/chrome --disable-setuid-sandbox --enable-webmidi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment