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

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <Settings/NamespaceSettings.hpp>
       4              : #include <stddef.h>
       5              : #include <stdint.h>
       6              : 
       7              : BEGIN_CS_NAMESPACE
       8              : 
       9              : /** 
      10              :  * @brief   Class for parsing BLE-MIDI packets. It doesn't parse the actual MIDI
      11              :  *          messages, it just extracts the relevant MIDI data from the BLE 
      12              :  *          packets.
      13              :  * 
      14              :  * @ingroup MIDIParsers
      15              :  */
      16              : class BLEMIDIParser {
      17              :   public:
      18           44 :     BLEMIDIParser(const uint8_t *data, size_t length)
      19           44 :         : data(data), end(data + length) {
      20              :         // Need at least two bytes to be useful.
      21              :         // Usually, we have header, timestamp and at least one MIDI byte,
      22              :         // but a SysEx continuation could perhaps have only a header and a
      23              :         // single data byte (this is not explicitly allowed by the spec, but
      24              :         // handling this case requires no extra effort)
      25           44 :         if (length < 2) {
      26           30 :             this->data = end;
      27              :         }
      28              :         // First byte should be a header. If it's a data byte, discard packet.
      29           14 :         else if (isData(data[0])) {
      30            1 :             this->data = end;
      31              :         }
      32              :         // If the second byte is a data byte, this is a SysEx continuation
      33              :         // packet
      34           13 :         else if (isData(data[1])) {
      35            1 :             this->timestamp = data[0] & 0x7F;
      36            1 :             this->timestamp <<= 7;
      37            1 :             this->data += 1;
      38              :         }
      39              :         // Otherwise, the second byte is a timestamp, so skip it
      40              :         else {
      41           12 :             this->timestamp = data[0] & 0x7F;
      42           12 :             this->timestamp <<= 7;
      43           12 :             this->timestamp |= data[1] & 0x7F;
      44           12 :             this->data += 2;
      45              :         }
      46           44 :     }
      47              : 
      48              :     /// Extend the BLE packet with the given buffer.
      49              :     /// @pre    The previous buffer was fully consumed (@ref pull returned false).
      50              :     /// @note   This function should only be used for a single packet that spans
      51              :     ///         multiple buffers. If you want to parse a new packet, you should
      52              :     ///         create a new BLEMIDIParser instance.
      53            0 :     void extend(const uint8_t *data, size_t length) {
      54            0 :         this->data = data;
      55            0 :         this->end = data + length;
      56            0 :     }
      57              : 
      58              :     /// Get the next MIDI byte from the BLE packet (if available).
      59              :     /// @return True if a byte was available, false otherwise.
      60          133 :     bool pull(uint8_t &output) {
      61          156 :         while (data != end) {
      62              :             // Simply pass on all normal data bytes to the MIDI parser.
      63          129 :             if (isData(*data)) {
      64           77 :                 output = *data++;
      65           77 :                 prevWasTimestamp = false;
      66           77 :                 return true;
      67              :             }
      68              :             // If it's not a data byte, it's either a timestamp byte or a
      69              :             // MIDI status byte.
      70              :             else {
      71              :                 // Timestamp bytes and MIDI status bytes should alternate.
      72           52 :                 prevWasTimestamp = !prevWasTimestamp;
      73              :                 // If the previous non-data byte was a timestamp, this one is
      74              :                 // a MIDI status byte, so send it to the MIDI parser.
      75           52 :                 if (!prevWasTimestamp) {
      76           29 :                     output = *data++;
      77           29 :                     return true;
      78              :                 }
      79              :                 // Otherwise it's a time stamp
      80              :                 else {
      81           23 :                     uint16_t timestampLow = *data++ & 0x7F;
      82              :                     // The BLE MIDI spec has the following to say about overflow:
      83              :                     // > Should the timestamp value of a subsequent MIDI message
      84              :                     // > in the same packet overflow/wrap (i.e., the
      85              :                     // > timestampLow is smaller than a preceding timestampLow),
      86              :                     // > the receiver is responsible for tracking this by
      87              :                     // > incrementing the timestampHigh by one (the incremented
      88              :                     // > value is not transmitted, only understood as a result
      89              :                     // > of the overflow condition).
      90           23 :                     if (timestampLow < (timestamp & 0x7F)) // overflow
      91            1 :                         timestamp += 0x80;
      92           23 :                     timestamp = (timestamp & 0x3F80) | timestampLow;
      93              :                 }
      94              :             }
      95              :         }
      96           27 :         return false;
      97              :     }
      98              : 
      99           40 :     uint16_t getTimestamp() const { return timestamp; }
     100              : 
     101              :   private:
     102              :     const uint8_t *data;
     103              :     const uint8_t *end;
     104              :     bool prevWasTimestamp = true;
     105              :     uint16_t timestamp = 0;
     106              : 
     107              :   private:
     108              :     /// Check if the given byte is a data byte (and not a header, timestamp or
     109              :     /// status byte).
     110          156 :     static bool isData(uint8_t data) { return (data & (1 << 7)) == 0; }
     111              : };
     112              : 
     113              : END_CS_NAMESPACE
        

Generated by: LCOV version 2.4-beta