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