1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/dom/MIDITypes.h"
8 #include "mozilla/dom/MIDIUtils.h"
9 #include "mozilla/UniquePtr.h"
10
11 // Taken from MIDI IMPLEMENTATION CHART INSTRUCTIONS, MIDI Spec v1.0, Pg. 97
12 static const uint8_t kCommandByte = 0x80;
13 static const uint8_t kSysexMessageStart = 0xF0;
14 static const uint8_t kSystemMessage = 0xF0;
15 static const uint8_t kSysexMessageEnd = 0xF7;
16 static const uint8_t kSystemRealtimeMessage = 0xF8;
17 // Represents the length of all possible command messages.
18 // Taken from MIDI Spec, Pg. 101v 1.0, Table 2
19 static const uint8_t kCommandLengths[] = {3, 3, 3, 3, 2, 2, 3};
20 // Represents the length of all possible system messages. The length of sysex
21 // messages is variable, so we just put zero since it won't be checked anyways.
22 // Taken from MIDI Spec v1.0, Pg. 105, Table 5
23 static const uint8_t kSystemLengths[] = {0, 2, 3, 2, 1, 1, 1, 1};
24
25 namespace mozilla {
26 namespace dom {
27 namespace MIDIUtils {
28
29 // Checks validity of MIDIMessage passed to it. Throws debug warnings and
30 // returns false if message is not valid.
IsValidMessage(const MIDIMessage * aMsg)31 bool IsValidMessage(const MIDIMessage* aMsg) {
32 if (NS_WARN_IF(!aMsg)) {
33 return false;
34 }
35 // Assert on parser problems
36 MOZ_ASSERT(aMsg->data().Length() > 0,
37 "Created a MIDI Message of Length 0. This should never happen!");
38 uint8_t cmd = aMsg->data()[0];
39 // If first byte isn't a command, something is definitely wrong.
40 MOZ_ASSERT((cmd & kCommandByte) == kCommandByte,
41 "Constructed a MIDI packet where first byte is not command!");
42 if (cmd == kSysexMessageStart) {
43 // All we can do with sysex is make sure it starts and ends with the
44 // correct command bytes.
45 if (aMsg->data()[aMsg->data().Length() - 1] != kSysexMessageEnd) {
46 NS_WARNING("Last byte of Sysex Message not 0xF7!");
47 return false;
48 }
49 return true;
50 }
51 // For system realtime messages, the length should always be 1.
52 if ((cmd & kSystemRealtimeMessage) == kSystemRealtimeMessage) {
53 return aMsg->data().Length() == 1;
54 }
55 // Otherwise, just use the correct array for testing lengths. We can't tell
56 // much about message validity other than that.
57 if ((cmd & kSystemMessage) == kSystemMessage) {
58 if (cmd - kSystemMessage >=
59 static_cast<uint8_t>(ArrayLength(kSystemLengths))) {
60 NS_WARNING("System Message Command byte not valid!");
61 return false;
62 }
63 return aMsg->data().Length() == kSystemLengths[cmd - kSystemMessage];
64 }
65 // For non system commands, we only care about differences in the high nibble
66 // of the first byte. Shift this down to give the index of the expected packet
67 // length.
68 uint8_t cmdIndex = (cmd - kCommandByte) >> 4;
69 if (cmdIndex >= ArrayLength(kCommandLengths)) {
70 // If our index is bigger than our array length, command byte is unknown;
71 NS_WARNING("Unknown MIDI command!");
72 return false;
73 }
74 return aMsg->data().Length() == kCommandLengths[cmdIndex];
75 }
76
ParseMessages(const nsTArray<uint8_t> & aByteBuffer,const TimeStamp & aTimestamp,nsTArray<MIDIMessage> & aMsgArray)77 uint32_t ParseMessages(const nsTArray<uint8_t>& aByteBuffer,
78 const TimeStamp& aTimestamp,
79 nsTArray<MIDIMessage>& aMsgArray) {
80 uint32_t bytesRead = 0;
81 bool inSysexMessage = false;
82 UniquePtr<MIDIMessage> currentMsg;
83 for (auto& byte : aByteBuffer) {
84 bytesRead++;
85 if ((byte & kSystemRealtimeMessage) == kSystemRealtimeMessage) {
86 MIDIMessage rt_msg;
87 rt_msg.data().AppendElement(byte);
88 rt_msg.timestamp() = aTimestamp;
89 aMsgArray.AppendElement(rt_msg);
90 continue;
91 }
92 if (byte == kSysexMessageEnd) {
93 if (!inSysexMessage) {
94 MOZ_ASSERT(inSysexMessage);
95 NS_WARNING(
96 "Got sysex message end with no sysex message being processed!");
97 }
98 inSysexMessage = false;
99 } else if (byte & kCommandByte) {
100 if (currentMsg && IsValidMessage(currentMsg.get())) {
101 aMsgArray.AppendElement(*currentMsg);
102 }
103 currentMsg = MakeUnique<MIDIMessage>();
104 currentMsg->timestamp() = aTimestamp;
105 }
106 currentMsg->data().AppendElement(byte);
107 if (byte == kSysexMessageStart) {
108 inSysexMessage = true;
109 }
110 }
111 if (currentMsg && IsValidMessage(currentMsg.get())) {
112 aMsgArray.AppendElement(*currentMsg);
113 }
114 return bytesRead;
115 }
116
IsSysexMessage(const MIDIMessage & aMsg)117 bool IsSysexMessage(const MIDIMessage& aMsg) {
118 if (aMsg.data().Length() == 0) {
119 return false;
120 }
121 if (aMsg.data()[0] == kSysexMessageStart) {
122 return true;
123 }
124 return false;
125 }
126 } // namespace MIDIUtils
127 } // namespace dom
128 } // namespace mozilla
129