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