Line data Source code
1 : #pragma once 2 : 3 : #include <AH/STL/algorithm> // std::fill 4 : #include <MIDI_Inputs/MIDIInputElement.hpp> 5 : #include <MIDI_Inputs/MIDIInputElementMatchers.hpp> 6 : #include <Print.h> 7 : 8 : BEGIN_CS_NAMESPACE 9 : 10 : namespace MCU { 11 : 12 : /// Class that receives and saves the text of a Mackie Control Universal 13 : /// 7-segment display like the assignment display and the time display. 14 : /// 15 : /// @note Implements Control Change updates only, not System Exclusive. 16 : /// 17 : /// @ingroup MIDIInputElements 18 : template <uint8_t LENGTH> 19 : class SevenSegmentDisplay 20 : : public MatchingMIDIInputElement<MIDIMessageType::ControlChange, 21 : TwoByteRangeMIDIMatcher>, 22 : public Printable { 23 : public: 24 : using Matcher = TwoByteRangeMIDIMatcher; 25 : using Parent = 26 : MatchingMIDIInputElement<MIDIMessageType::ControlChange, Matcher>; 27 : 28 15 : SevenSegmentDisplay(MIDIAddress address) : Parent({address, LENGTH}) { 29 15 : fillWithSpaces(); 30 15 : } 31 : 32 2 : void reset() override { 33 2 : if (!ignoreReset) { 34 1 : fillWithSpaces(); 35 1 : dirty = true; 36 : } 37 2 : } 38 : 39 : protected: 40 : /// Update a single character. 41 87 : void handleUpdate(typename Matcher::Result match) override { 42 87 : uint8_t index = LENGTH - 1 - match.index; 43 : // MIDI msg: character data → bits 0-5 44 : // MIDI msg: decimal point → bit 6 set, no decimal point → bit 6 not set 45 : // text: decimal point → bit 7 set, no decimal point → bit 7 not set 46 87 : uint8_t decimalPt = (match.value & 0x40) << 1; 47 87 : uint8_t chardata = match.value & 0x3F; 48 87 : uint8_t character = chardata >= 0x20 ? chardata : chardata + 0x40; 49 87 : character |= decimalPt; 50 87 : dirty |= text[index] != character; 51 87 : text[index] = character; 52 87 : } 53 : 54 16 : void fillWithSpaces() { std::fill(std::begin(text), std::end(text), ' '); } 55 : 56 : public: 57 : /// @name Data access 58 : /// @{ 59 : 60 : /** 61 : * @brief Copy the ASCII text into the given buffer. 62 : * 63 : * @param[out] buffer 64 : * The destination to write the text to. 65 : * Will be null-terminated. 66 : * Should have a size of at least `length`+1 bytes. 67 : * @param[in] offset 68 : * The offset to start copying from (in the source text, the offset 69 : * in the destination buffer is always zero). 70 : * @param[in] length 71 : * The number of characters to copy. 72 : */ 73 33 : void getText(char *buffer, uint8_t offset = 0, 74 : uint8_t length = LENGTH) const { 75 33 : if (offset >= LENGTH) 76 1 : offset = LENGTH - 1; 77 33 : if (length > LENGTH - offset) 78 4 : length = LENGTH - offset; 79 205 : for (uint8_t i = 0; i < length; i++) 80 172 : buffer[i] = getCharacterAt(i + offset); 81 33 : buffer[length] = '\0'; 82 33 : } 83 : 84 : /** 85 : * @brief Get the character at the given index. 86 : * @todo Documentation. 87 : */ 88 216 : char getCharacterAt(uint8_t index) const { return text[index] & 0x7F; } 89 : 90 : /** 91 : * @brief Copy the decimal points into the given buffer. 92 : * 93 : * @param[out] buffer 94 : * The destination to write the decimal points to. 95 : * Should have a size of at least `LENGTH` bytes. 96 : */ 97 : void getDecimalPoints(bool *buffer) const { 98 : for (uint8_t i = 0; i < LENGTH; i++) 99 : buffer[i] = getDecimalPointAt(i); 100 : } 101 : 102 : /** 103 : * @brief Get the decimal point state at the given index. 104 : * @todo Documentation. 105 : */ 106 10 : bool getDecimalPointAt(uint8_t index) const { return text[index] & 0x80; } 107 : 108 : /// @} 109 : 110 : /// @name Printing 111 : /// @{ 112 : 113 : /// Print out the text of the display to the given Print. 114 1 : size_t printTo(Print &printer) const override { 115 1 : size_t s = 0; 116 11 : for (uint8_t i = 0; i < LENGTH; i++) { 117 10 : s += printer.print(getCharacterAt(i)); 118 10 : if (getDecimalPointAt(i)) 119 5 : s += printer.print('.'); 120 : } 121 1 : return s; 122 : } 123 : 124 : /// @} 125 : 126 : /// @name Detecting changes 127 : /// @{ 128 : 129 : /// Check if the value was updated since the last time the dirty flag was 130 : /// cleared. 131 : bool getDirty() const { return dirty; } 132 : /// Clear the dirty flag. 133 : void clearDirty() { dirty = false; } 134 : 135 : /// @} 136 : 137 : private: 138 : AH::Array<char, LENGTH> text; ///< Non-ASCII and not null-terminated. 139 : bool dirty = true; 140 : 141 : public: 142 : /// Don't reset the state when calling the `reset` method. This is the 143 : /// default, because in the original MCU, displays don't get reset when a 144 : /// Reset All Controllers message is received. 145 : bool ignoreReset = true; 146 : }; 147 : 148 : } // namespace MCU 149 : 150 : END_CS_NAMESPACE