Line data Source code
1 : #pragma once
2 :
3 : #include <AH/Hardware/ExtendedInputOutput/ExtendedInputOutput.hpp>
4 : #include <AH/Math/MinMaxFix.hpp>
5 : #include <Banks/BankableMIDIInput.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 MIDIChannelCN &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 MIDIAddress &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 19 : }
123 :
124 : /// The address of the VU meter is the high nibble of the first (and only)
125 : /// data byte.
126 19 : MIDIAddress getTarget(const ChannelMessageMatcher &midimsg) const override {
127 19 : return {
128 19 : int8_t(midimsg.data1 >> 4),
129 19 : Channel(midimsg.channel),
130 19 : Cable(midimsg.CN),
131 : };
132 : }
133 :
134 1 : void decay() {
135 2 : for (uint8_t i = 0; i < NumValues; ++i)
136 2 : if (getValue(i) > 0)
137 1 : values[i]--;
138 1 : }
139 :
140 : /// Get the active bank selection
141 14 : virtual uint8_t getSelection() const { return 0; }
142 :
143 : /// Get the bank index from a MIDI address
144 10 : virtual setting_t getBankIndex(const MIDIAddress &target) const {
145 10 : (void)target;
146 10 : return 0;
147 : }
148 :
149 : /// Set the VU meter value.
150 12 : void setValue(uint8_t index, uint8_t newValue) {
151 12 : prevDecayTime = millis();
152 12 : values[index] = newValue | (values[index] & 0xF0);
153 12 : }
154 :
155 : /// Set the overload status.
156 4 : void setOverload(uint8_t index) { values[index] |= 0xF0; }
157 : /// Clear the overload status.
158 3 : void clearOverload(uint8_t index) { values[index] &= 0x0F; }
159 : /// Get the VU meter value from the raw value.
160 26 : uint8_t getValue(uint8_t index) const { return values[index] & 0x0F; }
161 : /// Get the overload status value from the raw value.
162 9 : bool getOverload(uint8_t index) const { return values[index] & 0xF0; }
163 :
164 : private:
165 19 : Array<uint8_t, NumValues> values = {{}};
166 : unsigned int decayTime;
167 12 : unsigned long prevDecayTime = 0;
168 :
169 : public:
170 : Callback callback;
171 : };
172 :
173 : // -------------------------------------------------------------------------- //
174 :
175 : /**
176 : * @brief A class for MIDI input elements that represent Mackie Control
177 : * Universal VU meters. This version is generic to allow for custom
178 : * callbacks.
179 : * This version cannot be banked.
180 : */
181 : template <class Callback = VUEmptyCallback>
182 7 : class GenericVU : public VU_Base<1, Callback> {
183 : public:
184 : /**
185 : * @brief Construct a new GenericVU object.
186 : *
187 : * @param track
188 : * The track of the VU meter. [1, 8]
189 : * @param channelCN
190 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
191 : * Number [CABLE_1, CABLE_16].
192 : * @param decayTime
193 : * The time in milliseconds it takes for the value to decay one
194 : * step.
195 : * The MCU protocol uses 300 ms per division, and two steps
196 : * per division, so the default is 150 ms per step.
197 : * Some software doesn't work if the VU meter decays automatically,
198 : * in that case, you can set the decay time to zero to disable
199 : * the decay.
200 : * @param callback
201 : * The callback object that is update when the value changes.
202 : * Used for displaying the value on a range of LEDs etc.
203 : */
204 7 : GenericVU(uint8_t track, const MIDIChannelCN &channelCN,
205 : unsigned int decayTime, const Callback &callback)
206 7 : : VU_Base<1, Callback>{
207 7 : track,
208 7 : channelCN,
209 7 : decayTime,
210 7 : callback,
211 14 : } {}
212 : };
213 :
214 : /**
215 : * @brief A class for MIDI input elements that represent Mackie Control
216 : * Universal VU meters.
217 : * This version cannot be banked.
218 : *
219 : * @ingroup MIDIInputElements
220 : */
221 7 : class VU : public GenericVU<> {
222 : public:
223 : /**
224 : * @brief Construct a new VU object.
225 : *
226 : * @param track
227 : * The track of the VU meter. [1, 8]
228 : * @param channelCN
229 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
230 : * Number [CABLE_1, CABLE_16].
231 : * @param decayTime
232 : * The time in milliseconds it takes for the value to decay one
233 : * step.
234 : * The MCU protocol uses 300 ms per division, and two steps
235 : * per division, so the default is 150 ms per step.
236 : * Some software doesn't work if the VU meter decays automatically,
237 : * in that case, you can set the decay time to zero to disable
238 : * the decay.
239 : */
240 7 : VU(uint8_t track, const MIDIChannelCN &channelCN,
241 : unsigned int decayTime = VUDecay::Default)
242 7 : : GenericVU<>{
243 7 : track,
244 7 : channelCN,
245 7 : decayTime,
246 7 : {},
247 14 : } {}
248 :
249 : /**
250 : * @brief Construct a new VU object.
251 : *
252 : * @param track
253 : * The track of the VU meter. [1, 8]
254 : * @param decayTime
255 : * The time in milliseconds it takes for the value to decay one
256 : * step.
257 : * The MCU protocol uses 300 ms per division, and two steps
258 : * per division, so the default is 150 ms per step.
259 : * Some software doesn't work if the VU meter decays automatically,
260 : * in that case, you can set the decay time to zero to disable
261 : * the decay.
262 : */
263 : VU(uint8_t track, unsigned int decayTime = VUDecay::Default)
264 : : GenericVU<>{
265 : track,
266 : CHANNEL_1,
267 : decayTime,
268 : {},
269 : } {}
270 : };
271 :
272 : // -------------------------------------------------------------------------- //
273 :
274 : namespace Bankable {
275 :
276 : /**
277 : * @brief A class for MIDI input elements that represent Mackie Control
278 : * Universal VU meters. This version is generic to allow for custom
279 : * callbacks.
280 : * This version can be banked.
281 : *
282 : * @tparam NumBanks
283 : * The number of banks.
284 : */
285 : template <uint8_t NumBanks, class Callback = VUEmptyCallback>
286 5 : class GenericVU : public VU_Base<NumBanks, Callback>,
287 : public BankableMIDIInput<NumBanks> {
288 : public:
289 : /**
290 : * @brief Construct a new Bankable VU object.
291 : *
292 : * @param config
293 : * The bank configuration to use.
294 : * @param track
295 : * The track of the VU meter. [1, 8]
296 : * @param channelCN
297 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
298 : * Number [CABLE_1, CABLE_16].
299 : * @param decayTime
300 : * The time in milliseconds it takes for the value to decay one
301 : * step.
302 : * The MCU protocol uses 300 ms per division, and two steps
303 : * per division, so the default is 150 ms per step.
304 : * Some software doesn't work if the VU meter decays automatically,
305 : * in that case, you can set the decay time to zero to disable
306 : * the decay.
307 : * @param callback
308 : * The callback object that is update when the value or bank
309 : * changes.
310 : * Used for displaying the value on a range of LEDs etc.
311 : */
312 5 : GenericVU(BankConfig<NumBanks> config, uint8_t track,
313 : const MIDIChannelCN &channelCN, unsigned int decayTime,
314 : const Callback &callback)
315 5 : : VU_Base<NumBanks, Callback>{
316 5 : track,
317 5 : channelCN,
318 5 : decayTime,
319 5 : callback,
320 : },
321 10 : BankableMIDIInput<NumBanks>{config} {}
322 :
323 : private:
324 20 : setting_t getSelection() const override {
325 20 : return BankableMIDIInput<NumBanks>::getSelection();
326 : };
327 :
328 9 : uint8_t getBankIndex(const MIDIAddress &target) const override {
329 9 : return BankableMIDIInput<NumBanks>::getBankIndex(target, this->address);
330 : }
331 :
332 : /// Check if the address of the incoming MIDI message is in one of the banks
333 : /// of this element.
334 9 : bool match(const MIDIAddress &target) const override {
335 18 : return BankableMIDIInput<NumBanks>::matchBankable(target,
336 9 : this->address);
337 : }
338 :
339 12 : void onBankSettingChange() override { this->callback.update(*this); }
340 : };
341 :
342 : /**
343 : * @brief A class for MIDI input elements that represent Mackie Control
344 : * Universal VU meters.
345 : *
346 : * @tparam NumBanks
347 : * The number of banks.
348 : *
349 : * @ingroup BankableMIDIInputElements
350 : */
351 : template <uint8_t NumBanks>
352 4 : class VU : public GenericVU<NumBanks> {
353 : public:
354 : /**
355 : * @brief Construct a new Bankable VU object.
356 : *
357 : * @param config
358 : * The bank configuration to use.
359 : * @param track
360 : * The track of the VU meter. [1, 8]
361 : * @param channelCN
362 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and optional Cable
363 : * Number [CABLE_1, CABLE_16].
364 : * @param decayTime
365 : * The time in milliseconds it takes for the value to decay one
366 : * step.
367 : * The MCU protocol uses 300 ms per division, and two steps
368 : * per division, so the default is 150 ms per step.
369 : * Some software doesn't work if the VU meter decays automatically,
370 : * in that case, you can set the decay time to zero to disable
371 : * the decay.
372 : */
373 4 : VU(BankConfig<NumBanks> config, uint8_t track,
374 : const MIDIChannelCN &channelCN,
375 : unsigned int decayTime = VUDecay::Default)
376 4 : : GenericVU<NumBanks>{
377 4 : config, track, channelCN, decayTime, {},
378 8 : } {}
379 :
380 : /**
381 : * @brief Construct a new Bankable VU object.
382 : *
383 : * @param config
384 : * The bank configuration to use.
385 : * @param track
386 : * The track of the VU meter. [1, 8]
387 : * @param decayTime
388 : * The time in milliseconds it takes for the value to decay one
389 : * step.
390 : * The MCU protocol uses 300 ms per division, and two steps
391 : * per division, so the default is 150 ms per step.
392 : * Some software doesn't work if the VU meter decays automatically,
393 : * in that case, you can set the decay time to zero to disable
394 : * the decay.
395 : */
396 : VU(BankConfig<NumBanks> config, uint8_t track,
397 : unsigned int decayTime = VUDecay::Default)
398 : : GenericVU<NumBanks>{
399 : config, track, CHANNEL_1, decayTime, {},
400 : } {}
401 : };
402 :
403 : } // namespace Bankable
404 :
405 : } // namespace MCU
406 :
407 : END_CS_NAMESPACE
|