LCOV - code coverage report
Current view: top level - src/MIDI_Inputs/MCU - VU.hpp (source / functions) Hit Total Coverage
Test: 169c36a3797bc662d84b5726f34a3f37d3c58247 Lines: 76 89 85.4 %
Date: 2024-11-09 15:32:27 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::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

Generated by: LCOV version 1.15