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
now(ExecutionContext * context)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:
validate(Uint8Array * array,ExceptionState & exceptionState,bool sysexEnabled)54 static bool validate(Uint8Array* array, ExceptionState& exceptionState, bool sysexEnabled)
55 {
56 MessageValidator validator(array);
57 return validator.process(exceptionState, sysexEnabled);
58 }
59 private:
MessageValidator(Uint8Array * array)60 MessageValidator(Uint8Array* array)
61 : m_data(array->data())
62 , m_length(array->length())
63 , m_offset(0) { }
64
process(ExceptionState & exceptionState,bool sysexEnabled)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:
isEndOfData()106 bool isEndOfData() { return m_offset >= m_length; }
isSysex()107 bool isSysex() { return m_data[m_offset] == 0xf0; }
isSystemMessage()108 bool isSystemMessage() { return m_data[m_offset] >= 0xf0; }
isEndOfSysex()109 bool isEndOfSysex() { return m_data[m_offset] == 0xf7; }
isRealTimeMessage()110 bool isRealTimeMessage() { return m_data[m_offset] >= 0xf8; }
isStatusByte()111 bool isStatusByte() { return m_data[m_offset] & 0x80; }
isReservedStatusByte()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
acceptRealTimeMessages()114 bool acceptRealTimeMessages()
115 {
116 for (; !isEndOfData(); m_offset++) {
117 if (isRealTimeMessage() && !isReservedStatusByte())
118 continue;
119 return true;
120 }
121 return false;
122 }
123
acceptCurrentSysex()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
acceptCurrentMessage()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
getPositionString()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
create(MIDIAccess * access,unsigned portIndex,const String & id,const String & manufacturer,const String & name,const String & version)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
MIDIOutput(MIDIAccess * access,unsigned portIndex,const String & id,const String & manufacturer,const String & name,const String & version)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
~MIDIOutput()188 MIDIOutput::~MIDIOutput()
189 {
190 }
191
send(Uint8Array * array,double timestamp,ExceptionState & exceptionState)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
send(Vector<unsigned> unsignedData,double timestamp,ExceptionState & exceptionState)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
send(Uint8Array * data,ExceptionState & exceptionState)223 void MIDIOutput::send(Uint8Array* data, ExceptionState& exceptionState)
224 {
225 send(data, 0.0, exceptionState);
226 }
227
send(Vector<unsigned> unsignedData,ExceptionState & exceptionState)228 void MIDIOutput::send(Vector<unsigned> unsignedData, ExceptionState& exceptionState)
229 {
230 send(unsignedData, 0.0, exceptionState);
231 }
232
trace(Visitor * visitor)233 void MIDIOutput::trace(Visitor* visitor)
234 {
235 MIDIPort::trace(visitor);
236 }
237
238 } // namespace blink
239