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 48 : 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
|