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

Generated by: LCOV version 2.4-beta