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/. */
7 #include "mozilla/dom/MIDITypes.h"
8 #include "mozilla/dom/MIDIUtils.h"
9 #include "mozilla/UniquePtr.h"
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 static const uint8_t kReservedStatuses
[] = {0xf4, 0xf5, 0xf9, 0xfd};
26 namespace mozilla::dom::MIDIUtils
{
28 static bool IsSystemRealtimeMessage(uint8_t aByte
) {
29 return (aByte
& kSystemRealtimeMessage
) == kSystemRealtimeMessage
;
32 static bool IsCommandByte(uint8_t aByte
) {
33 return (aByte
& kCommandByte
) == kCommandByte
;
36 static bool IsReservedStatus(uint8_t aByte
) {
37 for (const auto& msg
: kReservedStatuses
) {
46 // Checks validity of MIDIMessage passed to it. Throws debug warnings and
47 // returns false if message is not valid.
48 bool IsValidMessage(const MIDIMessage
* aMsg
) {
49 if (aMsg
->data().Length() == 0) {
53 uint8_t cmd
= aMsg
->data()[0];
54 // If first byte isn't a command, something is definitely wrong.
55 if (!IsCommandByte(cmd
)) {
56 NS_WARNING("Constructed a MIDI packet where first byte is not command!");
60 if (IsReservedStatus(cmd
)) {
61 NS_WARNING("Using a reserved message");
65 if (cmd
== kSysexMessageStart
) {
66 // All we can do with sysex is make sure it starts and ends with the
67 // correct command bytes and that it does not contain other command bytes.
68 if (aMsg
->data()[aMsg
->data().Length() - 1] != kSysexMessageEnd
) {
69 NS_WARNING("Last byte of Sysex Message not 0xF7!");
73 for (size_t i
= 1; i
< aMsg
->data().Length() - 2; i
++) {
74 if (IsCommandByte(aMsg
->data()[i
])) {
81 // For system realtime messages, the length should always be 1.
82 if (IsSystemRealtimeMessage(cmd
)) {
83 return aMsg
->data().Length() == 1;
85 // Otherwise, just use the correct array for testing lengths. We can't tell
86 // much about message validity other than that.
87 if ((cmd
& kSystemMessage
) == kSystemMessage
) {
88 if (cmd
- kSystemMessage
>=
89 static_cast<uint8_t>(std::size(kSystemLengths
))) {
90 NS_WARNING("System Message Command byte not valid!");
93 return aMsg
->data().Length() == kSystemLengths
[cmd
- kSystemMessage
];
95 // For non system commands, we only care about differences in the high nibble
96 // of the first byte. Shift this down to give the index of the expected packet
98 uint8_t cmdIndex
= (cmd
- kCommandByte
) >> 4;
99 if (cmdIndex
>= std::size(kCommandLengths
)) {
100 // If our index is bigger than our array length, command byte is unknown;
101 NS_WARNING("Unknown MIDI command!");
104 return aMsg
->data().Length() == kCommandLengths
[cmdIndex
];
107 bool ParseMessages(const nsTArray
<uint8_t>& aByteBuffer
,
108 const TimeStamp
& aTimestamp
,
109 nsTArray
<MIDIMessage
>& aMsgArray
) {
110 bool inSysexMessage
= false;
111 UniquePtr
<MIDIMessage
> currentMsg
= nullptr;
112 for (const auto& byte
: aByteBuffer
) {
113 if (IsSystemRealtimeMessage(byte
)) {
115 rt_msg
.data().AppendElement(byte
);
116 rt_msg
.timestamp() = aTimestamp
;
117 if (!IsValidMessage(&rt_msg
)) {
120 aMsgArray
.AppendElement(rt_msg
);
124 if (byte
== kSysexMessageEnd
) {
125 if (!inSysexMessage
) {
127 "Got sysex message end with no sysex message being processed!");
130 inSysexMessage
= false;
131 } else if (IsCommandByte(byte
)) {
133 if (!IsValidMessage(currentMsg
.get())) {
137 aMsgArray
.AppendElement(*currentMsg
);
140 currentMsg
= MakeUnique
<MIDIMessage
>();
141 currentMsg
->timestamp() = aTimestamp
;
145 NS_WARNING("No command byte has been encountered yet!");
149 currentMsg
->data().AppendElement(byte
);
151 if (byte
== kSysexMessageStart
) {
152 inSysexMessage
= true;
157 if (!IsValidMessage(currentMsg
.get())) {
160 aMsgArray
.AppendElement(*currentMsg
);
166 bool IsSysexMessage(const MIDIMessage
& aMsg
) {
167 if (aMsg
.data().Length() == 0) {
170 return aMsg
.data()[0] == kSysexMessageStart
;
172 } // namespace mozilla::dom::MIDIUtils