Line data Source code
1 : #pragma once
2 :
3 : #include <AH/Timing/MillisMicrosTimer.hpp>
4 : #include <MIDI_Inputs/InterfaceMIDIInputElements.hpp>
5 : #include <MIDI_Inputs/MIDIInputElementMatchers.hpp>
6 :
7 : BEGIN_CS_NAMESPACE
8 :
9 : namespace MCU {
10 :
11 : /// Struct that keeps track of the value and overload indicator of a Mackie
12 : /// Control Universal VU meter.
13 : struct VUState {
14 : /**
15 : * @brief Constructor.
16 : *
17 : * @param value
18 : * The value of the VU meter [0, 12].
19 : * @param overload
20 : * The state of the overload indicator.
21 : */
22 20 : VUState(uint8_t value = 0, bool overload = false)
23 20 : : value(value), overload(overload) {}
24 :
25 : uint8_t value : 4; ///< The value of the VU meter [0, 12].
26 : bool overload : 1; ///< The state of the overload indicator.
27 :
28 : enum Changed {
29 : NothingChanged = 0,
30 : ValueChanged = 1,
31 : OverloadChanged = 2,
32 : };
33 :
34 : /**
35 : * @brief Update the value or overload status with a new raw MIDI value.
36 : *
37 : * @param data
38 : * The raw 4-bit MIDI data (with track number masked out).
39 : *
40 : * @retval ValueChanged
41 : * The VU meter value has changed.
42 : * @retval OverloadChanged
43 : * The overload status has changed.
44 : * @retval NothingChanged
45 : * Neither the value nor overload status has changed.
46 : */
47 19 : Changed update(uint8_t data) {
48 19 : switch (data) {
49 3 : case 0xF: { // clear overload
50 3 : Changed changed = overload ? OverloadChanged : NothingChanged;
51 3 : overload = false;
52 3 : return changed;
53 : }
54 4 : case 0xE: { // set overload
55 4 : Changed changed = !overload ? OverloadChanged : NothingChanged;
56 4 : overload = true;
57 4 : return changed;
58 : }
59 0 : case 0xD: { // no meaning
60 0 : return NothingChanged;
61 : }
62 12 : default: { // set value
63 12 : Changed changed = value != data ? ValueChanged : NothingChanged;
64 12 : value = data;
65 12 : return changed;
66 : }
67 : }
68 : }
69 :
70 : /// Decay the VU value: subtract one from the position if it is not zero.
71 : /// @return Returns true if the value changed.
72 1 : bool decay() {
73 1 : if (value == 0)
74 0 : return false;
75 1 : value--;
76 1 : return true;
77 : }
78 : };
79 :
80 : // -------------------------------------------------------------------------- //
81 :
82 : /**
83 : * @brief MIDI Input matcher for Mackie Control Universal VU meters.
84 : *
85 : * In the Mackie Control Universal protocol, VU meters are updated using Channel
86 : * Pressure events.
87 : * Each device (cable number) has eight VU meters for the eight tracks. Only
88 : * MIDI channel 1 is used in the original protocol.
89 : *
90 : * The format of the MIDI message is as follows:
91 : * | Status | Data 1 |
92 : * |:-----------:|:-----------:|
93 : * | `1101 cccc` | `0hhh llll` |
94 : *
95 : * - `1101` (or `0xD`) is the status for Channel Pressure events
96 : * - `cccc` is the MIDI channel [0-15]
97 : * - `hhh` is the track index [0-7]
98 : * - `llll` is the level of the VU meter
99 : *
100 : * If the level is `0x0`, the meter is at 0%, if it's `0xC`, the meter is at
101 : * 100%.
102 : * `0xD` is an invalid value.
103 : * `0xE` sets the overload indicator, and `0xF` clears the overload indicator.
104 : *
105 : * @ingroup MIDIInputMatchers
106 : */
107 : struct VUMatcher {
108 : /// Constructor.
109 7 : VUMatcher(MIDIAddress address) : address(address) {}
110 :
111 : /// Output data of the matcher/parser.
112 : struct Result {
113 : bool match; ///< Whether the address of the message matched our address.
114 : uint8_t data; ///< The data to update the VU meter with [0x0, 0xF].
115 : };
116 :
117 : /// Parse and try to match the incoming MIDI message.
118 10 : Result operator()(ChannelMessage m) {
119 10 : uint8_t track = m.data1 >> 4;
120 10 : if (!MIDIAddress::matchSingle({track, m.getChannelCable()}, address))
121 0 : return {false, 0};
122 10 : uint8_t data = m.data1 & 0x0F;
123 10 : return {true, data};
124 : }
125 :
126 : MIDIAddress address; ///< MIDI address to compare incoming messages with.
127 : };
128 :
129 : /// MIDI Input matcher for Mackie Control Universal VU meters with bank support.
130 : /// @see @ref MCU::VUMatcher
131 : /// @ingroup MIDIInputMatchers
132 : template <uint8_t BankSize>
133 : struct BankableVUMatcher {
134 : /// Constructor.
135 5 : BankableVUMatcher(BankConfig<BankSize> config, MIDIAddress address)
136 5 : : config(config), address(address) {}
137 :
138 : /// Output data of the matcher/parser.
139 : struct Result {
140 : bool match; ///< Whether the address of the message matched our address.
141 : uint8_t data; ///< The data to update the VU meter with [0x0, 0xF].
142 : uint8_t bankIndex; ///< The bank index of the message [0, BankSize-1].
143 : };
144 :
145 : /// Parse and try to match the incoming MIDI message.
146 9 : Result operator()(ChannelMessage m) {
147 : using BankableMIDIMatcherHelpers::getBankIndex;
148 : using BankableMIDIMatcherHelpers::matchBankable;
149 9 : uint8_t track = m.data1 >> 4;
150 9 : MIDIAddress midiaddr = {track, m.getChannelCable()};
151 9 : if (!matchBankable(midiaddr, address, config))
152 0 : return {false, 0, 0};
153 9 : uint8_t data = m.data1 & 0x0F;
154 9 : uint8_t bankIndex = getBankIndex(midiaddr, address, config);
155 9 : return {true, data, bankIndex};
156 : }
157 :
158 : /// @todo Remove unnecessary methods.
159 10 : Bank<BankSize> &getBank() { return config.bank; }
160 37 : const Bank<BankSize> &getBank() const { return config.bank; }
161 : BankType getBankType() const { return config.type; }
162 : static constexpr setting_t getBankSize() { return BankSize; }
163 :
164 : /// Get the current bank setting.
165 : /// @see @ref Bank<N>::getSelection()
166 37 : setting_t getSelection() const { return getBank().getSelection(); }
167 :
168 : BaseBankConfig<BankSize> config; ///< Bank configuration.
169 : MIDIAddress address; ///< MIDI address to compare incoming messages with.
170 : };
171 :
172 : // -------------------------------------------------------------------------- //
173 :
174 : /// VU Decay time constants.
175 : namespace VUDecay {
176 : /// Don't decay automatically, hold the latest value until a new one is received.
177 : constexpr unsigned int Hold = 0;
178 : /// Decay one segment/block every 150 ms if no new values are received.
179 : constexpr unsigned int Default = 150;
180 : } // namespace VUDecay
181 :
182 : /**
183 : * @brief A MIDI input element that represents a Mackie Control Universal VU
184 : * meter.
185 : *
186 : * @ingroup MIDIInputElements
187 : */
188 : class VU : public MatchingMIDIInputElement<MIDIMessageType::CHANNEL_PRESSURE,
189 : VUMatcher>,
190 : public Interfaces::MCU::IVU {
191 : public:
192 : using Matcher = VUMatcher;
193 : using Parent = MatchingMIDIInputElement<MIDIMessageType::CHANNEL_PRESSURE,
194 : Matcher>;
195 : /**
196 : * @brief Constructor.
197 : *
198 : * @param track
199 : * The track of the VU meter. [1, 8]
200 : * @param channelCN
201 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and Cable
202 : * Number [CABLE_1, CABLE_16].
203 : * @param decayTime
204 : * The time in milliseconds it takes for the value to decay one
205 : * step.
206 : * The MCU protocol uses 300 ms per division, and two steps
207 : * per division, so the default is 150 ms per step.
208 : * Some software doesn't work if the VU meter decays automatically,
209 : * in that case, you can set the decay time to zero to disable
210 : * the decay.
211 : * @see @ref MCU::VUDecay
212 : */
213 7 : VU(uint8_t track, MIDIChannelCable channelCN,
214 : unsigned int decayTime = VUDecay::Default)
215 7 : : Parent({{track - 1, channelCN}}),
216 7 : IVU(12), decayTimer(decayTime) {}
217 :
218 : /**
219 : * @brief Constructor.
220 : *
221 : * @param track
222 : * The track of the VU meter. [1, 8]
223 : * @param decayTime
224 : * The time in milliseconds it takes for the value to decay one
225 : * step.
226 : * The MCU protocol uses 300 ms per division, and two steps
227 : * per division, so the default is 150 ms per step.
228 : * Some software doesn't work if the VU meter decays automatically,
229 : * in that case, you can set the decay time to zero to disable
230 : * the decay.
231 : * @see @ref MCU::VUDecay
232 : */
233 : VU(uint8_t track, unsigned int decayTime = VUDecay::Default)
234 : : VU(track, CHANNEL_1, decayTime) {}
235 :
236 : protected:
237 10 : bool handleUpdateImpl(typename Matcher::Result match) {
238 10 : auto changed = state.update(match.data);
239 10 : if (changed == VUState::ValueChanged)
240 : // reset the timer and fire after one interval
241 5 : decayTimer.beginNextPeriod();
242 10 : return changed;
243 : }
244 :
245 10 : void handleUpdate(typename Matcher::Result match) override {
246 10 : dirty |= handleUpdateImpl(match);
247 10 : }
248 :
249 1 : bool decay() {
250 1 : return decayTimer.getInterval() != VUDecay::Hold &&
251 2 : decayTimer &&
252 2 : state.decay();
253 : }
254 :
255 : public:
256 : /// Reset all values to zero.
257 1 : void reset() override { state = {}; }
258 :
259 : /// Decay the VU meter.
260 1 : void update() override {
261 1 : dirty |= decay();
262 1 : }
263 :
264 : public:
265 : /// @name Data access
266 : /// @{
267 :
268 : /// Get the most recent VU position that was received.
269 11 : uint8_t getValue() override { return state.value; }
270 : /// Get the status of the overload indicator.
271 3 : bool getOverload() override { return state.overload; }
272 :
273 : /// @}
274 :
275 : private:
276 : VUState state = {};
277 : AH::Timer<millis> decayTimer;
278 : };
279 :
280 : // -------------------------------------------------------------------------- //
281 :
282 : namespace Bankable {
283 :
284 : /**
285 : * @brief A MIDI input element that represents a Mackie Control Universal VU
286 : * meter. This version can be banked.
287 : *
288 : * @tparam BankSize
289 : * The number of banks.
290 : *
291 : * @ingroup BankableMIDIInputElements
292 : */
293 : template <uint8_t BankSize>
294 : class VU
295 : : public BankableMatchingMIDIInputElement<MIDIMessageType::CHANNEL_PRESSURE,
296 : BankableVUMatcher<BankSize>>,
297 : public Interfaces::MCU::IVU {
298 : public:
299 : using Matcher = BankableVUMatcher<BankSize>;
300 : using Parent
301 : = BankableMatchingMIDIInputElement<MIDIMessageType::CHANNEL_PRESSURE,
302 : Matcher>;
303 :
304 : /**
305 : * @brief Constructor.
306 : *
307 : * @param config
308 : * The bank configuration to use.
309 : * @param track
310 : * The track of the VU meter. [1, 8]
311 : * @param channelCN
312 : * The MIDI channel [CHANNEL_1, CHANNEL_16] and Cable
313 : * Number [CABLE_1, CABLE_16].
314 : * @param decayTime
315 : * The time in milliseconds it takes for the value to decay one
316 : * step.
317 : * The MCU protocol uses 300 ms per division, and two steps
318 : * per division, so the default is 150 ms per step.
319 : * Some software doesn't work if the VU meter decays automatically,
320 : * in that case, you can set the decay time to zero to disable
321 : * the decay.
322 : * @see @ref MCU::VUDecay
323 : */
324 5 : VU(BankConfig<BankSize> config, uint8_t track, MIDIChannelCable channelCN,
325 : unsigned int decayTime = VUDecay::Default)
326 5 : : Parent({config, {track - 1, channelCN}}),
327 5 : IVU(12), decayTimer(decayTime) {}
328 :
329 : /**
330 : * @brief Constructor.
331 : *
332 : * @param config
333 : * The bank configuration to use.
334 : * @param track
335 : * The track of the VU meter. [1, 8]
336 : * @param decayTime
337 : * The time in milliseconds it takes for the value to decay one
338 : * step.
339 : * The MCU protocol uses 300 ms per division, and two steps
340 : * per division, so the default is 150 ms per step.
341 : * Some software doesn't work if the VU meter decays automatically,
342 : * in that case, you can set the decay time to zero to disable
343 : * the decay.
344 : * @see @ref MCU::VUDecay
345 : */
346 : VU(BankConfig<BankSize> config, uint8_t track,
347 : unsigned int decayTime = VUDecay::Default)
348 : : VU(config, track, CHANNEL_1, decayTime) {}
349 :
350 : protected:
351 9 : bool handleUpdateImpl(typename Matcher::Result match) {
352 9 : auto changed = states[match.bankIndex].update(match.data);
353 16 : if (changed == VUState::ValueChanged &&
354 7 : match.bankIndex == this->getActiveBank())
355 : // Only care about active bank's decay.
356 : // Other banks will decay as well, but not as precisely.
357 : // They aren't visible anyway, so it's a good compromise.
358 1 : decayTimer.beginNextPeriod();
359 9 : return changed && match.bankIndex == this->getActiveBank();
360 : // Only mark dirty if the value of the active bank changed
361 : }
362 :
363 7 : void handleUpdate(typename Matcher::Result match) override {
364 7 : dirty |= handleUpdateImpl(match);
365 7 : }
366 :
367 2 : bool decay() {
368 2 : bool newdirty = false;
369 2 : if (decayTimer.getInterval() != VUDecay::Hold && decayTimer)
370 0 : for (uint8_t i = 0; i < BankSize; ++i)
371 0 : newdirty |= states[i].decay() && i == this->getActiveBank();
372 : // Only mark dirty if the value of the active bank decayed
373 2 : return newdirty;
374 : }
375 :
376 : public:
377 : /// Reset all values to zero.
378 0 : void reset() override {
379 0 : states = {{}};
380 0 : dirty = true;
381 0 : }
382 :
383 : /// Decay the VU meter.
384 0 : void update() override {
385 0 : dirty |= decay();
386 0 : }
387 :
388 : protected:
389 12 : void onBankSettingChange() override { dirty = true; }
390 :
391 : public:
392 : /// @name Data access
393 : /// @{
394 :
395 : /// Get the most recent VU position that was received for the given bank.
396 15 : uint8_t getValue(uint8_t bank) const { return states[bank].value; }
397 : /// Get the status of the overload indicator for the given bank.
398 6 : bool getOverload(uint8_t bank) const { return states[bank].overload; }
399 : /// Get the most recent VU position for the given bank as a value between
400 : /// 0 and 1.
401 : float getFloatValue(uint8_t bank) const { return getValue(bank) / 12.f; }
402 :
403 : /// Get the most recent VU position that was received for the active bank.
404 15 : uint8_t getValue() override { return getValue(this->getActiveBank()); }
405 : /// Get the status of the overload indicator for the active bank.
406 6 : bool getOverload() override { return getOverload(this->getActiveBank()); }
407 : /// Get the most recent VU position for the active bank as a value between
408 : /// 0 and 1.
409 0 : float getFloatValue() override { return getValue() / 12.f; }
410 :
411 : /// @}
412 :
413 : private:
414 : AH::Array<VUState, BankSize> states = {{}};
415 : AH::Timer<millis> decayTimer;
416 : };
417 :
418 : } // namespace Bankable
419 :
420 : } // namespace MCU
421 :
422 : END_CS_NAMESPACE
|