Line data Source code
1 : #pragma once
2 :
3 : #include <Banks/BankableMIDIInput.hpp>
4 : #include <AH/Hardware/ExtendedInputOutput/ExtendedInputOutput.hpp>
5 : #include <AH/Math/MinMaxFix.hpp>
6 : #include <MIDI_Inputs/MIDIInputElementChannelPressure.hpp>
7 : #include <string.h>
8 :
9 : BEGIN_CS_NAMESPACE
10 :
11 : /**
12 : * @brief An abstract interface for VU meters. To allow for both floating
13 : * point values and integers, all values are integers under the hood.
14 : *
15 : * Using floats instead integers would be a strange choice as LED bar VU meters
16 : * have discrete levels.
17 : * Continuous "analog" VU meters can use or override the `getFloatValue()`
18 : * method.
19 : */
20 : class IVU {
21 : public:
22 12 : IVU(uint8_t max) : max(max) {}
23 : /** Return the VU meter value as an integer. */
24 : virtual uint8_t getValue() = 0;
25 : /** Return the overload status. */
26 : virtual bool getOverload() = 0;
27 : /** Get the VU meter value as a floating point number. */
28 1 : virtual float getFloatValue() { return (float)getValue() / getMax(); }
29 : /** Get the maximum value that this VU meter can return. */
30 1 : uint8_t getMax() const { return max; }
31 :
32 : protected:
33 : const uint8_t max;
34 : };
35 :
36 : namespace MCU {
37 :
38 : /// VU Decay time constants
39 : namespace VUDecay {
40 : /// Don't decay automatically, hold the latest value until a new one is received.
41 : constexpr unsigned int Hold = 0;
42 : /// Decay one segment/block every 150 ms if no new values are received.
43 : constexpr unsigned int Default = 150;
44 : } // namespace VUDecay
45 :
46 : /// Empty callback for VU meters that does nothing.
47 : struct VUEmptyCallback {
48 : template <class T>
49 0 : void begin(const T &) {}
50 : template <class T>
51 29 : void update(const T &) {}
52 : };
53 :
54 : /**
55 : * @brief A MIDI input element that represents a Mackie Control Universal VU
56 : * meter.
57 : * This is a base class to both the Bankable and non-Bankable version.
58 : *
59 : * In the Mackie Control Universal protocol, VU meters are updated using Channel
60 : * Pressure events.
61 : * Each device (cable number) has eight VU meters for the eight tracks. Only
62 : * MIDI channel 1 is used in the original protocol.
63 : *
64 : * The format of the MIDI message is as follows:
65 : * `| 1101 cccc | 0hhh llll |`
66 : *
67 : * - `1101` (or `0xD`) is the status for Channel Pressure events
68 : * - `cccc` is the MIDI channel
69 : * - `hhh` is the track index [0-7]
70 : * - `llll` is the level of the VU meter
71 : *
72 : * If the level is `0x0`, the meter is at 0%, if it's `0xC`, the meter is at
73 : * 100%.
74 : * `0xD` is an invalid value.
75 : * `0xE` sets the overload indicator, and `0xF` clears the overload indicator.
76 : */
77 : template <uint8_t NumValues, class Callback>
78 12 : class VU_Base : public MIDIInputElementChannelPressure, public IVU {
79 : protected:
80 12 : VU_Base(uint8_t track, const MIDICNChannel &channelCN,
81 : unsigned int decayTime, const Callback &callback)
82 24 : : MIDIInputElementChannelPressure{{track - 1, channelCN}}, IVU(12),
83 48 : decayTime(decayTime), callback(callback) {}
84 :
85 : public:
86 : /// Initialize
87 1 : void begin() override { callback.begin(*this); }
88 : /// Reset all values to zero
89 1 : void reset() override {
90 1 : values = {{}};
91 1 : callback.update(*this);
92 1 : }
93 :
94 : /// Return the VU meter value as an integer in [0, 12].
95 25 : uint8_t getValue() override { return getValue(getSelection()); }
96 : /// Return the overload status.
97 9 : bool getOverload() override { return getOverload(getSelection()); }
98 :
99 : /// Update is called periodically, it decays the meter if the time is right.
100 1 : void update() override {
101 1 : if (decayTime && (millis() - prevDecayTime >= decayTime)) {
102 1 : prevDecayTime += decayTime;
103 1 : decay();
104 1 : callback.update(*this);
105 1 : }
106 1 : }
107 :
108 : private:
109 : /// Called when an incoming MIDI message matches this element
110 19 : bool updateImpl(const ChannelMessageMatcher &midimsg,
111 : const MIDICNChannelAddress &target) override {
112 19 : uint8_t data = midimsg.data1 & 0x0F;
113 19 : uint8_t index = getBankIndex(target);
114 19 : switch (data) {
115 3 : case 0xF: clearOverload(index); break;
116 4 : case 0xE: setOverload(index); break;
117 0 : case 0xD: break; // no meaning
118 12 : default: setValue(index, data); break;
119 : }
120 19 : callback.update(*this);
121 19 : return true;
122 : }
123 :
124 : /// The address of the VU meter is the high nibble of the first (and only)
125 : /// data byte.
126 : MIDICNChannelAddress
127 19 : getTarget(const ChannelMessageMatcher &midimsg) const override {
128 19 : return {
129 19 : int8_t(midimsg.data1 >> 4),
130 19 : Channel(midimsg.channel),
131 19 : midimsg.CN,
132 : };
133 : }
134 :
135 1 : void decay() {
136 2 : for (uint8_t i = 0; i < NumValues; ++i)
137 2 : if (getValue(i) > 0)
138 1 : values[i]--;
139 1 : }
140 :
141 : /// Get the active bank selection
142 14 : virtual uint8_t getSelection() const { return 0; }
143 :
144 : /// Get the bank index from a MIDI address
145 10 : virtual setting_t getBankIndex(const MIDICNChannelAddress &target) const {
146 10 : (void)target;
147 10 : return 0;
148 : }
149 :
150 : /// Set the VU meter value.
151 12 : void setValue(uint8_t index, uint8_t newValue) {
152 12 : prevDecayTime = millis();
153 12 : values[index] = newValue | (values[index] & 0xF0);
154 12 : }
155 :
156 : /// Set the overload status.
157 4 : void setOverload(uint8_t index) { values[index] |= 0xF0; }
158 : /// Clear the overload status.
159 3 : void clearOverload(uint8_t index) { values[index] &= 0x0F; }
160 : /// Get the VU meter value from the raw value.
161 26 : uint8_t getValue(uint8_t index) const { return values[index] & 0x0F; }
162 : /// Get the overload status value from the raw value.
163 9 : bool getOverload(uint8_t index) const { return values[index] & 0xF0; }
164 :
165 : private:
166 19 : Array<uint8_t, NumValues> values = {{}};
167 : unsigned int decayTime;
168 12 : unsigned long prevDecayTime = 0;
169 :
170 : public:
171 : Callback callback;
172 : };
173 :
174 : // -------------------------------------------------------------------------- //
175 :
176 : /**
177 : * @brief A class for MIDI input elements that represent Mackie Control
178 : * Universal VU meters. This version is generic to allow for custom
179 : * callbacks.
180 : * This version cannot be banked.
181 : */
182 : template <class Callback = VUEmptyCallback>
183 7 : class GenericVU : public VU_Base<1, Callback> {
184 : public:
185 : /**
186 : * @brief Construct a new GenericVU object.
187 : *
188 : * @param track
189 : * The track of the VU meter. [1, 8]
190 : * @param channelCN
191 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
192 : * Number [0, 15].
193 : * @param decayTime
194 : * The time in milliseconds it takes for the value to decay one
195 : * step.
196 : * The MCU protocol uses 300 ms per division, and two steps
197 : * per division, so the default is 150 ms per step.
198 : * Some software doesn't work if the VU meter decays automatically,
199 : * in that case, you can set the decay time to zero to disable
200 : * the decay.
201 : * @param callback
202 : * The callback object that is update when the value changes.
203 : * Used for displaying the value on a range of LEDs etc.
204 : */
205 7 : GenericVU(uint8_t track, const MIDICNChannel &channelCN,
206 : unsigned int decayTime, const Callback &callback)
207 7 : : VU_Base<1, Callback>{
208 7 : track,
209 7 : channelCN,
210 7 : decayTime,
211 7 : callback,
212 14 : } {}
213 : };
214 :
215 : /**
216 : * @brief A class for MIDI input elements that represent Mackie Control
217 : * Universal VU meters.
218 : * This version cannot be banked.
219 : *
220 : * @ingroup MIDIInputElements
221 : */
222 7 : class VU : public GenericVU<> {
223 : public:
224 : /**
225 : * @brief Construct a new VU object.
226 : *
227 : * @param track
228 : * The track of the VU meter. [1, 8]
229 : * @param channelCN
230 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
231 : * Number [0, 15].
232 : * @param decayTime
233 : * The time in milliseconds it takes for the value to decay one
234 : * step.
235 : * The MCU protocol uses 300 ms per division, and two steps
236 : * per division, so the default is 150 ms per step.
237 : * Some software doesn't work if the VU meter decays automatically,
238 : * in that case, you can set the decay time to zero to disable
239 : * the decay.
240 : */
241 7 : VU(uint8_t track, const MIDICNChannel &channelCN,
242 : unsigned int decayTime = VUDecay::Default)
243 7 : : GenericVU<>{
244 7 : track,
245 7 : channelCN,
246 7 : decayTime,
247 : {},
248 14 : } {}
249 :
250 : /**
251 : * @brief Construct a new VU object.
252 : *
253 : * @param track
254 : * The track of the VU meter. [1, 8]
255 : * @param decayTime
256 : * The time in milliseconds it takes for the value to decay one
257 : * step.
258 : * The MCU protocol uses 300 ms per division, and two steps
259 : * per division, so the default is 150 ms per step.
260 : * Some software doesn't work if the VU meter decays automatically,
261 : * in that case, you can set the decay time to zero to disable
262 : * the decay.
263 : */
264 : VU(uint8_t track, unsigned int decayTime = VUDecay::Default)
265 : : GenericVU<>{
266 : track,
267 : CHANNEL_1,
268 : decayTime,
269 : {},
270 : } {}
271 : };
272 :
273 : // -------------------------------------------------------------------------- //
274 :
275 : namespace Bankable {
276 :
277 : /**
278 : * @brief A class for MIDI input elements that represent Mackie Control
279 : * Universal VU meters. This version is generic to allow for custom
280 : * callbacks.
281 : * This version can be banked.
282 : *
283 : * @tparam NumBanks
284 : * The number of banks.
285 : */
286 : template <uint8_t NumBanks, class Callback = VUEmptyCallback>
287 5 : class GenericVU : public VU_Base<NumBanks, Callback>,
288 : public BankableMIDIInput<NumBanks> {
289 : public:
290 : /**
291 : * @brief Construct a new Bankable VU object.
292 : *
293 : * @param config
294 : * The bank configuration to use.
295 : * @param track
296 : * The track of the VU meter. [1, 8]
297 : * @param channelCN
298 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
299 : * Number [0, 15].
300 : * @param decayTime
301 : * The time in milliseconds it takes for the value to decay one
302 : * step.
303 : * The MCU protocol uses 300 ms per division, and two steps
304 : * per division, so the default is 150 ms per step.
305 : * Some software doesn't work if the VU meter decays automatically,
306 : * in that case, you can set the decay time to zero to disable
307 : * the decay.
308 : * @param callback
309 : * The callback object that is update when the value or bank
310 : * changes.
311 : * Used for displaying the value on a range of LEDs etc.
312 : */
313 5 : GenericVU(const BankConfig<NumBanks> &config, uint8_t track,
314 : const MIDICNChannel &channelCN, unsigned int decayTime,
315 : const Callback &callback)
316 5 : : VU_Base<NumBanks, Callback>{
317 5 : track,
318 5 : channelCN,
319 5 : decayTime,
320 5 : callback,
321 : },
322 10 : BankableMIDIInput<NumBanks>{config} {}
323 :
324 : private:
325 20 : setting_t getSelection() const override {
326 20 : return BankableMIDIInput<NumBanks>::getSelection();
327 : };
328 :
329 9 : uint8_t getBankIndex(const MIDICNChannelAddress &target) const override {
330 9 : return BankableMIDIInput<NumBanks>::getBankIndex(target, this->address);
331 : }
332 :
333 : /// Check if the address of the incoming MIDI message is in one of the banks
334 : /// of this element.
335 9 : bool match(const MIDICNChannelAddress &target) const override {
336 18 : return BankableMIDIInput<NumBanks>::matchBankable(target,
337 9 : this->address);
338 : }
339 :
340 12 : void onBankSettingChange() override { this->callback.update(*this); }
341 : };
342 :
343 : /**
344 : * @brief A class for MIDI input elements that represent Mackie Control
345 : * Universal VU meters.
346 : *
347 : * @tparam NumBanks
348 : * The number of banks.
349 : *
350 : * @ingroup BankableMIDIInputElements
351 : */
352 : template <uint8_t NumBanks>
353 4 : class VU : public GenericVU<NumBanks> {
354 : public:
355 : /**
356 : * @brief Construct a new Bankable VU object.
357 : *
358 : * @param config
359 : * The bank configuration to use.
360 : * @param track
361 : * The track of the VU meter. [1, 8]
362 : * @param channelCN
363 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
364 : * Number [0, 15].
365 : * @param decayTime
366 : * The time in milliseconds it takes for the value to decay one
367 : * step.
368 : * The MCU protocol uses 300 ms per division, and two steps
369 : * per division, so the default is 150 ms per step.
370 : * Some software doesn't work if the VU meter decays automatically,
371 : * in that case, you can set the decay time to zero to disable
372 : * the decay.
373 : */
374 4 : VU(const BankConfig<NumBanks> &config, uint8_t track,
375 : const MIDICNChannel &channelCN,
376 : unsigned int decayTime = VUDecay::Default)
377 4 : : GenericVU<NumBanks>{
378 4 : config, track, channelCN, decayTime, {},
379 8 : } {}
380 :
381 : /**
382 : * @brief Construct a new Bankable VU object.
383 : *
384 : * @param config
385 : * The bank configuration to use.
386 : * @param track
387 : * The track of the VU meter. [1, 8]
388 : * @param decayTime
389 : * The time in milliseconds it takes for the value to decay one
390 : * step.
391 : * The MCU protocol uses 300 ms per division, and two steps
392 : * per division, so the default is 150 ms per step.
393 : * Some software doesn't work if the VU meter decays automatically,
394 : * in that case, you can set the decay time to zero to disable
395 : * the decay.
396 : */
397 : VU(const BankConfig<NumBanks> &config, uint8_t track,
398 : unsigned int decayTime = VUDecay::Default)
399 : : GenericVU<NumBanks>{
400 : config, track, CHANNEL_1, decayTime, {},
401 : } {}
402 : };
403 :
404 : } // namespace Bankable
405 :
406 : } // namespace MCU
407 :
408 : END_CS_NAMESPACE
|