LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces - USBMIDI_Sender.hpp (source / functions) Hit Total Coverage
Test: 91b605873905a6fcb78324052c97dbac10849539 Lines: 52 67 77.6 %
Date: 2022-11-08 01:34:37 Functions: 6 7 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <MIDI_Parsers/MIDI_MessageTypes.hpp>
       4             : #include <Settings/SettingsWrapper.hpp>
       5             : 
       6             : AH_DIAGNOSTIC_WERROR()
       7             : 
       8             : BEGIN_CS_NAMESPACE
       9             : 
      10             : /**
      11             :  * @brief   A class for sending MIDI USB messages. Includes support for chunked
      12             :  *          MIDI USB SysEx messages.
      13             :  */
      14             : class USBMIDI_Sender {
      15             :   public:
      16             :     /// Send a MIDI Channel message using the given sender.
      17             :     template <class Send>
      18             :     void sendChannelMessage(ChannelMessage, Send &&send);
      19             : 
      20             :     /// Send a MIDI System Common message using the given sender.
      21             :     template <class Send>
      22             :     void sendSysCommonMessage(SysCommonMessage, Send &&send);
      23             : 
      24             :     /// Send a MIDI Real-Time message using the given sender.
      25             :     template <class Send>
      26             :     void sendRealTimeMessage(RealTimeMessage, Send &&send);
      27             : 
      28             :     /// Send a MIDI System Exclusive message using the given sender.
      29             :     /// Message may be chunked, remaining bytes are stored until next chunk is
      30             :     /// sent.
      31             :     template <class Send>
      32             :     void sendSysEx(SysExMessage, Send &&send);
      33             : 
      34             :     /// Send a MIDI System Exclusive message using the given sender.
      35             :     /// Message should be complete, i.e. from SysEx Start to SysEx End.
      36             :     template <class Send>
      37             :     void sendFullSysEx(SysExMessage, Send &&send);
      38             : 
      39             : #if !NO_SYSEX_OUTPUT
      40             :   private:
      41             :     /// Send a single SysEx starts or continues USB packet. Exactly 3 bytes are
      42             :     /// sent. The `data` pointer is not incremented.
      43             :     template <class Send>
      44             :     void sendSysExStartCont1(const uint8_t *data, Cable cable, Send &send);
      45             :     /// Send as many SysEx starts or continues USB packets, such that the
      46             :     /// remaining length is 3, 2 or 1 byte. The `data` pointer is incremented,
      47             :     /// and the `length` is decremented.
      48             :     /// The reason for leaving 3, 2 or 1 bytes remaining is so the message can
      49             :     /// be finished using a SysExEnd USB packet, which has to have 3, 2 or 1
      50             :     /// bytes.
      51             :     template <class Send>
      52             :     void sendSysExStartCont(const uint8_t *&data, uint16_t &length, Cable cable,
      53             :                             Send &send);
      54             :     /// Send a SysExEnd USB packet. The `length` should be either 3, 2 or 1
      55             :     /// bytes, and the last byte of `data` should be a SysExEnd byte.
      56             :     template <class Send>
      57             :     void sendSysExEnd(const uint8_t *data, uint16_t length, Cable cable,
      58             :                       Send &send);
      59             : 
      60             :   private:
      61             :     /// Stores remainder of outgoing SysEx chunks. Each USB packet (except the
      62             :     /// last one) should contain a multiple of 3 SysEx bytes. If the SysEx chunk
      63             :     /// size is not a multiple of 3, there will be remaining bytes that can't be
      64             :     /// sent yet, until the next chunk arrives. See the comments in the
      65             :     /// @ref USBMIDI_Sender::sendSysEx() implementation for more details.
      66             :     uint8_t storedSysExData[16][3];
      67             :     /// Number of remaining SysEx bytes stored.
      68             :     uint8_t storedSysExLength[16] = {};
      69             : #endif
      70             : 
      71             :   private:
      72             :     using CIN = MIDICodeIndexNumber;
      73             : };
      74             : 
      75             : template <class Send>
      76           3 : void USBMIDI_Sender::sendChannelMessage(ChannelMessage msg, Send &&send) {
      77           3 :     send(msg.cable, CIN(msg.header >> 4), // CN|CIN
      78           3 :          msg.header,                      // status
      79           3 :          msg.data1,                       // data 1
      80           3 :          msg.data2);                      // data 2
      81           3 : }
      82             : 
      83             : template <class Send>
      84           0 : void USBMIDI_Sender::sendSysCommonMessage(SysCommonMessage msg, Send &&send) {
      85           0 :     auto cn = msg.cable;
      86           0 :     switch (msg.getNumberOfDataBytes()) {
      87           0 :         case 2: // 3B
      88           0 :             send(cn, CIN::SYSTEM_COMMON_3B, msg.header, msg.data1, msg.data2);
      89           0 :             break;
      90           0 :         case 1: // 2B
      91           0 :             send(cn, CIN::SYSTEM_COMMON_2B, msg.header, msg.data1, 0);
      92           0 :             break;
      93           0 :         case 0: // 1B
      94           0 :             send(cn, CIN::SYSTEM_COMMON_1B, msg.header, 0, 0);
      95           0 :             break;
      96           0 :         default: break;
      97             :     }
      98           0 : }
      99             : 
     100             : template <class Send>
     101           1 : void USBMIDI_Sender::sendRealTimeMessage(RealTimeMessage msg, Send &&send) {
     102           1 :     send(msg.cable, CIN::SINGLE_BYTE, msg.message, 0, 0);
     103           1 : }
     104             : 
     105             : // This is the readable documentation version for sending full SysEx messages,
     106             : // the chunked version below is more complicated, but the principle is similar.
     107             : template <class Send>
     108             : void USBMIDI_Sender::sendFullSysEx(SysExMessage msg, Send &&send) {
     109             :     size_t length = msg.length;
     110             :     const uint8_t *data = msg.data;
     111             :     Cable cn = msg.cable;
     112             :     while (length > 3) {
     113             :         send(cn, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
     114             :         data += 3;
     115             :         length -= 3;
     116             :     }
     117             :     switch (length) {
     118             :         case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
     119             :         case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
     120             :         case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
     121             :         default: break;
     122             :     }
     123             : }
     124             : 
     125             : #if !NO_SYSEX_OUTPUT
     126             : 
     127             : template <class Send>
     128          15 : void USBMIDI_Sender::sendSysExStartCont1(const uint8_t *data, Cable cable,
     129             :                                          Send &send) {
     130          15 :     send(cable, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
     131          15 : }
     132             : 
     133             : template <class Send>
     134          11 : void USBMIDI_Sender::sendSysExStartCont(const uint8_t *&data, uint16_t &length,
     135             :                                         Cable cable, Send &send) {
     136          11 :     while (length > 3) {
     137           3 :         sendSysExStartCont1(data, cable, send);
     138           3 :         data += 3;
     139           3 :         length -= 3;
     140             :     }
     141           8 : }
     142             : 
     143             : template <class Send>
     144           9 : void USBMIDI_Sender::sendSysExEnd(const uint8_t *data, uint16_t length,
     145             :                                   Cable cn, Send &send) {
     146           9 :     switch (length) {
     147           4 :         case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
     148           3 :         case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
     149           2 :         case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
     150             :         default: break; // LCOV_EXCL_LINE
     151             :     }
     152           9 : }
     153             : 
     154             : template <class Send>
     155          19 : void USBMIDI_Sender::sendSysEx(const SysExMessage msg, Send &&send) {
     156             :     // Don't bother trying to send empty messages
     157          19 :     if (msg.length == 0)
     158           0 :         return;
     159             : 
     160          19 :     Cable cable = msg.cable;
     161          19 :     uint8_t c = cable.getRaw();
     162          19 :     uint16_t length = msg.length;
     163          19 :     const uint8_t *data = msg.data;
     164             : 
     165             :     // Even if the previous chunk wasn't terminated correctly, if this is a new
     166             :     // SysEx message, always forget the previous unsent chunk.
     167          19 :     if (msg.isFirstChunk()) {
     168             :         // TODO: send a SysExEnd for previous chunk?
     169          10 :         storedSysExLength[c] = 0;
     170             :     }
     171             : 
     172             :     // Complete the previous unsent chunk to (at most) 3 bytes
     173          60 :     while (length > 0 && storedSysExLength[c] < 3) {
     174          41 :         storedSysExData[c][storedSysExLength[c]++] = *data++;
     175          41 :         --length;
     176             :     }
     177             : 
     178             :     // If all bytes of the new chunk were used, there are <= 3 stored bytes
     179          19 :     if (length == 0) {
     180             :         // If this chunk is the last one, terminate the SysEx (termination can
     181             :         // have 1, 2 or 3 bytes, so no need to store anything)
     182          11 :         if (msg.isLastChunk()) {
     183           3 :             sendSysExEnd(storedSysExData[c], storedSysExLength[c], cable, send);
     184           3 :             storedSysExLength[c] = 0;
     185             :         }
     186             :         // If it's the end of the chunk but not the end of the SysEx, and if we
     187             :         // have exactly 3 bytes left, we can send them immediately
     188           8 :         else if (storedSysExLength[c] == 3) {
     189           3 :             sendSysExStartCont1(storedSysExData[c], cable, send);
     190           3 :             storedSysExLength[c] = 0;
     191             :         }
     192             :         // If we have less than 3 bytes, we cannot send them now, we have to
     193             :         // store them and wait for the next chunk
     194             :     }
     195             :     // If there are new bytes left in the chunk, there are exactly 3 stored
     196             :     // bytes
     197             :     else {
     198             :         // First send the 3 stored bytes
     199           8 :         sendSysExStartCont1(storedSysExData[c], cable, send);
     200             :         // Then send whatever new data is left in the new chunk (but leave at
     201             :         // least 3 bytes)
     202           8 :         sendSysExStartCont(data, length, cable, send);
     203             :         // If this chunk is the last one, terminate the SysEx (termination can
     204             :         // have 1, 2 or 3 bytes, so no need to store anything)
     205           8 :         if (msg.isLastChunk()) {
     206           6 :             sendSysExEnd(data, length, cable, send);
     207           6 :             storedSysExLength[c] = 0;
     208             :         }
     209             :         // If it's the end of the chunk but not the end of the SysEx, and if we
     210             :         // have exactly 3 bytes left, we can send them immediately
     211           2 :         else if (length == 3) {
     212           1 :             sendSysExStartCont1(data, cable, send);
     213           1 :             storedSysExLength[c] = 0;
     214             :         }
     215             :         // If we have 1 or 2 bytes left, we cannot send them now, so store them
     216             :         // until we get enough data to fill a 3-byte packet
     217             :         else {
     218           1 :             storedSysExLength[c] = length;
     219           1 :             memcpy(storedSysExData[c], data, length);
     220             :         }
     221             :     }
     222             : }
     223             : 
     224             : #else
     225             : 
     226             : template <class Send>
     227             : void USBMIDI_Sender::sendSysEx(SysExMessage, Send &&) {}
     228             : 
     229             : #endif
     230             : 
     231             : END_CS_NAMESPACE
     232             : 
     233             : AH_DIAGNOSTIC_POP()

Generated by: LCOV version 1.15