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::ChannelPressure,
189 : VUMatcher>,
190 : public Interfaces::MCU::IVU {
191 : public:
192 : using Matcher = VUMatcher;
193 : using Parent =
194 : MatchingMIDIInputElement<MIDIMessageType::ChannelPressure, 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}}), IVU(12), decayTimer(decayTime) {}
216 :
217 : /**
218 : * @brief Constructor.
219 : *
220 : * @param track
221 : * The track of the VU meter. [1, 8]
222 : * @param decayTime
223 : * The time in milliseconds it takes for the value to decay one
224 : * step.
225 : * The MCU protocol uses 300 ms per division, and two steps
226 : * per division, so the default is 150 ms per step.
227 : * Some software doesn't work if the VU meter decays automatically,
228 : * in that case, you can set the decay time to zero to disable
229 : * the decay.
230 : * @see @ref MCU::VUDecay
231 : */
232 : VU(uint8_t track, unsigned int decayTime = VUDecay::Default)
233 : : VU(track, Channel_1, decayTime) {}
234 :
235 : protected:
236 10 : bool handleUpdateImpl(typename Matcher::Result match) {
237 10 : auto changed = state.update(match.data);
238 10 : if (changed == VUState::ValueChanged)
239 : // reset the timer and fire after one interval
240 5 : decayTimer.beginNextPeriod();
241 10 : return changed;
242 : }
243 :
244 10 : void handleUpdate(typename Matcher::Result match) override {
245 10 : dirty |= handleUpdateImpl(match);
246 10 : }
247 :
248 1 : bool decay() {
249 2 : return decayTimer.getInterval() != VUDecay::Hold && decayTimer &&
250 2 : state.decay();
251 : }
252 :
253 : public:
254 : /// Reset all values to zero.
255 1 : void reset() override { state = {}; }
256 :
257 : /// Decay the VU meter.
258 1 : void update() override { dirty |= decay(); }
259 :
260 : public:
261 : /// @name Data access
262 : /// @{
263 :
264 : /// Get the most recent VU position that was received.
265 11 : uint8_t getValue() override { return state.value; }
266 : /// Get the status of the overload indicator.
267 3 : bool getOverload() override { return state.overload; }
268 :
269 : /// @}
270 :
271 : private:
272 : VUState state = {};
273 : AH::Timer<millis> decayTimer;
274 : };
275 :
276 : // -------------------------------------------------------------------------- //
277 :
278 : namespace Bankable {
279 :
280 : /**
281 : * @brief A MIDI input element that represents a Mackie Control Universal VU
282 : * meter. This version can be banked.
283 : *
284 : * @tparam BankSize
285 : * The number of banks.
286 : *
287 : * @ingroup BankableMIDIInputElements
288 : */
289 : template <uint8_t BankSize>
290 : class VU
291 : : public BankableMatchingMIDIInputElement<MIDIMessageType::ChannelPressure,
292 : BankableVUMatcher<BankSize>>,
293 : public Interfaces::MCU::IVU {
294 : public:
295 : using Matcher = BankableVUMatcher<BankSize>;
296 : using Parent =
297 : BankableMatchingMIDIInputElement<MIDIMessageType::ChannelPressure,
298 : Matcher>;
299 :
300 : /**
301 : * @brief Constructor.
302 : *
303 : * @param config
304 : * The bank configuration to use.
305 : * @param track
306 : * The track of the VU meter. [1, 8]
307 : * @param channelCN
308 : * The MIDI channel [Channel_1, Channel_16] and Cable
309 : * Number [Cable_1, Cable_16].
310 : * @param decayTime
311 : * The time in milliseconds it takes for the value to decay one
312 : * step.
313 : * The MCU protocol uses 300 ms per division, and two steps
314 : * per division, so the default is 150 ms per step.
315 : * Some software doesn't work if the VU meter decays automatically,
316 : * in that case, you can set the decay time to zero to disable
317 : * the decay.
318 : * @see @ref MCU::VUDecay
319 : */
320 5 : VU(BankConfig<BankSize> config, uint8_t track, MIDIChannelCable channelCN,
321 : unsigned int decayTime = VUDecay::Default)
322 5 : : Parent({config, {track - 1, channelCN}}), IVU(12),
323 5 : decayTimer(decayTime) {}
324 :
325 : /**
326 : * @brief Constructor.
327 : *
328 : * @param config
329 : * The bank configuration to use.
330 : * @param track
331 : * The track of the VU meter. [1, 8]
332 : * @param decayTime
333 : * The time in milliseconds it takes for the value to decay one
334 : * step.
335 : * The MCU protocol uses 300 ms per division, and two steps
336 : * per division, so the default is 150 ms per step.
337 : * Some software doesn't work if the VU meter decays automatically,
338 : * in that case, you can set the decay time to zero to disable
339 : * the decay.
340 : * @see @ref MCU::VUDecay
341 : */
342 : VU(BankConfig<BankSize> config, uint8_t track,
343 : unsigned int decayTime = VUDecay::Default)
344 : : VU(config, track, Channel_1, decayTime) {}
345 :
346 : protected:
347 9 : bool handleUpdateImpl(typename Matcher::Result match) {
348 9 : auto changed = states[match.bankIndex].update(match.data);
349 16 : if (changed == VUState::ValueChanged &&
350 7 : match.bankIndex == this->getActiveBank())
351 : // Only care about active bank's decay.
352 : // Other banks will decay as well, but not as precisely.
353 : // They aren't visible anyway, so it's a good compromise.
354 1 : decayTimer.beginNextPeriod();
355 9 : return changed && match.bankIndex == this->getActiveBank();
356 : // Only mark dirty if the value of the active bank changed
357 : }
358 :
359 7 : void handleUpdate(typename Matcher::Result match) override {
360 7 : dirty |= handleUpdateImpl(match);
361 7 : }
362 :
363 2 : bool decay() {
364 2 : bool newdirty = false;
365 2 : if (decayTimer.getInterval() != VUDecay::Hold && decayTimer)
366 0 : for (uint8_t i = 0; i < BankSize; ++i)
367 0 : newdirty |= states[i].decay() && i == this->getActiveBank();
368 : // Only mark dirty if the value of the active bank decayed
369 2 : return newdirty;
370 : }
371 :
372 : public:
373 : /// Reset all values to zero.
374 0 : void reset() override {
375 0 : states = {{}};
376 0 : dirty = true;
377 0 : }
378 :
379 : /// Decay the VU meter.
380 0 : void update() override { dirty |= decay(); }
381 :
382 : protected:
383 12 : void onBankSettingChange() override { dirty = true; }
384 :
385 : public:
386 : /// @name Data access
387 : /// @{
388 :
389 : /// Get the most recent VU position that was received for the given bank.
390 15 : uint8_t getValue(uint8_t bank) const { return states[bank].value; }
391 : /// Get the status of the overload indicator for the given bank.
392 6 : bool getOverload(uint8_t bank) const { return states[bank].overload; }
393 : /// Get the most recent VU position for the given bank as a value between
394 : /// 0 and 1.
395 : float getFloatValue(uint8_t bank) const { return getValue(bank) / 12.f; }
396 :
397 : /// Get the most recent VU position that was received for the active bank.
398 15 : uint8_t getValue() override { return getValue(this->getActiveBank()); }
399 : /// Get the status of the overload indicator for the active bank.
400 6 : bool getOverload() override { return getOverload(this->getActiveBank()); }
401 : /// Get the most recent VU position for the active bank as a value between
402 : /// 0 and 1.
403 0 : float getFloatValue() override { return getValue() / 12.f; }
404 :
405 : /// @}
406 :
407 : private:
408 : AH::Array<VUState, BankSize> states = {{}};
409 : AH::Timer<millis> decayTimer;
410 : };
411 :
412 : } // namespace Bankable
413 :
414 : } // namespace MCU
415 :
416 : END_CS_NAMESPACE
|