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