LCOV - code coverage report
Current view: top level - src/MIDI_Inputs/MCU - LCD.hpp (source / functions) Hit Total Coverage
Test: 3a807a259ebe0769dd942f7f612dca5273937539 Lines: 32 35 91.4 %
Date: 2024-03-24 17:16:54 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <AH/Debug/Debug.hpp>
       4             : #include <AH/Math/MinMaxFix.hpp>
       5             : #include <MIDI_Inputs/MIDIInputElement.hpp>
       6             : #include <string.h> // memcpy
       7             : 
       8             : #ifndef ARDUINO
       9             : #include <cassert>
      10             : #endif
      11             : 
      12             : BEGIN_CS_NAMESPACE
      13             : 
      14             : using AH::max;
      15             : using AH::min;
      16             : 
      17             : namespace MCU {
      18             : 
      19             : /// Counts the number of instances of the LCD class.
      20             : /// If there are multiple LCD objects, we have to update all of them before
      21             : /// breaking out of the MIDI input handling loop.
      22             : class LCDCounter {
      23             :   public:
      24          12 :     LCDCounter() { instances++; }
      25          12 :     ~LCDCounter() { instances--; }
      26             : 
      27          15 :     static uint8_t getInstances() { return instances; }
      28             : 
      29             :   private:
      30             :     static uint8_t instances;
      31             : };
      32             : /**
      33             :  * @brief   A class that represents the Mackie Control Universal LCD display and
      34             :  *          saves the text it receives.
      35             :  * 
      36             :  * The format of the MIDI message is as follows (hex):
      37             :  * | SysEx Start | Data 1 | Data 2 | Data 3 | Data 4 | Data 5 | Data 6 | Data 7 | &emsp;...&emsp; | SysEx End |
      38             :  * |:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:---:|:----:|
      39             :  * | `F0` | `mm` | `mm` | `mm` | `nn` | `12` | `oo` | `yy` | ... | `F7` |
      40             :  * 
      41             :  * - `mm` is manufacturer ID (`00 00 66` for Mackie)
      42             :  * - `nn` is model number (`10` for Logic Control, `11` for Logic Control XT)
      43             :  * - `oo` is offset relative to the first character of the LCD [0x00, 0x6F]
      44             :  * - `yy`... is the ASCII text data
      45             :  * 
      46             :  * The manufacturer ID and model number are ignored by this class.
      47             :  * 
      48             :  * @ingroup MIDIInputElements
      49             :  */
      50             : template <uint8_t BufferSize = 112>
      51             : class LCD : public MIDIInputElementSysEx, private LCDCounter {
      52             :   public:
      53             :     /// @param  offset
      54             :     ///         The text sent over MIDI is 112 characters long, by changing the
      55             :     ///         offset within this text and the length of the text, we can
      56             :     ///         listen to just a section of the text. E.g. `offset = 56` and
      57             :     ///         `BufferSize = 56` will listen to just the second line of the
      58             :     ///         LCD display. The default is `offset = 0` and `BufferSize = 112`,
      59             :     ///         i.e. listening to the entire display.
      60             :     /// @param  cable
      61             :     ///         The MIDI USB cable number to listen for.
      62          12 :     LCD(uint8_t offset = 0, Cable cable = Cable_1)
      63          12 :         : offset(offset), cable(cable) {
      64             :         // Null-terminate the buffer
      65          12 :         buffer[BufferSize] = '\0';
      66             :         // Fill the buffer with spaces
      67          72 :         for (uint8_t i = 0; i < BufferSize; i++)
      68          60 :             buffer[i] = ' ';
      69          12 :     }
      70             : 
      71             :   protected:
      72          15 :     bool updateWith(SysExMessage midimsg) override {
      73             :         // If this message is meant for a different cable than ours, return:
      74          15 :         if (midimsg.getCable() != this->cable)
      75           0 :             return false;
      76             : 
      77             :         // We can't handle chunked SysEx data (yet), and it wouldn't make a ton
      78             :         // of sense, since the default SysEx buffer size is the same size as the
      79             :         // SysEx message we expect, so it shouldn't arrive in chunks.
      80          15 :         if (!midimsg.isCompleteMessage())
      81           0 :             return false;
      82             : 
      83             :         // Format:
      84             :         // F0 mm mm mm nn 12 oo yy... F7
      85             :         // mm = manufacturer ID (00 00 66 for Mackie)
      86             :         // nn = model number (10 for Logic Control, 11 for Logic Control XT)
      87             :         // oo = offset [0x00, 0x6F]
      88             :         // yy... = ASCII data
      89          15 :         if (midimsg.data[5] != 0x12)
      90           0 :             return false;
      91             : 
      92          15 :         const uint8_t midiOffset = midimsg.data[6];
      93          15 :         const uint8_t midiLength = midimsg.length - 8;
      94          15 :         const uint8_t *text = midimsg.data + 7;
      95          15 :         const uint8_t midiBufferEnd = midiOffset + midiLength;
      96             : 
      97          15 :         const uint8_t bufferEnd = this->offset + BufferSize;
      98             : 
      99             :         // If there's no overlap between incoming range and the range that we're
     100             :         // listening for, return:
     101          15 :         if (midiOffset >= bufferEnd || this->offset >= midiBufferEnd)
     102             :             // If there are other instances, maybe it'll match one of those,
     103             :             // otherwise, stop handling this message:
     104           2 :             return getInstances() == 1;
     105             : 
     106             :         // Find the ranges that overlap between the text data in the message
     107             :         // (src) and the range of characters we're listening for (dst):
     108          13 :         uint8_t srcStart = max(0, this->offset - midiOffset);
     109          13 :         uint8_t dstStart = max(0, midiOffset - this->offset);
     110          13 :         uint8_t length = midiBufferEnd - midiOffset -
     111          39 :                          max(0, this->offset - midiOffset) -
     112          26 :                          max(0, midiBufferEnd - bufferEnd);
     113             : 
     114             :         // Copy the interesting part to our buffer:
     115             : #ifdef ARDUINO
     116             :         memcpy(&buffer[dstStart], &text[srcStart], length);
     117             : #else // Tests
     118          57 :         for (uint8_t i = 0; i < length; ++i) {
     119          44 :             buffer[dstStart + i] = text[srcStart + i];
     120          44 :             assert(dstStart + i < BufferSize);
     121          44 :             assert(srcStart + i < midiLength);
     122             :         }
     123             : #endif
     124             : 
     125          13 :         dirty = true;
     126             : 
     127             :         // If this is the only instance, the others don't have to be updated
     128             :         // anymore, so we return true to break the loop:
     129          13 :         return getInstances() == 1;
     130             :     }
     131             : 
     132             :   public:
     133             :     /// @name   Data access
     134             :     /// @{
     135             : 
     136             :     /// Get a pointer to the null-terminated display text.
     137          12 :     const char *getText() const { return buffer.data; }
     138             : 
     139             :     /// @}
     140             : 
     141             :     /// @name   Detecting changes
     142             :     /// @{
     143             : 
     144             :     /// Check if the text was updated since the last time the dirty flag was
     145             :     /// cleared.
     146             :     bool getDirty() const { return dirty; }
     147             :     /// Clear the dirty flag.
     148             :     void clearDirty() { dirty = false; }
     149             : 
     150             :     /// @}
     151             : 
     152             :   private:
     153             :     Array<char, BufferSize + 1> buffer;
     154             :     uint8_t offset;
     155             :     Cable cable;
     156             :     bool dirty = true;
     157             : };
     158             : 
     159             : } // namespace MCU
     160             : 
     161             : END_CS_NAMESPACE

Generated by: LCOV version 1.15