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

Generated by: LCOV version 1.15