diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/media/midi | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/media/midi')
-rw-r--r-- | chromium/media/midi/midi_manager.cc | 28 | ||||
-rw-r--r-- | chromium/media/midi/midi_manager.h | 34 | ||||
-rw-r--r-- | chromium/media/midi/midi_manager_mac.cc | 63 | ||||
-rw-r--r-- | chromium/media/midi/midi_manager_mac.h | 18 | ||||
-rw-r--r-- | chromium/media/midi/midi_manager_win.cc | 597 | ||||
-rw-r--r-- | chromium/media/midi/midi_manager_win.h | 40 | ||||
-rw-r--r-- | chromium/media/midi/midi_message_queue.cc | 119 | ||||
-rw-r--r-- | chromium/media/midi/midi_message_queue.h | 72 | ||||
-rw-r--r-- | chromium/media/midi/midi_message_queue_unittest.cc | 173 | ||||
-rw-r--r-- | chromium/media/midi/midi_message_util.cc | 34 | ||||
-rw-r--r-- | chromium/media/midi/midi_message_util.h | 25 | ||||
-rw-r--r-- | chromium/media/midi/midi_message_util_unittest.cc | 34 |
12 files changed, 1171 insertions, 66 deletions
diff --git a/chromium/media/midi/midi_manager.cc b/chromium/media/midi/midi_manager.cc index b3262e4a034..6d3f1d30b95 100644 --- a/chromium/media/midi/midi_manager.cc +++ b/chromium/media/midi/midi_manager.cc @@ -6,12 +6,10 @@ #include "base/bind.h" #include "base/bind_helpers.h" -#include "base/message_loop/message_loop.h" -#include "base/threading/thread.h" namespace media { -#if !defined(OS_MACOSX) +#if !defined(OS_MACOSX) && !defined(OS_WIN) // TODO(crogers): implement MIDIManager for other platforms. MIDIManager* MIDIManager::Create() { return NULL; @@ -22,7 +20,8 @@ MIDIManager::MIDIManager() : initialized_(false) { } -MIDIManager::~MIDIManager() {} +MIDIManager::~MIDIManager() { +} bool MIDIManager::StartSession(MIDIManagerClient* client) { // Lazily initialize the MIDI back-end. @@ -63,25 +62,4 @@ void MIDIManager::ReceiveMIDIData( (*i)->ReceiveMIDIData(port_index, data, length, timestamp); } -bool MIDIManager::CurrentlyOnMIDISendThread() { - return send_thread_->message_loop() == base::MessageLoop::current(); -} - -void MIDIManager::DispatchSendMIDIData(MIDIManagerClient* client, - uint32 port_index, - const std::vector<uint8>& data, - double timestamp) { - // Lazily create the thread when first needed. - if (!send_thread_) { - send_thread_.reset(new base::Thread("MIDISendThread")); - send_thread_->Start(); - send_message_loop_ = send_thread_->message_loop_proxy(); - } - - send_message_loop_->PostTask( - FROM_HERE, - base::Bind(&MIDIManager::SendMIDIData, base::Unretained(this), - client, port_index, data, timestamp)); -} - } // namespace media diff --git a/chromium/media/midi/midi_manager.h b/chromium/media/midi/midi_manager.h index 6a301a942d9..f42a40de769 100644 --- a/chromium/media/midi/midi_manager.h +++ b/chromium/media/midi/midi_manager.h @@ -9,16 +9,10 @@ #include <vector> #include "base/basictypes.h" -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop_proxy.h" #include "base/synchronization/lock.h" #include "media/base/media_export.h" #include "media/midi/midi_port_info.h" -namespace base { -class Thread; -} - namespace media { // A MIDIManagerClient registers with the MIDIManager to receive MIDI data. @@ -63,17 +57,18 @@ class MEDIA_EXPORT MIDIManager { // A client calls ReleaseSession() to stop receiving MIDI data. void EndSession(MIDIManagerClient* client); - // DispatchSendMIDIData() schedules one or more messages to be sent - // at the given time on a dedicated thread. + // DispatchSendMIDIData() is called when MIDI data should be sent to the MIDI + // system. + // This method is supposed to return immediately and should not block. // |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. A value of 0 // means send "now" or as soon as possible. - void DispatchSendMIDIData(MIDIManagerClient* client, - uint32 port_index, - const std::vector<uint8>& data, - double timestamp); + virtual void DispatchSendMIDIData(MIDIManagerClient* client, + uint32 port_index, + const std::vector<uint8>& data, + 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 @@ -89,13 +84,6 @@ class MEDIA_EXPORT MIDIManager { // Initializes the MIDI system, returning |true| on success. virtual bool Initialize() = 0; - // Implements the platform-specific details of sending MIDI data. - // This function runs on MIDISendThread. - virtual void SendMIDIData(MIDIManagerClient* client, - uint32 port_index, - const std::vector<uint8>& data, - double timestamp) = 0; - void AddInputPort(const MIDIPortInfo& info); void AddOutputPort(const MIDIPortInfo& info); @@ -105,9 +93,6 @@ class MEDIA_EXPORT MIDIManager { size_t length, double timestamp); - // Checks if current thread is MIDISendThread. - bool CurrentlyOnMIDISendThread(); - bool initialized_; // Keeps track of all clients who wish to receive MIDI data. @@ -120,11 +105,6 @@ class MEDIA_EXPORT MIDIManager { MIDIPortInfoList input_ports_; MIDIPortInfoList output_ports_; - // |send_thread_| is used to send MIDI data by calling the platform-specific - // API. - scoped_ptr<base::Thread> send_thread_; - scoped_refptr<base::MessageLoopProxy> send_message_loop_; - DISALLOW_COPY_AND_ASSIGN(MIDIManager); }; diff --git a/chromium/media/midi/midi_manager_mac.cc b/chromium/media/midi/midi_manager_mac.cc index 4477944e773..a36d1debe13 100644 --- a/chromium/media/midi/midi_manager_mac.cc +++ b/chromium/media/midi/midi_manager_mac.cc @@ -4,12 +4,13 @@ #include "media/midi/midi_manager_mac.h" -#include <iostream> #include <string> #include "base/debug/trace_event.h" +#include "base/message_loop/message_loop.h" #include "base/strings/string_number_conversions.h" #include "base/strings/sys_string_conversions.h" + #include <CoreAudio/HostTime.h> using base::IntToString; @@ -31,7 +32,8 @@ MIDIManagerMac::MIDIManagerMac() coremidi_input_(0), coremidi_output_(0), packet_list_(NULL), - midi_packet_(NULL) { + midi_packet_(NULL), + send_thread_("MIDISendThread") { } bool MIDIManagerMac::Initialize() { @@ -103,13 +105,31 @@ bool MIDIManagerMac::Initialize() { return true; } +void MIDIManagerMac::DispatchSendMIDIData(MIDIManagerClient* client, + uint32 port_index, + const std::vector<uint8>& data, + double timestamp) { + if (!send_thread_.IsRunning()) + send_thread_.Start(); + + // OK to use base::Unretained(this) since we join to thread in dtor(). + send_thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&MIDIManagerMac::SendMIDIData, base::Unretained(this), + client, port_index, data, timestamp)); +} + MIDIManagerMac::~MIDIManagerMac() { + // Wait for the termination of |send_thread_| before disposing MIDI ports. + send_thread_.Stop(); + if (coremidi_input_) MIDIPortDispose(coremidi_input_); if (coremidi_output_) MIDIPortDispose(coremidi_output_); } +// static void MIDIManagerMac::ReadMIDIDispatch(const MIDIPacketList* packet_list, void* read_proc_refcon, void* src_conn_refcon) { @@ -133,7 +153,7 @@ void MIDIManagerMac::ReadMIDI(MIDIEndpointRef source, uint32 port_index = source_map_[source]; // Go through each packet and process separately. - for(size_t i = 0; i < packet_list->numPackets; i++) { + 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); @@ -150,7 +170,7 @@ void MIDIManagerMac::SendMIDIData(MIDIManagerClient* client, uint32 port_index, const std::vector<uint8>& data, double timestamp) { - DCHECK(CurrentlyOnMIDISendThread()); + DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); // System Exclusive has already been filtered. MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp); @@ -177,34 +197,57 @@ void MIDIManagerMac::SendMIDIData(MIDIManagerClient* client, client->AccumulateMIDIBytesSent(data.size()); } +// static MIDIPortInfo MIDIManagerMac::GetPortInfoFromEndpoint( MIDIEndpointRef endpoint) { SInt32 id_number = 0; MIDIObjectGetIntegerProperty(endpoint, kMIDIPropertyUniqueID, &id_number); string id = IntToString(id_number); + string manufacturer; CFStringRef manufacturer_ref = NULL; - MIDIObjectGetStringProperty( + OSStatus result = MIDIObjectGetStringProperty( endpoint, kMIDIPropertyManufacturer, &manufacturer_ref); - string manufacturer = SysCFStringRefToUTF8(manufacturer_ref); + if (result == noErr) { + manufacturer = SysCFStringRefToUTF8(manufacturer_ref); + } else { + // kMIDIPropertyManufacturer is not supported in IAC driver providing + // endpoints, and the result will be kMIDIUnknownProperty (-10835). + DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status " + << result; + } + string name; CFStringRef name_ref = NULL; - MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref); - string name = SysCFStringRefToUTF8(name_ref); + result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &name_ref); + if (result == noErr) + name = SysCFStringRefToUTF8(name_ref); + else + DLOG(WARNING) << "Failed to get kMIDIPropertyName with status " << result; + string version; SInt32 version_number = 0; - MIDIObjectGetIntegerProperty( + result = MIDIObjectGetIntegerProperty( endpoint, kMIDIPropertyDriverVersion, &version_number); - string version = IntToString(version_number); + if (result == noErr) { + version = IntToString(version_number); + } else { + // kMIDIPropertyDriverVersion is not supported in IAC driver providing + // endpoints, and the result will be kMIDIUnknownProperty (-10835). + DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status " + << result; + } return MIDIPortInfo(id, manufacturer, name, version); } +// static double MIDIManagerMac::MIDITimeStampToSeconds(MIDITimeStamp timestamp) { UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp); return static_cast<double>(nanoseconds) / 1.0e9; } +// static MIDITimeStamp MIDIManagerMac::SecondsToMIDITimeStamp(double seconds) { UInt64 nanos = UInt64(seconds * 1.0e9); return AudioConvertNanosToHostTime(nanos); diff --git a/chromium/media/midi/midi_manager_mac.h b/chromium/media/midi/midi_manager_mac.h index 2397b8034f7..cc8bf74a3c5 100644 --- a/chromium/media/midi/midi_manager_mac.h +++ b/chromium/media/midi/midi_manager_mac.h @@ -12,6 +12,7 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/threading/thread.h" #include "media/midi/midi_manager.h" #include "media/midi/midi_port_info.h" @@ -24,10 +25,10 @@ class MEDIA_EXPORT MIDIManagerMac : public MIDIManager { // MIDIManager implementation. virtual bool Initialize() OVERRIDE; - virtual void SendMIDIData(MIDIManagerClient* client, - uint32 port_index, - const std::vector<uint8>& data, - double timestamp) OVERRIDE; + virtual void DispatchSendMIDIData(MIDIManagerClient* client, + uint32 port_index, + const std::vector<uint8>& data, + double timestamp) OVERRIDE; private: // CoreMIDI callback for MIDI data. @@ -39,6 +40,12 @@ class MEDIA_EXPORT MIDIManagerMac : public MIDIManager { void *src_conn_refcon); virtual void ReadMIDI(MIDIEndpointRef source, const MIDIPacketList *pktlist); + // An internal callback that runs on MIDISendThread. + void SendMIDIData(MIDIManagerClient* client, + uint32 port_index, + const std::vector<uint8>& data, + double timestamp); + // Helper static media::MIDIPortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint); static double MIDITimeStampToSeconds(MIDITimeStamp timestamp); @@ -62,6 +69,9 @@ class MEDIA_EXPORT MIDIManagerMac : public MIDIManager { // Keeps track of all destinations. std::vector<MIDIEndpointRef> destinations_; + // |send_thread_| is used to send MIDI data. + base::Thread send_thread_; + DISALLOW_COPY_AND_ASSIGN(MIDIManagerMac); }; diff --git a/chromium/media/midi/midi_manager_win.cc b/chromium/media/midi/midi_manager_win.cc new file mode 100644 index 00000000000..d250e6aefff --- /dev/null +++ b/chromium/media/midi/midi_manager_win.cc @@ -0,0 +1,597 @@ +// Copyright 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_win.h" + +#include <windows.h> + +// Prevent unnecessary functions from being included from <mmsystem.h> +#define MMNODRV +#define MMNOSOUND +#define MMNOWAVE +#define MMNOAUX +#define MMNOMIXER +#define MMNOTIMER +#define MMNOJOY +#define MMNOMCI +#define MMNOMMIO +#include <mmsystem.h> + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread.h" +#include "media/midi/midi_message_queue.h" +#include "media/midi/midi_message_util.h" +#include "media/midi/midi_port_info.h" + +namespace media { +namespace { + +std::string GetInErrorMessage(MMRESULT result) { + wchar_t text[MAXERRORLENGTH]; + MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); + if (get_result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to get error message." + << " original error: " << result + << " midiInGetErrorText error: " << get_result; + return std::string(); + } + return WideToUTF8(text); +} + +std::string GetOutErrorMessage(MMRESULT result) { + wchar_t text[MAXERRORLENGTH]; + MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text)); + if (get_result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to get error message." + << " original error: " << result + << " midiOutGetErrorText error: " << get_result; + return std::string(); + } + return WideToUTF8(text); +} + +class MIDIHDRDeleter { + public: + void operator()(MIDIHDR* header) { + if (!header) + return; + delete[] static_cast<char*>(header->lpData); + header->lpData = NULL; + header->dwBufferLength = 0; + delete header; + } +}; + +typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR; + +ScopedMIDIHDR CreateMIDIHDR(size_t size) { + ScopedMIDIHDR header(new MIDIHDR); + ZeroMemory(header.get(), sizeof(*header)); + header->lpData = new char[size]; + header->dwBufferLength = size; + return header.Pass(); +} + +void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle, + const std::vector<uint8>& message) { + if (message.size() >= 4) + return; + + DWORD packed_message = 0; + for (size_t i = 0; i < message.size(); ++i) + packed_message |= (static_cast<uint32>(message[i]) << (i * 8)); + MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to output short message: " << GetOutErrorMessage(result); +} + +void SendLongMIDIMessageInternal(HMIDIOUT midi_out_handle, + const std::vector<uint8>& message) { + // Implementation note: + // Sending long MIDI message can be performed synchronously or asynchronously + // depending on the driver. There are 2 options to support both cases: + // 1) Call midiOutLongMsg() API and wait for its completion within this + // function. In this approach, we can avoid memory copy by directly pointing + // |message| as the data buffer to be sent. + // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg() + // API. The buffer will be freed in the MOM_DONE event hander, which tells + // us that the task of midiOutLongMsg() API is completed. + // Here we choose option 2) in favor of asynchronous design. + + // Note for built-in USB-MIDI driver: + // From an observation on Windows 7/8.1 with a USB-MIDI keyboard, + // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data + // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly + // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size + // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at + // most 1 sec or so with a typical USB-MIDI device. + const size_t kSysExSizeLimit = 60 * 1024; + if (message.size() >= kSysExSizeLimit) { + DVLOG(1) << "Ingnoreing SysEx message due to the size limit" + << ", size = " << message.size(); + return; + } + + ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); + for (size_t i = 0; i < message.size(); ++i) + midi_header->lpData[i] = static_cast<char>(message[i]); + + MMRESULT result = midiOutPrepareHeader( + midi_out_handle, midi_header.get(), sizeof(*midi_header)); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to prepare output buffer: " + << GetOutErrorMessage(result); + return; + } + + result = midiOutLongMsg( + midi_out_handle, midi_header.get(), sizeof(*midi_header)); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to output long message: " + << GetOutErrorMessage(result); + result = midiOutUnprepareHeader( + midi_out_handle, midi_header.get(), sizeof(*midi_header)); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to uninitialize output buffer: " + << GetOutErrorMessage(result); + return; + } + + // The ownership of |midi_header| is moved to MOM_DONE event handler. + midi_header.release(); +} + +} // namespace + +class MIDIManagerWin::InDeviceInfo { + public: + ~InDeviceInfo() { + Uninitialize(); + } + void set_port_index(int index) { + port_index_ = index; + } + int port_index() const { + return port_index_; + } + bool device_to_be_closed() const { + return device_to_be_closed_; + } + HMIDIIN midi_handle() const { + return midi_handle_; + } + const base::TimeDelta& start_time_offset() const { + return start_time_offset_; + } + + static scoped_ptr<InDeviceInfo> Create(MIDIManagerWin* manager, + UINT device_id) { + scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager)); + if (!obj->Initialize(device_id)) + obj.reset(); + return obj.Pass(); + } + + private: + static const int kInvalidPortIndex = -1; + static const size_t kBufferLength = 32 * 1024; + + explicit InDeviceInfo(MIDIManagerWin* manager) + : manager_(manager), + port_index_(kInvalidPortIndex), + midi_handle_(NULL), + started_(false), + device_to_be_closed_(false) { + } + + bool Initialize(DWORD device_id) { + Uninitialize(); + midi_header_ = CreateMIDIHDR(kBufferLength); + + // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and + // MIM_CLOSE events. + // - MIM_DATA: This is the only way to get a short MIDI message with + // timestamp information. + // - MIM_LONGDATA: This is the only way to get a long MIDI message with + // timestamp information. + // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2) + // the MIDI device becomes unavailable for some reasons, e.g., the cable + // is disconnected. As for the former case, HMIDIOUT will be invalidated + // soon after the callback is finished. As for the later case, however, + // HMIDIOUT continues to be valid until midiInClose() is called. + MMRESULT result = midiInOpen(&midi_handle_, + device_id, + reinterpret_cast<DWORD_PTR>(&HandleMessage), + reinterpret_cast<DWORD_PTR>(this), + CALLBACK_FUNCTION); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to open output device. " + << " id: " << device_id + << " message: " << GetInErrorMessage(result); + return false; + } + result = midiInPrepareHeader( + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to initialize input buffer: " + << GetInErrorMessage(result); + return false; + } + result = midiInAddBuffer( + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to attach input buffer: " + << GetInErrorMessage(result); + return false; + } + result = midiInStart(midi_handle_); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to start input port: " + << GetInErrorMessage(result); + return false; + } + started_ = true; + start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks(); + return true; + } + + void Uninitialize() { + MMRESULT result = MMSYSERR_NOERROR; + if (midi_handle_ && started_) { + result = midiInStop(midi_handle_); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to stop input port: " << GetInErrorMessage(result); + started_ = false; + start_time_offset_ = base::TimeDelta(); + } + if (midi_handle_) { + // midiInReset flushes pending messages. We ignore these messages. + device_to_be_closed_ = true; + result = midiInReset(midi_handle_); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to reset input port: " << GetInErrorMessage(result); + result = midiInClose(midi_handle_); + device_to_be_closed_ = false; + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to close input port: " << GetInErrorMessage(result); + midi_header_.reset(); + midi_handle_ = NULL; + port_index_ = kInvalidPortIndex; + } + } + + static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, + UINT message, + DWORD_PTR instance, + DWORD_PTR param1, + DWORD_PTR param2) { + // This method can be called back on any thread depending on Windows + // multimedia subsystem and underlying MIDI drivers. + InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance); + if (!self) + return; + if (self->midi_handle() != midi_in_handle) + return; + + switch (message) { + case MIM_DATA: + self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff), + static_cast<uint8>((param1 >> 8) & 0xff), + static_cast<uint8>((param1 >> 16) & 0xff), + self->TickToTimeDelta(param2)); + return; + case MIM_LONGDATA: + self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1), + self->TickToTimeDelta(param2)); + return; + case MIM_CLOSE: + // TODO(yukawa): Implement crbug.com/279097. + return; + } + } + + void OnShortMessageReceived(uint8 status_byte, + uint8 first_data_byte, + uint8 second_data_byte, + base::TimeDelta timestamp) { + if (device_to_be_closed()) + return; + const size_t len = GetMIDIMessageLength(status_byte); + if (len == 0 || port_index() == kInvalidPortIndex) + return; + const uint8 kData[] = { status_byte, first_data_byte, second_data_byte }; + DCHECK_LE(len, arraysize(kData)); + manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF()); + } + + void OnLongMessageReceived(MIDIHDR* header, base::TimeDelta timestamp) { + if (header != midi_header_.get()) + return; + MMRESULT result = MMSYSERR_NOERROR; + if (device_to_be_closed()) { + if (midi_header_ && + (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { + result = midiInUnprepareHeader( + midi_handle_, midi_header_.get(), sizeof(*midi_header_)); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to uninitialize input buffer: " + << GetInErrorMessage(result); + } + return; + } + if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) { + manager_->ReceiveMIDIData(port_index_, + reinterpret_cast<const uint8*>(header->lpData), + header->dwBytesRecorded, + timestamp.InSecondsF()); + } + result = midiInAddBuffer(midi_handle(), header, sizeof(*header)); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to attach input port: " << GetInErrorMessage(result); + } + + base::TimeDelta TickToTimeDelta(DWORD tick) const { + const base::TimeDelta delta = + base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick)); + return start_time_offset_ + delta; + } + + MIDIManagerWin* manager_; + int port_index_; + HMIDIIN midi_handle_; + ScopedMIDIHDR midi_header_; + base::TimeDelta start_time_offset_; + bool started_; + bool device_to_be_closed_; + DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InDeviceInfo); +}; + +class MIDIManagerWin::OutDeviceInfo { + public: + ~OutDeviceInfo() { + Uninitialize(); + } + + static scoped_ptr<OutDeviceInfo> Create(UINT device_id) { + scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo); + if (!obj->Initialize(device_id)) + obj.reset(); + return obj.Pass(); + } + + HMIDIOUT midi_handle() const { + return midi_handle_; + } + + void Quit() { + quitting_ = true; + } + + void Send(const std::vector<uint8>& data) { + // Check if the attached device is still available or not. + if (!midi_handle_) + return; + + // Give up sending MIDI messages here if the device is already closed. + // Note that this check is optional. Regardless of that we check |closed_| + // or not, nothing harmful happens as long as |midi_handle_| is still valid. + if (closed_) + return; + + // MIDI Running status must be filtered out. + MIDIMessageQueue message_queue(false); + message_queue.Add(data); + std::vector<uint8> message; + while (!quitting_) { + message_queue.Get(&message); + if (message.empty()) + break; + // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes. + if (message.size() <= 3) + SendShortMIDIMessageInternal(midi_handle_, message); + else + SendLongMIDIMessageInternal(midi_handle_, message); + } + } + + private: + OutDeviceInfo() + : midi_handle_(NULL), + closed_(false), + quitting_(false) {} + + bool Initialize(DWORD device_id) { + Uninitialize(); + // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE + // events. + // - MOM_DONE: SendLongMIDIMessageInternal() relies on this event to clean + // up the backing store where a long MIDI message is stored. + // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2) + // the MIDI device becomes unavailable for some reasons, e.g., the cable + // is disconnected. As for the former case, HMIDIOUT will be invalidated + // soon after the callback is finished. As for the later case, however, + // HMIDIOUT continues to be valid until midiOutClose() is called. + MMRESULT result = midiOutOpen(&midi_handle_, + device_id, + reinterpret_cast<DWORD_PTR>(&HandleMessage), + reinterpret_cast<DWORD_PTR>(this), + CALLBACK_FUNCTION); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to open output device. " + << " id: " << device_id + << " message: "<< GetOutErrorMessage(result); + midi_handle_ = NULL; + return false; + } + return true; + } + + void Uninitialize() { + if (!midi_handle_) + return; + + MMRESULT result = midiOutReset(midi_handle_); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to reset output port: " << GetOutErrorMessage(result); + result = midiOutClose(midi_handle_); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to close output port: " << GetOutErrorMessage(result); + midi_handle_ = NULL; + closed_ = true; + } + + static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle, + UINT message, + DWORD_PTR instance, + DWORD_PTR param1, + DWORD_PTR param2) { + // This method can be called back on any thread depending on Windows + // multimedia subsystem and underlying MIDI drivers. + + OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance); + if (!self) + return; + if (self->midi_handle() != midi_out_handle) + return; + switch (message) { + case MOM_DONE: { + // Take ownership of the MIDIHDR object. + ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); + if (!header) + return; + MMRESULT result = midiOutUnprepareHeader( + self->midi_handle(), header.get(), sizeof(*header)); + DLOG_IF(ERROR, result != MMSYSERR_NOERROR) + << "Failed to uninitialize output buffer: " + << GetOutErrorMessage(result); + return; + } + case MOM_CLOSE: + // No lock is required since this flag is just a hint to avoid + // unnecessary API calls that will result in failure anyway. + self->closed_ = true; + // TODO(yukawa): Implement crbug.com/279097. + return; + } + } + + HMIDIOUT midi_handle_; + + // True if the device is already closed. + volatile bool closed_; + + // True if the MIDIManagerWin is trying to stop the sender thread. + volatile bool quitting_; + + DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutDeviceInfo); +}; + +MIDIManagerWin::MIDIManagerWin() + : send_thread_("MIDISendThread") { +} + +bool MIDIManagerWin::Initialize() { + const UINT num_in_devices = midiInGetNumDevs(); + in_devices_.reserve(num_in_devices); + for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { + MIDIINCAPS caps = {}; + MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to obtain input device info: " + << GetInErrorMessage(result); + continue; + } + scoped_ptr<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id)); + if (!in_device) + continue; + MIDIPortInfo info( + base::IntToString(static_cast<int>(device_id)), + "", + base::WideToUTF8(caps.szPname), + base::IntToString(static_cast<int>(caps.vDriverVersion))); + AddInputPort(info); + in_device->set_port_index(input_ports_.size() - 1); + in_devices_.push_back(in_device.Pass()); + } + + const UINT num_out_devices = midiOutGetNumDevs(); + out_devices_.reserve(num_out_devices); + for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { + MIDIOUTCAPS caps = {}; + MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) { + DLOG(ERROR) << "Failed to obtain output device info: " + << GetOutErrorMessage(result); + continue; + } + scoped_ptr<OutDeviceInfo> out_port(OutDeviceInfo::Create(device_id)); + if (!out_port) + continue; + MIDIPortInfo info( + base::IntToString(static_cast<int>(device_id)), + "", + base::WideToUTF8(caps.szPname), + base::IntToString(static_cast<int>(caps.vDriverVersion))); + AddOutputPort(info); + out_devices_.push_back(out_port.Pass()); + } + + return true; +} + +MIDIManagerWin::~MIDIManagerWin() { + // Cleanup order is important. |send_thread_| must be stopped before + // |out_devices_| is cleared. + for (size_t i = 0; i < output_ports_.size(); ++i) + out_devices_[i]->Quit(); + send_thread_.Stop(); + + out_devices_.clear(); + output_ports_.clear(); + in_devices_.clear(); + input_ports_.clear(); +} + +void MIDIManagerWin::DispatchSendMIDIData(MIDIManagerClient* client, + uint32 port_index, + const std::vector<uint8>& data, + double timestamp) { + if (out_devices_.size() <= port_index) + return; + + base::TimeDelta delay; + if (timestamp != 0.0) { + base::TimeTicks time_to_send = + base::TimeTicks() + base::TimeDelta::FromMicroseconds( + timestamp * base::Time::kMicrosecondsPerSecond); + delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta()); + } + + if (!send_thread_.IsRunning()) + send_thread_.Start(); + + OutDeviceInfo* out_port = out_devices_[port_index].get(); + send_thread_.message_loop()->PostDelayedTask( + FROM_HERE, + base::Bind(&OutDeviceInfo::Send, base::Unretained(out_port), data), + delay); + + // Call back AccumulateMIDIBytesSent() on |send_thread_| to emulate the + // behavior of MIDIManagerMac::SendMIDIData. + // TODO(yukawa): Do this task in a platform-independent way if possible. + // See crbug.com/325810. + send_thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&MIDIManagerClient::AccumulateMIDIBytesSent, + base::Unretained(client), data.size())); +} + +MIDIManager* MIDIManager::Create() { + return new MIDIManagerWin(); +} + +} // namespace media diff --git a/chromium/media/midi/midi_manager_win.h b/chromium/media/midi/midi_manager_win.h new file mode 100644 index 00000000000..eef8b4e5680 --- /dev/null +++ b/chromium/media/midi/midi_manager_win.h @@ -0,0 +1,40 @@ +// Copyright 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_WIN_H_ +#define MEDIA_MIDI_MIDI_MANAGER_WIN_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "media/midi/midi_manager.h" + +namespace media { + +class MIDIManagerWin : public MIDIManager { + public: + MIDIManagerWin(); + virtual ~MIDIManagerWin(); + + // MIDIManager implementation. + virtual bool Initialize() OVERRIDE; + virtual void DispatchSendMIDIData(MIDIManagerClient* client, + uint32 port_index, + const std::vector<uint8>& data, + double timestamp) OVERRIDE; + + private: + class InDeviceInfo; + class OutDeviceInfo; + std::vector<scoped_ptr<InDeviceInfo> > in_devices_; + std::vector<scoped_ptr<OutDeviceInfo> > out_devices_; + base::Thread send_thread_; + DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin); +}; + +} // namespace media + +#endif // MEDIA_MIDI_MIDI_MANAGER_WIN_H_ diff --git a/chromium/media/midi/midi_message_queue.cc b/chromium/media/midi/midi_message_queue.cc new file mode 100644 index 00000000000..3452e80be9d --- /dev/null +++ b/chromium/media/midi/midi_message_queue.cc @@ -0,0 +1,119 @@ +// Copyright 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_message_queue.h" + +#include <algorithm> + +#include "base/logging.h" +#include "media/midi/midi_message_util.h" + +namespace media { +namespace { + +const uint8 kSysEx = 0xf0; +const uint8 kEndOfSysEx = 0xf7; + +bool IsDataByte(uint8 data) { + return (data & 0x80) == 0; +} + +bool IsFirstStatusByte(uint8 data) { + return !IsDataByte(data) && data != kEndOfSysEx; +} + +bool IsSystemRealTimeMessage(uint8 data) { + return 0xf8 <= data && data <= 0xff; +} + +} // namespace + +MIDIMessageQueue::MIDIMessageQueue(bool allow_running_status) + : allow_running_status_(allow_running_status) {} + +MIDIMessageQueue::~MIDIMessageQueue() {} + +void MIDIMessageQueue::Add(const std::vector<uint8>& data) { + queue_.insert(queue_.end(), data.begin(), data.end()); +} + +void MIDIMessageQueue::Add(const uint8* data, size_t length) { + queue_.insert(queue_.end(), data, data + length); +} + +void MIDIMessageQueue::Get(std::vector<uint8>* message) { + message->clear(); + + while (true) { + if (queue_.empty()) + return; + + const uint8 next = queue_.front(); + queue_.pop_front(); + + // "System Real Time Messages" is a special kind of MIDI messages, which can + // appear at arbitrary byte position of MIDI stream. Here we reorder + // "System Real Time Messages" prior to |next_message_| so that each message + // can be clearly separated as a complete MIDI message. + if (IsSystemRealTimeMessage(next)) { + message->push_back(next); + return; + } + + // Here |next_message_[0]| may contain the previous status byte when + // |allow_running_status_| is true. Following condition fixes up + // |next_message_| if running status condition is not fulfilled. + if (!next_message_.empty() && + ((next_message_[0] == kSysEx && IsFirstStatusByte(next)) || + (next_message_[0] != kSysEx && !IsDataByte(next)))) { + // An invalid data sequence is found or running status condition is not + // fulfilled. + next_message_.clear(); + } + + if (next_message_.empty()) { + if (IsFirstStatusByte(next)) { + next_message_.push_back(next); + } else { + // MIDI protocol doesn't provide any error correction mechanism in + // physical layers, and incoming messages can be corrupted, and should + // be corrected here. + } + continue; + } + + // Here we can assume |next_message_| starts with a valid status byte. + const uint8 status_byte = next_message_[0]; + next_message_.push_back(next); + + if (status_byte == kSysEx) { + if (next == kEndOfSysEx) { + std::swap(*message, next_message_); + next_message_.clear(); + return; + } + continue; + } + + DCHECK(IsDataByte(next)); + DCHECK_NE(kSysEx, status_byte); + const size_t target_len = GetMIDIMessageLength(status_byte); + if (next_message_.size() < target_len) + continue; + if (next_message_.size() == target_len) { + std::swap(*message, next_message_); + next_message_.clear(); + if (allow_running_status_) { + // Speculatively keep the status byte in case of running status. If this + // assumption is not true, |next_message_| will be cleared anyway. + next_message_.push_back(status_byte); + } + return; + } + + NOTREACHED(); + } +} + +} // namespace media diff --git a/chromium/media/midi/midi_message_queue.h b/chromium/media/midi/midi_message_queue.h new file mode 100644 index 00000000000..06f0f4787fb --- /dev/null +++ b/chromium/media/midi/midi_message_queue.h @@ -0,0 +1,72 @@ +// Copyright 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_MESSAGE_QUEUE_H_ +#define MEDIA_MIDI_MIDI_MESSAGE_QUEUE_H_ + +#include <deque> +#include <vector> + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +// A simple message splitter for possibly unsafe/corrupted MIDI data stream. +// This class allows you to: +// - maintain fragmented MIDI message. +// - skip any invalid data sequence. +// - reorder MIDI messages so that "System Real Time Message", which can be +// inserted at any point of the byte stream, is placed at the boundary of +// complete MIDI messages. +// - (Optional) reconstruct complete MIDI messages from data stream where +// MIDI status byte is abbreviated (a.k.a. "running status"). +// +// Example (pseudo message loop): +// MIDIMessageQueue queue(true); // true to support "running status" +// while (true) { +// if (is_incoming_midi_data_available()) { +// std::vector<uint8> incoming_data; +// read_incoming_midi_data(&incoming_data) +// queue.Add(incoming_data); +// } +// while (true) { +// std::vector<uint8> next_message; +// queue.Get(&next_message); +// if (!next_message.empty()) +// dispatch(next_message); +// } +// } +class MEDIA_EXPORT MIDIMessageQueue { + public: + // Initializes the queue. Set true to |allow_running_status| to enable + // "MIDI running status" reconstruction. + explicit MIDIMessageQueue(bool allow_running_status); + ~MIDIMessageQueue(); + + // Enqueues |data| to the internal buffer. + void Add(const std::vector<uint8>& data); + void Add(const uint8* data, size_t length); + + // Fills the next complete MIDI message into |message|. If |message| is + // not empty, the data sequence falls into one of the following types of + // MIDI message. + // - Single "Channel Voice Message" (w/o "System Real Time Messages") + // - Single "Channel Mode Message" (w/o "System Real Time Messages") + // - Single "System Exclusive Message" (w/o "System Real Time Messages") + // - Single "System Common Message" (w/o "System Real Time Messages") + // - Single "System Real Time message" + // |message| is empty if there is no complete MIDI message any more. + void Get(std::vector<uint8>* message); + + private: + std::deque<uint8> queue_; + std::vector<uint8> next_message_; + const bool allow_running_status_; + DISALLOW_COPY_AND_ASSIGN(MIDIMessageQueue); +}; + +} // namespace media + +#endif // MEDIA_MIDI_MIDI_MESSAGE_QUEUE_H_ diff --git a/chromium/media/midi/midi_message_queue_unittest.cc b/chromium/media/midi/midi_message_queue_unittest.cc new file mode 100644 index 00000000000..a00eea6b9e4 --- /dev/null +++ b/chromium/media/midi/midi_message_queue_unittest.cc @@ -0,0 +1,173 @@ +// Copyright 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_message_queue.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace { + +const uint8 kGMOn[] = { 0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7 }; +const uint8 kGSOn[] = { + 0xf0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41, 0xf7, +}; +const uint8 kNoteOn[] = { 0x90, 0x3c, 0x7f }; +const uint8 kNoteOnWithRunningStatus[] = { + 0x90, 0x3c, 0x7f, 0x3c, 0x7f, 0x3c, 0x7f, +}; +const uint8 kChannelPressure[] = { 0xd0, 0x01 }; +const uint8 kChannelPressureWithRunningStatus[] = { + 0xd0, 0x01, 0x01, 0x01, +}; +const uint8 kTimingClock[] = { 0xf8 }; +const uint8 kBrokenData1[] = { 0x90 }; +const uint8 kBrokenData2[] = { 0xf7 }; +const uint8 kBrokenData3[] = { 0xf2, 0x00 }; +const uint8 kDataByte0[] = { 0x00 }; + +template <typename T, size_t N> +void Add(MIDIMessageQueue* queue, const T(&array)[N]) { + queue->Add(array, N); +} + +template <typename T, size_t N> +::testing::AssertionResult ExpectEqualSequence( + const char* expr1, const char* expr2, + const T(&expected)[N], const std::vector<T>& actual) { + if (actual.size() != N) { + return ::testing::AssertionFailure() + << "expected: " << ::testing::PrintToString(expected) + << ", actual: " << ::testing::PrintToString(actual); + } + for (size_t i = 0; i < N; ++i) { + if (expected[i] != actual[i]) { + return ::testing::AssertionFailure() + << "expected: " << ::testing::PrintToString(expected) + << ", actual: " << ::testing::PrintToString(actual); + } + } + return ::testing::AssertionSuccess(); +} + +#define EXPECT_MESSAGE(expected, actual) \ + EXPECT_PRED_FORMAT2(ExpectEqualSequence, expected, actual) + +TEST(MIDIMessageQueueTest, EmptyData) { + MIDIMessageQueue queue(false); + std::vector<uint8> message; + queue.Get(&message); + EXPECT_TRUE(message.empty()); +} + +TEST(MIDIMessageQueueTest, RunningStatusDisabled) { + MIDIMessageQueue queue(false); + Add(&queue, kGMOn); + Add(&queue, kBrokenData1); + Add(&queue, kNoteOnWithRunningStatus); + Add(&queue, kBrokenData2); + Add(&queue, kChannelPressureWithRunningStatus); + Add(&queue, kBrokenData3); + Add(&queue, kNoteOn); + Add(&queue, kBrokenData1); + Add(&queue, kGSOn); + Add(&queue, kBrokenData2); + Add(&queue, kTimingClock); + Add(&queue, kBrokenData3); + + std::vector<uint8> message; + queue.Get(&message); + EXPECT_MESSAGE(kGMOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message) << "Running status should be ignored"; + queue.Get(&message); + EXPECT_MESSAGE(kChannelPressure, message) + << "Running status should be ignored"; + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kGSOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_TRUE(message.empty()); +} + +TEST(MIDIMessageQueueTest, RunningStatusEnabled) { + MIDIMessageQueue queue(true); + Add(&queue, kGMOn); + Add(&queue, kBrokenData1); + Add(&queue, kNoteOnWithRunningStatus); + Add(&queue, kBrokenData2); + Add(&queue, kChannelPressureWithRunningStatus); + Add(&queue, kBrokenData3); + Add(&queue, kNoteOn); + Add(&queue, kBrokenData1); + Add(&queue, kGSOn); + Add(&queue, kBrokenData2); + Add(&queue, kTimingClock); + Add(&queue, kDataByte0); + + std::vector<uint8> message; + queue.Get(&message); + EXPECT_MESSAGE(kGMOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message) + << "Running status should be converted into a canonical MIDI message"; + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message) + << "Running status should be converted into a canonical MIDI message"; + queue.Get(&message); + EXPECT_MESSAGE(kChannelPressure, message); + queue.Get(&message); + EXPECT_MESSAGE(kChannelPressure, message) + << "Running status should be converted into a canonical MIDI message"; + queue.Get(&message); + EXPECT_MESSAGE(kChannelPressure, message) + << "Running status should be converted into a canonical MIDI message"; + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kGSOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_TRUE(message.empty()) + << "Running status must not be applied to real time messages"; +} + +TEST(MIDIMessageQueueTest, RunningStatusEnabledWithRealTimeEvent) { + MIDIMessageQueue queue(true); + const uint8 kNoteOnWithRunningStatusWithkTimingClock[] = { + 0x90, 0xf8, 0x3c, 0xf8, 0x7f, 0xf8, 0x3c, 0xf8, 0x7f, 0xf8, 0x3c, 0xf8, + 0x7f, + }; + Add(&queue, kNoteOnWithRunningStatusWithkTimingClock); + std::vector<uint8> message; + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_MESSAGE(kTimingClock, message); + queue.Get(&message); + EXPECT_MESSAGE(kNoteOn, message); + queue.Get(&message); + EXPECT_TRUE(message.empty()); +} + +} // namespace +} // namespace media diff --git a/chromium/media/midi/midi_message_util.cc b/chromium/media/midi/midi_message_util.cc new file mode 100644 index 00000000000..83d3cc071d9 --- /dev/null +++ b/chromium/media/midi/midi_message_util.cc @@ -0,0 +1,34 @@ +// Copyright 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_message_util.h" + +namespace media { + +size_t GetMIDIMessageLength(uint8 status_byte) { + if (status_byte < 0x80) + return 0; + if (0x80 <= status_byte && status_byte <= 0xbf) + return 3; + if (0xc0 <= status_byte && status_byte <= 0xdf) + return 2; + if (0xe0 <= status_byte && status_byte <= 0xef) + return 3; + if (status_byte == 0xf0) + return 0; + if (status_byte == 0xf1) + return 2; + if (status_byte == 0xf2) + return 3; + if (status_byte == 0xf3) + return 2; + if (0xf4 <= status_byte && status_byte <= 0xf6) + return 1; + if (status_byte == 0xf7) + return 0; + // 0xf8 <= status_byte && status_byte <= 0xff + return 1; +} + +} // namespace media diff --git a/chromium/media/midi/midi_message_util.h b/chromium/media/midi/midi_message_util.h new file mode 100644 index 00000000000..1dc6d3cba78 --- /dev/null +++ b/chromium/media/midi/midi_message_util.h @@ -0,0 +1,25 @@ +// Copyright 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_MESSAGE_UTIL_H_ +#define MEDIA_MIDI_MIDI_MESSAGE_UTIL_H_ + +#include <deque> +#include <vector> + +#include "base/basictypes.h" +#include "media/base/media_export.h" + +namespace media { + +// Returns the length of a MIDI message in bytes. Never returns 4 or greater. +// Returns 0 if |status_byte| is: +// - not a valid status byte, namely data byte. +// - the MIDI System Exclusive message. +// - the End of System Exclusive message. +MEDIA_EXPORT size_t GetMIDIMessageLength(uint8 status_byte); + +} // namespace media + +#endif // MEDIA_MIDI_MIDI_MESSAGE_UTIL_H_ diff --git a/chromium/media/midi/midi_message_util_unittest.cc b/chromium/media/midi/midi_message_util_unittest.cc new file mode 100644 index 00000000000..af3679c2987 --- /dev/null +++ b/chromium/media/midi/midi_message_util_unittest.cc @@ -0,0 +1,34 @@ +// Copyright 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_message_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace { + +const uint8 kGMOn[] = { 0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7 }; +const uint8 kNoteOn[] = { 0x90, 0x3c, 0x7f }; +const uint8 kChannelPressure[] = { 0xd0, 0x01 }; +const uint8 kTimingClock[] = { 0xf8 }; + +TEST(GetMIDIMessageLengthTest, BasicTest) { + // Check basic functionarity + EXPECT_EQ(arraysize(kNoteOn), GetMIDIMessageLength(kNoteOn[0])); + EXPECT_EQ(arraysize(kChannelPressure), + GetMIDIMessageLength(kChannelPressure[0])); + EXPECT_EQ(arraysize(kTimingClock), GetMIDIMessageLength(kTimingClock[0])); + + // SysEx message should be mapped to 0-length + EXPECT_EQ(0u, GetMIDIMessageLength(kGMOn[0])); + + // Any data byte should be mapped to 0-length + EXPECT_EQ(0u, GetMIDIMessageLength(kGMOn[1])); + EXPECT_EQ(0u, GetMIDIMessageLength(kNoteOn[1])); + EXPECT_EQ(0u, GetMIDIMessageLength(kChannelPressure[1])); +} + +} // namespace +} // namespace media |