Line data Source code
1 : #pragma once
2 :
3 : #include <AH/Arduino-Wrapper.h> // Print
4 : #include <AH/STL/cstddef> // size_t
5 : #include <AH/STL/vector>
6 : #include <AH/Settings/Warnings.hpp>
7 : #include <Settings/NamespaceSettings.hpp>
8 :
9 : #ifndef ARDUINO
10 : #include <iostream>
11 : #endif
12 :
13 : AH_DIAGNOSTIC_WERROR()
14 :
15 : #include <Def/Cable.hpp>
16 : #include <Def/Channel.hpp>
17 : #include <Def/MIDIAddress.hpp>
18 :
19 : BEGIN_CS_NAMESPACE
20 :
21 : // -------------------------------------------------------------------------- //
22 :
23 : /// All possible MIDI status byte values (without channel).
24 : enum class MIDIMessageType : uint8_t {
25 : NONE = 0x00,
26 : /* Channel Voice Messages */
27 : NOTE_OFF = 0x80, // 3B
28 : NOTE_ON = 0x90, // 3B
29 : KEY_PRESSURE = 0xA0, // 3B
30 : CC = 0xB0, // 3B
31 : CONTROL_CHANGE = CC, // 3B
32 : PROGRAM_CHANGE = 0xC0, // 2B
33 : CHANNEL_PRESSURE = 0xD0, // 2B
34 : PITCH_BEND = 0xE0, // 3B
35 :
36 : SYSEX_START = 0xF0,
37 :
38 : /* System Common messages */
39 : MTC_QUARTER_FRAME = 0xF1,
40 : SONG_POSITION_POINTER = 0xF2,
41 : SONG_SELECT = 0xF3,
42 : UNDEFINED_SYSCOMMON_1 = 0xF4,
43 : UNDEFINED_SYSCOMMON_2 = 0xF5,
44 : TUNE_REQUEST = 0xF6,
45 : SYSEX_END = 0xF7,
46 :
47 : /* System Real-Time messages */
48 : TIMING_CLOCK = 0xF8,
49 : UNDEFINED_REALTIME_1 = 0xF9,
50 : START = 0xFA,
51 : CONTINUE = 0xFB,
52 : STOP = 0xFC,
53 : UNDEFINED_REALTIME_2 = 0xFD,
54 : ACTIVE_SENSING = 0xFE,
55 : SYSTEM_RESET = 0xFF,
56 : };
57 :
58 : /// MIDI USB Code Index Numbers.
59 : ///
60 : /// @see Table 4-1 in <https://usb.org/sites/default/files/midi10.pdf>.
61 : enum class MIDICodeIndexNumber : uint8_t {
62 : MISC_FUNCTION_CODES = 0x0,
63 : CABLE_EVENTS = 0x1,
64 : SYSTEM_COMMON_2B = 0x2,
65 : SYSTEM_COMMON_3B = 0x3,
66 : SYSEX_START_CONT = 0x4,
67 : SYSTEM_COMMON_1B = 0x5,
68 : SYSEX_END_1B = 0x5,
69 : SYSEX_END_2B = 0x6,
70 : SYSEX_END_3B = 0x7,
71 :
72 : NOTE_OFF = 0x8,
73 : NOTE_ON = 0x9,
74 : KEY_PRESSURE = 0xA,
75 : CONTROL_CHANGE = 0xB,
76 : PROGRAM_CHANGE = 0xC,
77 : CHANNEL_PRESSURE = 0xD,
78 : PITCH_BEND = 0xE,
79 :
80 : SINGLE_BYTE = 0xF,
81 : };
82 :
83 : // -------------------------------------------------------------------------- //
84 :
85 : struct MIDIMessage {
86 : /// Constructor.
87 636 : MIDIMessage(uint8_t header, uint8_t data1, uint8_t data2,
88 : Cable cable = CABLE_1)
89 636 : : header(header), data1(data1), data2(data2), cable(cable) {}
90 :
91 : /// Constructor.
92 7 : MIDIMessage(MIDIMessageType header, uint8_t data1, uint8_t data2,
93 : Cable cable = CABLE_1)
94 7 : : header(uint8_t(header)), data1(data1), data2(data2), cable(cable) {}
95 :
96 : uint8_t header; ///< MIDI status byte (message type and channel).
97 : uint8_t data1; ///< First MIDI data byte
98 : uint8_t data2; ///< First MIDI data byte
99 :
100 : Cable cable; ///< USB MIDI cable number;
101 :
102 : /// Check for equality.
103 189 : bool operator==(MIDIMessage other) const {
104 370 : return this->header == other.header && this->data1 == other.data1 &&
105 370 : this->data2 == other.data2 && this->cable == other.cable;
106 : }
107 : /// Check for inequality.
108 : bool operator!=(MIDIMessage other) const { return !(*this == other); }
109 :
110 : /// Get the MIDI message type.
111 : MIDIMessageType getMessageType() const {
112 : if (hasValidChannelMessageHeader()) {
113 : return static_cast<MIDIMessageType>(header & 0xF0);
114 : } else {
115 : return static_cast<MIDIMessageType>(header);
116 : }
117 : }
118 : /// Set the MIDI message type.
119 : /// @note Do not use this version for Channel Messages, it doesn't
120 : /// correctly handle the channel.
121 : void setMessageType(MIDIMessageType type) {
122 : header = static_cast<uint8_t>(type);
123 : }
124 :
125 : /// Get the first data byte.
126 107 : uint8_t getData1() const { return data1; }
127 : /// Get the second data byte.
128 132 : uint8_t getData2() const { return data2; }
129 : /// Set the first data byte.
130 : void setData1(uint8_t data) { data1 = data; }
131 : /// Set the second data byte.
132 : void setData2(uint8_t data) { data2 = data; }
133 :
134 : /// Get the MIDI USB cable number of the message.
135 28 : Cable getCable() const { return cable; }
136 : /// Set the MIDI USB cable number of the message.
137 : void setCable(Cable cable) { this->cable = cable; }
138 :
139 : /// Check whether the header is a valid header for a channel message.
140 944 : bool hasValidChannelMessageHeader() const {
141 1888 : return header >= (uint8_t(MIDIMessageType::NOTE_OFF) | 0x00) &&
142 1888 : header <= (uint8_t(MIDIMessageType::PITCH_BEND) | 0x0F);
143 : }
144 :
145 : /// Check whether the header is a valid header for a System Common message.
146 : /// @note SysExEnd is considered a System Common message by the MIDI
147 : /// Standard, SysExStart is not.
148 : /// @note Reserved System Common messages are also considered valid System
149 : /// Common messages.
150 732 : bool hasValidSystemCommonHeader() const {
151 732 : return (header & 0xF8) == 0xF0 && header != 0xF0;
152 : }
153 :
154 : /// If Data 1 and Data 2 represent a single 14-bit number, you can use this
155 : /// method to retrieve that number.
156 2 : uint16_t getData14bit() const {
157 2 : return data1 | (uint16_t(data2) << uint16_t(7));
158 : }
159 : /// If Data 1 and Data 2 represent a single 14-bit number, you can use this
160 : /// method to set that number.
161 : void setData14bit(uint16_t data) {
162 : data1 = (data >> 0) & 0x7F;
163 : data2 = (data >> 7) & 0x7F;
164 : }
165 :
166 : /// Make sure that the status byte has the most significant bit set and
167 : /// the data bytes have the most significant bits cleared.
168 142 : void sanitize() {
169 142 : header |= 0x80;
170 142 : data1 &= 0x7F;
171 142 : data2 &= 0x7F;
172 142 : }
173 : };
174 :
175 : struct ChannelMessage : MIDIMessage {
176 : using MIDIMessage::MIDIMessage;
177 :
178 : /// Constructor.
179 337 : ChannelMessage(MIDIMessageType type, Channel channel, uint8_t data1,
180 : uint8_t data2 = 0x00, Cable cable = CABLE_1)
181 337 : : MIDIMessage(uint8_t(type) | channel.getRaw(), data1, data2, cable) {}
182 :
183 96 : explicit ChannelMessage(const MIDIMessage &msg) : MIDIMessage(msg) {}
184 :
185 : /// Get the MIDI message type.
186 219 : MIDIMessageType getMessageType() const {
187 219 : return static_cast<MIDIMessageType>(header & 0xF0);
188 : }
189 : /// Set the MIDI message type.
190 : void setMessageType(MIDIMessageType type) {
191 : header &= 0x0F;
192 : header |= static_cast<uint8_t>(type) & 0xF0;
193 : }
194 :
195 : /// Get the MIDI channel of the message.
196 223 : Channel getChannel() const { return Channel(header & 0x0F); }
197 : /// Set the MIDI channel of the message.
198 1 : void setChannel(Channel channel) {
199 1 : header &= 0xF0;
200 1 : header |= channel.getRaw();
201 1 : }
202 :
203 : /// Get the MIDI channel and cable number.
204 : /// @note Valid for all MIDI Channel messages, including Channel Pressure
205 : /// and Pitch Bend.
206 211 : MIDIChannelCable getChannelCable() const { return {getChannel(), cable}; }
207 : /// Get the MIDI address of this message, using `data1` as the address.
208 : /// @note Don't use this for Channel Pressure or Pitch Bend messages,
209 : /// as `data1` will have a different meaning in those cases.
210 188 : MIDIAddress getAddress() const { return {data1, getChannelCable()}; }
211 :
212 : /// Check whether this message has one or two data bytes.
213 : ///
214 : /// - 2 data bytes: Note On/Off, Aftertouch, Control Change or Pitch Bend
215 : /// - 1 data byte: Program Change or Channel Pressure
216 : ///
217 : /// Returns false if the header is a SysEx, Real-Time or System Common byte.
218 76 : bool hasTwoDataBytes() const {
219 76 : auto type = getMessageType();
220 76 : return type <= MIDIMessageType::CONTROL_CHANGE ||
221 76 : type == MIDIMessageType::PITCH_BEND;
222 : }
223 :
224 : constexpr static auto NOTE_OFF = MIDIMessageType::NOTE_OFF;
225 : constexpr static auto NOTE_ON = MIDIMessageType::NOTE_ON;
226 : constexpr static auto KEY_PRESSURE = MIDIMessageType::KEY_PRESSURE;
227 : constexpr static auto CC = MIDIMessageType::CC;
228 : constexpr static auto CONTROL_CHANGE = MIDIMessageType::CONTROL_CHANGE;
229 : constexpr static auto PROGRAM_CHANGE = MIDIMessageType::PROGRAM_CHANGE;
230 : constexpr static auto CHANNEL_PRESSURE = MIDIMessageType::CHANNEL_PRESSURE;
231 : constexpr static auto PITCH_BEND = MIDIMessageType::PITCH_BEND;
232 : };
233 :
234 : struct SysCommonMessage : MIDIMessage {
235 : using MIDIMessage::MIDIMessage;
236 :
237 : /// Constructor.
238 7 : SysCommonMessage(MIDIMessageType type, uint8_t data1 = 0x00,
239 : uint8_t data2 = 0x00, Cable cable = CABLE_1)
240 7 : : MIDIMessage(type, data1, data2, cable) {}
241 : /// Constructor.
242 3 : SysCommonMessage(MIDIMessageType type, uint8_t data1, Cable cable)
243 3 : : SysCommonMessage(type, data1, 0x00, cable) {}
244 : /// Constructor.
245 1 : SysCommonMessage(MIDIMessageType type, Cable cable)
246 1 : : SysCommonMessage(type, 0x00, 0x00, cable) {}
247 :
248 41 : explicit SysCommonMessage(const MIDIMessage &msg) : MIDIMessage(msg) {}
249 :
250 : /// Get the MIDI message type.
251 55 : MIDIMessageType getMessageType() const {
252 55 : return static_cast<MIDIMessageType>(header);
253 : }
254 :
255 : /// Get the number of data bytes of this type of System Common message.
256 29 : uint8_t getNumberOfDataBytes() const {
257 29 : if (getMessageType() == MIDIMessageType::SONG_POSITION_POINTER)
258 10 : return 2;
259 19 : else if (getMessageType() <= MIDIMessageType::SONG_SELECT)
260 17 : return 1;
261 : else
262 2 : return 0;
263 : }
264 :
265 : constexpr static auto MTC_QUARTER_FRAME =
266 : MIDIMessageType::MTC_QUARTER_FRAME;
267 : constexpr static auto SONG_POSITION_POINTER =
268 : MIDIMessageType::SONG_POSITION_POINTER;
269 : constexpr static auto SONG_SELECT = MIDIMessageType::SONG_SELECT;
270 : constexpr static auto UNDEFINED_SYSCOMMON_1 =
271 : MIDIMessageType::UNDEFINED_SYSCOMMON_1;
272 : constexpr static auto UNDEFINED_SYSCOMMON_2 =
273 : MIDIMessageType::UNDEFINED_SYSCOMMON_2;
274 : constexpr static auto TUNE_REQUEST = MIDIMessageType::TUNE_REQUEST;
275 : };
276 :
277 : struct SysExMessage {
278 : /// Constructor.
279 2 : SysExMessage() : data(nullptr), length(0), cable(CABLE_1) {}
280 :
281 : /// Constructor.
282 157 : SysExMessage(const uint8_t *data, uint16_t length, Cable cable = CABLE_1)
283 157 : : data(data), length(length), cable(cable.getRaw()) {}
284 :
285 : /// Constructor.
286 28 : SysExMessage(const std::vector<uint8_t> &vec, Cable cable = CABLE_1)
287 28 : : SysExMessage(vec.data(), vec.size(), cable) {}
288 :
289 : /// Constructor.
290 : template <uint16_t N>
291 32 : SysExMessage(const uint8_t (&array)[N], Cable cable = CABLE_1)
292 32 : : SysExMessage(array, N, cable) {}
293 :
294 : const uint8_t *data;
295 : uint16_t length;
296 :
297 : Cable cable;
298 :
299 49 : bool operator==(SysExMessage other) const {
300 98 : return this->length == other.length && this->cable == other.cable &&
301 49 : (this->length == 0 ||
302 91 : memcmp(this->data, other.data, length) == 0);
303 : }
304 : bool operator!=(SysExMessage other) const { return !(*this == other); }
305 :
306 : /// Get the MIDI USB cable number of the message.
307 17 : Cable getCable() const { return cable; }
308 : /// Set the MIDI USB cable number of the message.
309 : void setCable(Cable cable) { this->cable = cable; }
310 :
311 47 : bool isFirstChunk() const {
312 47 : return length >= 1 && data[0] == uint8_t(MIDIMessageType::SYSEX_START);
313 : }
314 :
315 47 : bool isLastChunk() const {
316 94 : return length >= 1 &&
317 94 : data[length - 1] == uint8_t(MIDIMessageType::SYSEX_END);
318 : }
319 :
320 21 : bool isCompleteMessage() const { return isFirstChunk() && isLastChunk(); }
321 :
322 : constexpr static auto SYSEX_START = MIDIMessageType::SYSEX_START;
323 : constexpr static auto SYSEX_END = MIDIMessageType::SYSEX_END;
324 : };
325 :
326 : struct RealTimeMessage {
327 : /// Constructor.
328 1991 : RealTimeMessage(uint8_t message, Cable cable = CABLE_1)
329 1991 : : message(message), cable(cable.getRaw()) {}
330 :
331 : /// Constructor.
332 10 : RealTimeMessage(MIDIMessageType message, Cable cable = CABLE_1)
333 10 : : message(uint8_t(message)), cable(cable.getRaw()) {}
334 :
335 : uint8_t message;
336 : Cable cable;
337 :
338 30 : bool operator==(RealTimeMessage other) const {
339 30 : return this->message == other.message && this->cable == other.cable;
340 : }
341 : bool operator!=(RealTimeMessage other) const { return !(*this == other); }
342 :
343 : /// Set the MIDI message type.
344 : void setMessageType(MIDIMessageType type) {
345 : message = static_cast<uint8_t>(type);
346 : }
347 : /// Get the MIDI message type.
348 2 : MIDIMessageType getMessageType() const {
349 2 : return static_cast<MIDIMessageType>(message);
350 : }
351 :
352 : /// Get the MIDI USB cable number of the message.
353 4 : Cable getCable() const { return cable; }
354 : /// Set the MIDI USB cable number of the message.
355 : void setCable(Cable cable) { this->cable = cable; }
356 :
357 : /// Check whether the header is a valid header for a Real-Time message.
358 10 : bool isValid() const { return message >= 0xF8; }
359 :
360 : constexpr static auto TIMING_CLOCK = MIDIMessageType::TIMING_CLOCK;
361 : constexpr static auto UNDEFINED_REALTIME_1 =
362 : MIDIMessageType::UNDEFINED_REALTIME_1;
363 : constexpr static auto START = MIDIMessageType::START;
364 : constexpr static auto CONTINUE = MIDIMessageType::CONTINUE;
365 : constexpr static auto STOP = MIDIMessageType::STOP;
366 : constexpr static auto UNDEFINED_REALTIME_2 =
367 : MIDIMessageType::UNDEFINED_REALTIME_2;
368 : constexpr static auto ACTIVE_SENSING = MIDIMessageType::ACTIVE_SENSING;
369 : constexpr static auto RESET = MIDIMessageType::SYSTEM_RESET;
370 : };
371 :
372 : #ifndef ARDUINO
373 0 : inline std::ostream &operator<<(std::ostream &os, SysExMessage m) {
374 0 : os << "SysExMessage [" << m.length << "] " << AH::HexDump(m.data, m.length)
375 0 : << " (cable " << m.cable.getOneBased() << ")";
376 0 : return os;
377 : }
378 : #endif
379 :
380 : inline Print &operator<<(Print &os, SysExMessage m) {
381 : os << "SysExMessage [" << m.length << "] " << AH::HexDump(m.data, m.length)
382 : << " (cable " << m.cable.getOneBased() << ")";
383 : return os;
384 : }
385 :
386 : FlashString_t enum_to_string(MIDIMessageType);
387 6 : inline Print &operator<<(Print &os, MIDIMessageType m) {
388 6 : return os << enum_to_string(m);
389 : }
390 :
391 : END_CS_NAMESPACE
392 :
393 : AH_DIAGNOSTIC_POP()
|