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