Home | History | Annotate | Download | only in webmidi
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "modules/webmidi/MIDIOutput.h"
     33 
     34 #include "bindings/core/v8/ExceptionState.h"
     35 #include "core/dom/ExceptionCode.h"
     36 #include "core/dom/ExecutionContext.h"
     37 #include "core/frame/LocalDOMWindow.h"
     38 #include "core/timing/Performance.h"
     39 #include "modules/webmidi/MIDIAccess.h"
     40 
     41 namespace blink {
     42 
     43 namespace {
     44 
     45 double now(ExecutionContext* context)
     46 {
     47     LocalDOMWindow* window = context ? context->executingWindow() : 0;
     48     Performance* performance = window ? &window->performance() : 0;
     49     return performance ? performance->now() : 0.0;
     50 }
     51 
     52 class MessageValidator {
     53 public:
     54     static bool validate(Uint8Array* array, ExceptionState& exceptionState, bool sysexEnabled)
     55     {
     56         MessageValidator validator(array);
     57         return validator.process(exceptionState, sysexEnabled);
     58     }
     59 private:
     60     MessageValidator(Uint8Array* array)
     61         : m_data(array->data())
     62         , m_length(array->length())
     63         , m_offset(0) { }
     64 
     65     bool process(ExceptionState& exceptionState, bool sysexEnabled)
     66     {
     67         while (!isEndOfData() && acceptRealTimeMessages()) {
     68             if (!isStatusByte()) {
     69                 exceptionState.throwTypeError("Running status is not allowed " + getPositionString());
     70                 return false;
     71             }
     72             if (isEndOfSysex()) {
     73                 exceptionState.throwTypeError("Unexpected end of system exclusive message " + getPositionString());
     74                 return false;
     75             }
     76             if (isReservedStatusByte()) {
     77                 exceptionState.throwTypeError("Reserved status is not allowed " + getPositionString());
     78                 return false;
     79             }
     80             if (isSysex()) {
     81                 if (!sysexEnabled) {
     82                     exceptionState.throwDOMException(InvalidAccessError, "System exclusive message is not allowed " + getPositionString());
     83                     return false;
     84                 }
     85                 if (!acceptCurrentSysex()) {
     86                     if (isEndOfData())
     87                         exceptionState.throwTypeError("System exclusive message is not ended by end of system exclusive message.");
     88                     else
     89                         exceptionState.throwTypeError("System exclusive message contains a status byte " + getPositionString());
     90                     return false;
     91                 }
     92             } else {
     93                 if (!acceptCurrentMessage()) {
     94                     if (isEndOfData())
     95                         exceptionState.throwTypeError("Message is incomplete.");
     96                     else
     97                         exceptionState.throwTypeError("Unexpected status byte at index " + getPositionString());
     98                     return false;
     99                 }
    100             }
    101         }
    102         return true;
    103     }
    104 
    105 private:
    106     bool isEndOfData() { return m_offset >= m_length; }
    107     bool isSysex() { return m_data[m_offset] == 0xf0; }
    108     bool isSystemMessage() { return m_data[m_offset] >= 0xf0; }
    109     bool isEndOfSysex() { return m_data[m_offset] == 0xf7; }
    110     bool isRealTimeMessage() { return m_data[m_offset] >= 0xf8; }
    111     bool isStatusByte() { return m_data[m_offset] & 0x80; }
    112     bool isReservedStatusByte() { return m_data[m_offset] == 0xf4 || m_data[m_offset] == 0xf5 || m_data[m_offset] == 0xf9 || m_data[m_offset] == 0xfd; }
    113 
    114     bool acceptRealTimeMessages()
    115     {
    116         for (; !isEndOfData(); m_offset++) {
    117             if (isRealTimeMessage() && !isReservedStatusByte())
    118                 continue;
    119             return true;
    120         }
    121         return false;
    122     }
    123 
    124     bool acceptCurrentSysex()
    125     {
    126         ASSERT(isSysex());
    127         for (m_offset++; !isEndOfData(); m_offset++) {
    128             if (isReservedStatusByte())
    129                 return false;
    130             if (isRealTimeMessage())
    131                 continue;
    132             if (isEndOfSysex()) {
    133                 m_offset++;
    134                 return true;
    135             }
    136             if (isStatusByte())
    137                 return false;
    138         }
    139         return false;
    140     }
    141 
    142     bool acceptCurrentMessage()
    143     {
    144         ASSERT(isStatusByte());
    145         ASSERT(!isSysex());
    146         ASSERT(!isReservedStatusByte());
    147         ASSERT(!isRealTimeMessage());
    148         static const int channelMessageLength[7] = { 3, 3, 3, 3, 2, 2, 3 }; // for 0x8*, 0x9*, ..., 0xe*
    149         static const int systemMessageLength[7] = { 2, 3, 2, 0, 0, 1, 0 }; // for 0xf1, 0xf2, ..., 0xf7
    150         size_t length = isSystemMessage() ? systemMessageLength[m_data[m_offset] - 0xf1] : channelMessageLength[(m_data[m_offset] >> 4) - 8];
    151         size_t count = 1;
    152         for (m_offset++; !isEndOfData(); m_offset++) {
    153             if (isReservedStatusByte())
    154                 return false;
    155             if (isRealTimeMessage())
    156                 continue;
    157             if (isStatusByte())
    158                 return false;
    159             if (++count == length) {
    160                 m_offset++;
    161                 return true;
    162             }
    163         }
    164         return false;
    165     }
    166 
    167     String getPositionString() { return "at index " + String::number(m_offset) + " (" + String::number(m_data[m_offset]) + ")."; }
    168 
    169     const unsigned char* m_data;
    170     const size_t m_length;
    171     size_t m_offset;
    172 };
    173 
    174 } // namespace
    175 
    176 MIDIOutput* MIDIOutput::create(MIDIAccess* access, unsigned portIndex, const String& id, const String& manufacturer, const String& name, const String& version)
    177 {
    178     ASSERT(access);
    179     return adoptRefCountedGarbageCollectedWillBeNoop(new MIDIOutput(access, portIndex, id, manufacturer, name, version));
    180 }
    181 
    182 MIDIOutput::MIDIOutput(MIDIAccess* access, unsigned portIndex, const String& id, const String& manufacturer, const String& name, const String& version)
    183     : MIDIPort(access, id, manufacturer, name, MIDIPortTypeOutput, version)
    184     , m_portIndex(portIndex)
    185 {
    186 }
    187 
    188 MIDIOutput::~MIDIOutput()
    189 {
    190 }
    191 
    192 void MIDIOutput::send(Uint8Array* array, double timestamp, ExceptionState& exceptionState)
    193 {
    194     if (timestamp == 0.0)
    195         timestamp = now(executionContext());
    196 
    197     if (!array)
    198         return;
    199 
    200     if (MessageValidator::validate(array, exceptionState, midiAccess()->sysexEnabled()))
    201         midiAccess()->sendMIDIData(m_portIndex, array->data(), array->length(), timestamp);
    202 }
    203 
    204 void MIDIOutput::send(Vector<unsigned> unsignedData, double timestamp, ExceptionState& exceptionState)
    205 {
    206     if (timestamp == 0.0)
    207         timestamp = now(executionContext());
    208 
    209     RefPtr<Uint8Array> array = Uint8Array::create(unsignedData.size());
    210 
    211     for (size_t i = 0; i < unsignedData.size(); ++i) {
    212         if (unsignedData[i] > 0xff) {
    213             exceptionState.throwTypeError("The value at index " + String::number(i) + " (" + String::number(unsignedData[i]) + ") is greater than 0xFF.");
    214             return;
    215         }
    216         unsigned char value = unsignedData[i] & 0xff;
    217         array->set(i, value);
    218     }
    219 
    220     send(array.get(), timestamp, exceptionState);
    221 }
    222 
    223 void MIDIOutput::send(Uint8Array* data, ExceptionState& exceptionState)
    224 {
    225     send(data, 0.0, exceptionState);
    226 }
    227 
    228 void MIDIOutput::send(Vector<unsigned> unsignedData, ExceptionState& exceptionState)
    229 {
    230     send(unsignedData, 0.0, exceptionState);
    231 }
    232 
    233 void MIDIOutput::trace(Visitor* visitor)
    234 {
    235     MIDIPort::trace(visitor);
    236 }
    237 
    238 } // namespace blink
    239