LCOV - code coverage report
Current view: top level - src/MIDI_Parsers - MIDI_MessageTypes.hpp (source / functions) Hit Total Coverage
Test: ffed98f648fe78e7aa7bdd228474317d40dadbec Lines: 83 87 95.4 %
Date: 2022-05-28 15:22:59 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/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()

Generated by: LCOV version 1.15