LCOV - code coverage report
Current view: top level - src/MIDI_Parsers - USBMIDI_Parser.hpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 100.0 % 36 36
Test Date: 2026-06-06 17:44:35 Functions: 100.0 % 13 13
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #include "MIDI_Parser.hpp"
       2              : #include "SysExBuffer.hpp"
       3              : #include <AH/Containers/Array.hpp>
       4              : 
       5              : #ifdef MIDI_NUM_CABLES
       6              : #define USB_MIDI_NUMBER_OF_CABLES MIDI_NUM_CABLES
       7              : #elif defined(USB_MIDI4_SERIAL) || defined(USB_MIDI4)
       8              : #define USB_MIDI_NUMBER_OF_CABLES 4
       9              : #elif defined(USB_MIDI16_AUDIO_SERIAL) || defined(USB_MIDI16_SERIAL) ||        \
      10              :     defined(USB_MIDI16)
      11              : // TODO: || defined(USB_EVERYTHING)
      12              : #define USB_MIDI_NUMBER_OF_CABLES 16
      13              : #elif defined(ARDUINO_ARCH_RP2040)
      14              : #define USB_MIDI_NUMBER_OF_CABLES 16
      15              : #elif !defined(ARDUINO) || defined(DOXYGEN)
      16              : #define USB_MIDI_NUMBER_OF_CABLES 16
      17              : #else
      18              : #define USB_MIDI_NUMBER_OF_CABLES 1
      19              : #endif
      20              : 
      21              : BEGIN_CS_NAMESPACE
      22              : 
      23              : /**
      24              :  * @brief   Parser for MIDI over USB packets.
      25              :  * 
      26              :  * @ingroup MIDIParsers
      27              :  */
      28              : class USBMIDI_Parser : public MIDI_Parser {
      29              :   public:
      30              :     using MIDIUSBPacket_t = AH::Array<uint8_t, 4>;
      31              : 
      32              :     /**
      33              :      * @brief   Parse one incoming MIDI message.
      34              :      * @param   puller
      35              :      *          The source of MIDI USB packets.
      36              :      * @return  The type of MIDI message available, or 
      37              :      *          `MIDIReadEvent::NO_MESSAGE` if `puller` ran out of packets
      38              :      *          before a complete message was parsed.
      39              :      */
      40              :     template <class BytePuller>
      41              :     MIDIReadEvent pull(BytePuller &&puller);
      42              : 
      43              :   protected:
      44              :     /// Feed a new packet to the parser.
      45              :     MIDIReadEvent feed(MIDIUSBPacket_t packet);
      46              :     /// Resume the parser with the previously stored and unhandled packet.
      47              :     MIDIReadEvent resume();
      48              : 
      49              :   public:
      50              : #if !IGNORE_SYSEX
      51              :     /// Get the latest SysEx message.
      52           30 :     SysExMessage getSysExMessage() const {
      53              :         return {
      54           30 :             sysexbuffers[activeCable.getRaw()].getBuffer(),
      55           30 :             sysexbuffers[activeCable.getRaw()].getLength(),
      56              :             activeCable,
      57           90 :         };
      58              :     }
      59              : #endif
      60              : 
      61              :   protected:
      62              :     MIDIReadEvent handleChannelMessage(MIDIUSBPacket_t packet, Cable cable);
      63              :     MIDIReadEvent handleSingleByte(MIDIUSBPacket_t packet, Cable cable);
      64              :     MIDIReadEvent handleSysExStartCont(MIDIUSBPacket_t packet, Cable cable);
      65              :     template <uint8_t NumBytes>
      66              :     MIDIReadEvent handleSysExEnd(MIDIUSBPacket_t packet, Cable cable);
      67              :     MIDIReadEvent handleSysCommon(MIDIUSBPacket_t packet, Cable cable);
      68              : 
      69              :   protected:
      70              : #if !IGNORE_SYSEX
      71           14 :     void startSysEx(Cable cable) { sysexbuffers[cable.getRaw()].start(); }
      72           12 :     void endSysEx(Cable cable) {
      73           12 :         sysexbuffers[cable.getRaw()].end();
      74           12 :         activeCable = cable;
      75           12 :     }
      76            2 :     void endSysExChunk(Cable cable) { activeCable = cable; }
      77          114 :     bool hasSysExSpace(Cable cable, uint8_t amount) const {
      78          114 :         return sysexbuffers[cable.getRaw()].hasSpaceLeft(amount);
      79              :     }
      80            4 :     void addSysExByte(Cable cable, uint8_t data) {
      81            4 :         sysexbuffers[cable.getRaw()].add(data);
      82            4 :     }
      83          108 :     void addSysExBytes(Cable cable, const uint8_t *data, uint8_t len) {
      84          108 :         sysexbuffers[cable.getRaw()].add(data, len);
      85          108 :     }
      86          108 :     bool receivingSysEx(Cable cable) const {
      87          108 :         return sysexbuffers[cable.getRaw()].isReceiving();
      88              :     }
      89              : 
      90            2 :     void storePacket(MIDIUSBPacket_t packet) { storedPacket = packet; }
      91           39 :     bool hasStoredPacket() const { return storedPacket[0] != 0x00; }
      92            2 :     MIDIUSBPacket_t popStoredPacket() {
      93            2 :         MIDIUSBPacket_t t = storedPacket;
      94            2 :         storedPacket[0] = 0x00;
      95            2 :         return t;
      96              :     }
      97              : 
      98              :     Cable activeCable = Cable_1;
      99              : 
     100              :   private:
     101              :     SysExBuffer sysexbuffers[USB_MIDI_NUMBER_OF_CABLES] = {};
     102              :     MIDIUSBPacket_t storedPacket = {{ 0x00 }};
     103              : #endif
     104              : };
     105              : 
     106              : template <class BytePuller>
     107           39 : inline MIDIReadEvent USBMIDI_Parser::pull(BytePuller &&puller) {
     108              :     // First try resuming the parser, we might have a stored packet that has to
     109              :     // be parsed first.
     110           39 :     MIDIReadEvent evt = resume();
     111           39 :     if (evt != MIDIReadEvent::NO_MESSAGE)
     112            1 :         return evt;
     113              : 
     114              :     // If resumption didn't produce a message, read new packets from the input
     115              :     // and parse them until either we get a message, or until the input runs out
     116              :     // of new packets.
     117           38 :     MIDIUSBPacket_t midiPacket;
     118          141 :     while (puller.pull(midiPacket)) {
     119          131 :         evt = feed(midiPacket);
     120          131 :         if (evt != MIDIReadEvent::NO_MESSAGE)
     121           28 :             return evt;
     122              :     }
     123           10 :     return MIDIReadEvent::NO_MESSAGE;
     124              : }
     125              : 
     126              : END_CS_NAMESPACE
        

Generated by: LCOV version 2.4-beta