LCOV - code coverage report
Current view: top level - src/MIDI_Parsers - USBMIDI_Parser.hpp (source / functions) Hit Total Coverage
Test: b8a30b4b7040ae1abf162fd0a258beaa2de43626 Lines: 36 36 100.0 %
Date: 2024-12-21 21:28:55 Functions: 13 13 100.0 %
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 1.15