LCOV - code coverage report
Current view: top level - src/MIDI_Parsers - MIDI_MessageTypes.hpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 95.4 % 87 83
Test Date: 2026-06-06 17:44:35 Functions: 98.0 % 49 48
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 2.4-beta