LCOV - code coverage report
Current view: top level - src/MIDI_Parsers - MIDI_MessageTypes.hpp (source / functions) Hit Total Coverage
Test: b8a30b4b7040ae1abf162fd0a258beaa2de43626 Lines: 83 87 95.4 %
Date: 2024-12-21 21:28:55 Functions: 48 49 98.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.15