blob: 21402a9ef0be6f5660b3e3f6512a7f034e5f9110 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "modules/webmidi/MIDIOutput.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "media/midi/midi_service.mojom-blink.h"
#include "modules/webmidi/MIDIAccess.h"
using midi::mojom::PortState;
namespace blink {
namespace {
double now(ExecutionContext* context) {
LocalDOMWindow* window = context ? context->executingWindow() : nullptr;
Performance* performance =
window ? DOMWindowPerformance::performance(*window) : nullptr;
return performance ? performance->now() : 0.0;
}
class MessageValidator {
public:
static bool validate(DOMUint8Array* array,
ExceptionState& exceptionState,
bool sysexEnabled) {
MessageValidator validator(array);
return validator.process(exceptionState, sysexEnabled);
}
private:
MessageValidator(DOMUint8Array* array)
: m_data(array->data()), m_length(array->length()), m_offset(0) {}
bool process(ExceptionState& exceptionState, bool sysexEnabled) {
while (!isEndOfData() && acceptRealTimeMessages()) {
if (!isStatusByte()) {
exceptionState.throwTypeError("Running status is not allowed " +
getPositionString());
return false;
}
if (isEndOfSysex()) {
exceptionState.throwTypeError(
"Unexpected end of system exclusive message " +
getPositionString());
return false;
}
if (isReservedStatusByte()) {
exceptionState.throwTypeError("Reserved status is not allowed " +
getPositionString());
return false;
}
if (isSysex()) {
if (!sysexEnabled) {
exceptionState.throwDOMException(
InvalidAccessError,
"System exclusive message is not allowed " + getPositionString());
return false;
}
if (!acceptCurrentSysex()) {
if (isEndOfData())
exceptionState.throwTypeError(
"System exclusive message is not ended by end of system "
"exclusive message.");
else
exceptionState.throwTypeError(
"System exclusive message contains a status byte " +
getPositionString());
return false;
}
} else {
if (!acceptCurrentMessage()) {
if (isEndOfData())
exceptionState.throwTypeError("Message is incomplete.");
else
exceptionState.throwTypeError("Unexpected status byte " +
getPositionString());
return false;
}
}
}
return true;
}
private:
bool isEndOfData() { return m_offset >= m_length; }
bool isSysex() { return m_data[m_offset] == 0xf0; }
bool isSystemMessage() { return m_data[m_offset] >= 0xf0; }
bool isEndOfSysex() { return m_data[m_offset] == 0xf7; }
bool isRealTimeMessage() { return m_data[m_offset] >= 0xf8; }
bool isStatusByte() { return m_data[m_offset] & 0x80; }
bool isReservedStatusByte() {
return m_data[m_offset] == 0xf4 || m_data[m_offset] == 0xf5 ||
m_data[m_offset] == 0xf9 || m_data[m_offset] == 0xfd;
}
bool acceptRealTimeMessages() {
for (; !isEndOfData(); m_offset++) {
if (isRealTimeMessage() && !isReservedStatusByte())
continue;
return true;
}
return false;
}
bool acceptCurrentSysex() {
DCHECK(isSysex());
for (m_offset++; !isEndOfData(); m_offset++) {
if (isReservedStatusByte())
return false;
if (isRealTimeMessage())
continue;
if (isEndOfSysex()) {
m_offset++;
return true;
}
if (isStatusByte())
return false;
}
return false;
}
bool acceptCurrentMessage() {
DCHECK(isStatusByte());
DCHECK(!isSysex());
DCHECK(!isReservedStatusByte());
DCHECK(!isRealTimeMessage());
DCHECK(!isEndOfSysex());
static const int channelMessageLength[7] = {
3, 3, 3, 3, 2, 2, 3}; // for 0x8*, 0x9*, ..., 0xe*
static const int systemMessageLength[7] = {
2, 3, 2, 0, 0, 1, 0}; // for 0xf1, 0xf2, ..., 0xf7
size_t length = isSystemMessage()
? systemMessageLength[m_data[m_offset] - 0xf1]
: channelMessageLength[(m_data[m_offset] >> 4) - 8];
m_offset++;
DCHECK_GT(length, 0UL);
if (length == 1)
return true;
for (size_t count = 1; !isEndOfData(); m_offset++) {
if (isReservedStatusByte())
return false;
if (isRealTimeMessage())
continue;
if (isStatusByte())
return false;
if (++count == length) {
m_offset++;
return true;
}
}
return false;
}
String getPositionString() {
return "at index " + String::number(m_offset) + " (" +
String::number(m_data[m_offset]) + ").";
}
const unsigned char* m_data;
const size_t m_length;
size_t m_offset;
};
} // namespace
MIDIOutput* MIDIOutput::create(MIDIAccess* access,
unsigned portIndex,
const String& id,
const String& manufacturer,
const String& name,
const String& version,
PortState state) {
DCHECK(access);
MIDIOutput* output =
new MIDIOutput(access, portIndex, id, manufacturer, name, version, state);
output->suspendIfNeeded();
return output;
}
MIDIOutput::MIDIOutput(MIDIAccess* access,
unsigned portIndex,
const String& id,
const String& manufacturer,
const String& name,
const String& version,
PortState state)
: MIDIPort(access, id, manufacturer, name, TypeOutput, version, state),
m_portIndex(portIndex) {}
MIDIOutput::~MIDIOutput() {}
void MIDIOutput::send(DOMUint8Array* array,
double timestamp,
ExceptionState& exceptionState) {
DCHECK(array);
if (timestamp == 0.0)
timestamp = now(getExecutionContext());
// Implicit open. It does nothing if the port is already opened.
// This should be performed even if |array| is invalid.
open();
if (MessageValidator::validate(array, exceptionState,
midiAccess()->sysexEnabled()))
midiAccess()->sendMIDIData(m_portIndex, array->data(), array->length(),
timestamp);
}
void MIDIOutput::send(Vector<unsigned> unsignedData,
double timestamp,
ExceptionState& exceptionState) {
if (timestamp == 0.0)
timestamp = now(getExecutionContext());
DOMUint8Array* array = DOMUint8Array::create(unsignedData.size());
DOMUint8Array::ValueType* const arrayData = array->data();
const uint32_t arrayLength = array->length();
for (size_t i = 0; i < unsignedData.size(); ++i) {
if (unsignedData[i] > 0xff) {
exceptionState.throwTypeError("The value at index " + String::number(i) +
" (" + String::number(unsignedData[i]) +
") is greater than 0xFF.");
return;
}
if (i < arrayLength)
arrayData[i] = unsignedData[i] & 0xff;
}
send(array, timestamp, exceptionState);
}
void MIDIOutput::send(DOMUint8Array* data, ExceptionState& exceptionState) {
DCHECK(data);
send(data, 0.0, exceptionState);
}
void MIDIOutput::send(Vector<unsigned> unsignedData,
ExceptionState& exceptionState) {
send(unsignedData, 0.0, exceptionState);
}
DEFINE_TRACE(MIDIOutput) {
MIDIPort::trace(visitor);
}
} // namespace blink