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 |  ...  | 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