LCOV - code coverage report
Current view: top level - src/MIDI_Inputs/MCU - VU.hpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 85.4 % 89 76
Test Date: 2026-06-06 17:44:35 Functions: 80.9 % 47 38
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.4-beta