LCOV - code coverage report
Current view: top level - src/MIDI_Inputs/MCU - VU.hpp (source / functions) Hit Total Coverage
Test: ffed98f648fe78e7aa7bdd228474317d40dadbec Lines: 80 95 84.2 %
Date: 2022-05-28 15:22:59 Functions: 38 47 80.9 %
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::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

Generated by: LCOV version 1.15