| /* |
| * 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 |