LCOV - code coverage report
Current view: top level - src/MIDI_Inputs/MCU - VPotRing.hpp (source / functions) Hit Total Coverage
Test: ffed98f648fe78e7aa7bdd228474317d40dadbec Lines: 58 85 68.2 %
Date: 2022-05-28 15:22:59 Functions: 28 36 77.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <AH/STL/algorithm>
       4             : #include <AH/Timing/MillisMicrosTimer.hpp>
       5             : #include <MIDI_Inputs/InterfaceMIDIInputElements.hpp>
       6             : #include <MIDI_Inputs/MIDIInputElementMatchers.hpp>
       7             : 
       8             : BEGIN_CS_NAMESPACE
       9             : 
      10             : namespace MCU {
      11             : 
      12             : /// Struct that keeps track of the value and overload indicator of a Mackie
      13             : /// Control Universal VPot LED ring.
      14             : struct VPotState {
      15             :     /// Constructor.
      16           9 :     VPotState(uint8_t value = 0) : value(value) {}
      17             : 
      18             :     uint8_t value; ///< The value representing the VPot position, mode and LED.
      19             : 
      20           7 :     bool update(uint8_t data) {
      21           7 :         data = sanitizeData(data);
      22           7 :         bool changed = data != this->value;
      23           7 :         this->value = data;
      24           7 :         return changed;
      25             :     }
      26             : 
      27             :     /// Determines how the VPot value is displayed using the LEDs.
      28             :     enum Mode {
      29             :         SingleDot = 0, ///< Single dot.
      30             :         BoostCut = 1,  ///< Boost/Cut.
      31             :         Wrap = 2,      ///< Wrap.
      32             :         Spread = 3,    ///< Spread.
      33             :     };
      34             : 
      35             :     /// @name Data access
      36             :     /// @{
      37             : 
      38             :     /// Return the position of the V-Pot ring. [0, 11]
      39          17 :     uint8_t getPosition() const { return value & 0x0F; }
      40             :     /// Return the status of the center LED of the V-Pot ring.
      41          11 :     bool getCenterLed() const { return value & 0x40; }
      42             :     /// Return the mode of the V-Pot ring.
      43          16 :     Mode getMode() const { return static_cast<Mode>((value & 0x30) >> 4); }
      44             : 
      45             :     /// Get the first segment that should be on.
      46           3 :     uint8_t getStartOn() const {
      47           3 :         int8_t position = getPosition();
      48           3 :         if (position == 0)
      49           1 :             return 0;
      50           2 :         int8_t value = position - 1;
      51           2 :         switch (getMode()) {
      52           0 :             case SingleDot: return value;
      53           2 :             case BoostCut: return std::min(+value, 5);
      54           0 :             case Wrap: return 0;
      55           0 :             case Spread: return std::max(5 - value, 0);
      56           0 :             default: return 0;
      57             :         }
      58             :     }
      59             : 
      60             :     /// Get the first segment that should be off.
      61           3 :     uint8_t getStartOff() const {
      62           3 :         uint8_t value = getPosition();
      63           3 :         switch (getMode()) {
      64           1 :             case SingleDot: return value;
      65           2 :             case BoostCut: return std::max(+value, 6);
      66           0 :             case Wrap: return value;
      67           0 :             case Spread: return std::min(5 + value, 11);
      68           0 :             default: return 0;
      69             :         }
      70             :     }
      71             : 
      72             :     /// @}
      73             : 
      74             :     /// Make sure that the received value is valid and will not result in array
      75             :     /// out of bounds conditions.
      76           7 :     static uint8_t sanitizeData(uint8_t data) {
      77             :         // Maximum valid position value is 0xB.
      78           7 :         return (data & 0x0F) <= 0x0B ? data : ((data & 0xF0) | 0x0B);
      79             :     }
      80             : };
      81             : 
      82             : // -------------------------------------------------------------------------- //
      83             : 
      84             : /**
      85             :  * @brief   MIDI Input matcher for Mackie Control Universal VPot LED rings.
      86             :  *
      87             :  * In the Mackie Control Universal protocol, the VPot LED rings are updated 
      88             :  * using Control Change events.  
      89             :  * Each device (cable number) has eight VPots for the eight tracks. Only
      90             :  * MIDI channel 1 is used in the original protocol.
      91             :  * 
      92             :  * The format of the MIDI message is as follows:
      93             :  * | Status      | Data 1      | Data 2      |
      94             :  * |:-----------:|:-----------:|:-----------:|
      95             :  * | `1011 cccc` | `0011 0ttt` | `0pxx vvvv` |
      96             :  * 
      97             :  * - `1011` (or `0xB`) is the status for Control Change events
      98             :  * - `cccc` is the MIDI channel [0-15]
      99             :  * - `ttt` is the track index [0-7]
     100             :  * - `p` is the state of the center LED
     101             :  * - `xx` is the display mode
     102             :  * - `vvvv` is the VPot value [0, 11]
     103             :  * 
     104             :  * The display modes are defined in @ref MCU::VPotState::Mode.
     105             :  * 
     106             :  * @ingroup    MIDIInputMatchers
     107             :  */
     108             : struct VPotMatcher : public TwoByteMIDIMatcher {
     109             :     /**
     110             :      * @brief   Constructor.
     111             :      * 
     112             :      * @param   track
     113             :      *          The track of the VPot [1, 8].
     114             :      * @param   channelCN
     115             :      *          The MIDI channel [CHANNEL_1, CHANNEL_16] and Cable Number 
     116             :      *          [CABLE_1, CABLE_16].
     117             :      */
     118           1 :     VPotMatcher(uint8_t track, MIDIChannelCable channelCN)
     119           1 :         : TwoByteMIDIMatcher({track + 0x30 - 1, channelCN}) {}
     120             : };
     121             : 
     122             : /// MIDI Input matcher for Mackie Control Universal VPot LED rings with bank
     123             : /// support.
     124             : /// @see    @ref MCU::VPotMatcher
     125             : /// @ingroup    MIDIInputMatchers
     126             : template <uint8_t BankSize>
     127             : struct BankableVPotMatcher : public BankableTwoByteMIDIMatcher<BankSize> {
     128             :     /**
     129             :      * @brief   Constructor.
     130             :      * 
     131             :      * @param   config
     132             :      *          The bank configuration to use: the bank to add this element to,
     133             :      *          and whether to change the address, channel or cable number.
     134             :      * @param   track
     135             :      *          The track of the VPot [1, 8].
     136             :      * @param   channelCN
     137             :      *          The MIDI channel [CHANNEL_1, CHANNEL_16] and Cable Number 
     138             :      *          [CABLE_1, CABLE_16].
     139             :      */
     140           4 :     BankableVPotMatcher(BankConfig<BankSize> config, uint8_t track,
     141             :                         MIDIChannelCable channelCN)
     142             :         : BankableTwoByteMIDIMatcher<BankSize>(config,
     143           4 :                                                {track + 0x30 - 1, channelCN}) {}
     144             : };
     145             : 
     146             : // -------------------------------------------------------------------------- //
     147             : 
     148             : /** 
     149             :  * @brief   A MIDI input element that represents a Mackie Control Universal VPot
     150             :  *          ring.
     151             :  * 
     152             :  * @ingroup MIDIInputElements
     153             :  */
     154             : class VPotRing
     155             :     : public MatchingMIDIInputElement<MIDIMessageType::CONTROL_CHANGE,
     156             :                                       VPotMatcher>,
     157             :       public Interfaces::MCU::IVPot {
     158             :   public:
     159             :     using Matcher = VPotMatcher;
     160             :     using Parent 
     161             :         = MatchingMIDIInputElement<MIDIMessageType::CONTROL_CHANGE, Matcher>;
     162             : 
     163             :     /**
     164             :      * @brief   Constructor.
     165             :      * 
     166             :      * @param   track
     167             :      *          The track of the VPot [1, 8].
     168             :      * @param   channelCN
     169             :      *          The MIDI channel [CHANNEL_1, CHANNEL_16] and Cable Number 
     170             :      *          [CABLE_1, CABLE_16].
     171             :      */
     172           1 :     VPotRing(uint8_t track, MIDIChannelCable channelCN = CHANNEL_1)
     173           1 :         : Parent({track, channelCN}) {}
     174             : 
     175             :   protected:
     176           2 :     bool handleUpdateImpl(typename Matcher::Result match) {
     177           2 :         return state.update(match.value);
     178             :     }
     179             : 
     180           2 :     void handleUpdate(typename Matcher::Result match) override {
     181           2 :         dirty |= handleUpdateImpl(match);
     182           2 :     }
     183             : 
     184             :   public:
     185             :     /// @name Data access
     186             :     /// @{
     187             : 
     188             :     /// Get the full state of the VPot ring.
     189             :     VPotState getState() const { return state; }
     190             : 
     191             :     /// @copydoc    VPotState::getPosition
     192           2 :     uint8_t getPosition() const { return state.getPosition(); }
     193             :     /// @copydoc    VPotState::getCenterLed
     194           2 :     bool getCenterLed() const override { return state.getCenterLed(); }
     195             :     /// @copydoc    VPotState::getMode
     196           2 :     VPotState::Mode getMode() const { return state.getMode(); }
     197             : 
     198             :     /// @copydoc    VPotState::getStartOn
     199           0 :     uint8_t getStartOn() const override { return state.getStartOn(); }
     200             :     /// @copydoc    VPotState::getStartOff
     201           0 :     uint8_t getStartOff() const override { return state.getStartOff(); }
     202             : 
     203             :     /// @}
     204             : 
     205             :     /// Reset the state to zero.
     206           0 :     void reset() override {
     207           0 :         if (!ignoreReset) {
     208           0 :             state = {};
     209           0 :             dirty = true;
     210             :         }
     211           0 :     }
     212             : 
     213             :   private:
     214             :     VPotState state = {};
     215             : 
     216             :   public:
     217             :     /// Don't reset the state when calling the `reset` method. This is the
     218             :     /// default, because in the original MCU, VPots don't get reset when a
     219             :     /// Reset All Controllers message is received.
     220             :     bool ignoreReset = true;
     221             : };
     222             : 
     223             : // -------------------------------------------------------------------------- //
     224             : 
     225             : namespace Bankable {
     226             : 
     227             : /** 
     228             :  * @brief   A MIDI input element that represents a Mackie Control Universal VPot
     229             :  *          ring. This version can be banked.
     230             :  *
     231             :  * @tparam  BankSize
     232             :  *          The number of banks.
     233             :  * 
     234             :  * @ingroup BankableMIDIInputElements
     235             :  */
     236             : template <uint8_t BankSize>
     237             : class VPotRing
     238             :     : public BankableMatchingMIDIInputElement<MIDIMessageType::CONTROL_CHANGE,
     239             :                                               BankableVPotMatcher<BankSize>>,
     240             :       public Interfaces::MCU::IVPot {
     241             :   public:
     242             :     using Matcher = BankableVPotMatcher<BankSize>;
     243             :     using Parent 
     244             :         = BankableMatchingMIDIInputElement<MIDIMessageType::CONTROL_CHANGE,
     245             :                                            Matcher>;
     246             :     /**
     247             :      * @brief   Constructor.
     248             :      * 
     249             :      * @param   config
     250             :      *          The bank configuration to use: the bank to add this element to,
     251             :      *          and whether to change the address, channel or cable number.
     252             :      * @param   track
     253             :      *          The track of the VPot [1, 8].
     254             :      * @param   channelCN
     255             :      *          The MIDI channel [CHANNEL_1, CHANNEL_16] and Cable Number 
     256             :      *          [CABLE_1, CABLE_16].
     257             :      */
     258           4 :     VPotRing(BankConfig<BankSize> config, uint8_t track,
     259             :              MIDIChannelCable channelCN = CHANNEL_1)
     260           4 :         : Parent({config, track, channelCN}) {}
     261             : 
     262             :   protected:
     263           5 :     bool handleUpdateImpl(typename Matcher::Result match) {
     264          10 :         return states[match.bankIndex].update(match.value) &&
     265          10 :                match.bankIndex == this->getActiveBank();
     266             :         // Only mark dirty if the value of the active bank changed
     267             :     }
     268             :     
     269           3 :     void handleUpdate(typename Matcher::Result match) override {
     270           3 :         dirty |= handleUpdateImpl(match);
     271           3 :     }
     272             : 
     273             :   public:
     274             :     /// @name Data access
     275             :     /// @{
     276             : 
     277             :     /// Get the full state of the VPot ring. (For the given bank.)
     278          30 :     VPotState getState(uint8_t bank) const { return states[bank]; }
     279             : 
     280             :     /// @copydoc    VPotState::getPosition
     281             :     /// (For the given bank.)
     282           9 :     uint8_t getPosition(uint8_t bank) const {
     283           9 :         return getState(bank).getPosition();
     284             :     }
     285             :     /// @copydoc    VPotState::getCenterLed
     286             :     /// (For the given bank.)
     287           9 :     bool getCenterLed(uint8_t bank) const {
     288           9 :         return getState(bank).getCenterLed();
     289             :     }
     290             :     /// @copydoc    VPotState::getMode
     291             :     /// (For the given bank.)
     292           9 :     VPotState::Mode getMode(uint8_t bank) const {
     293           9 :         return getState(bank).getMode();
     294             :     }
     295             : 
     296             :     /// @copydoc    VPotState::getStartOn
     297             :     /// (For the given bank.)
     298           0 :     uint8_t getStartOn(uint8_t bank) const {
     299           0 :         return getState(bank).getStartOn();
     300             :     }
     301             :     /// @copydoc    VPotState::getStartOff
     302             :     /// (For the given bank.)
     303           0 :     uint8_t getStartOff(uint8_t bank) const {
     304           0 :         return getState(bank).getStartOff();
     305             :     }
     306             : 
     307             :     /// Get the full state of the VPot ring. (For the active bank.)
     308           3 :     VPotState getState() const { return getState(this->getActiveBank()); }
     309             : 
     310             :     /// @copydoc    VPotState::getPosition
     311             :     /// (For the active bank.)
     312           9 :     uint8_t getPosition() const { return getPosition(this->getActiveBank()); }
     313             :     /// @copydoc    VPotState::getCenterLed
     314             :     /// (For the active bank.)
     315           9 :     bool getCenterLed() const override {
     316           9 :         return getCenterLed(this->getActiveBank());
     317             :     }
     318             :     /// @copydoc    VPotState::getMode
     319             :     /// (For the active bank.)
     320           9 :     VPotState::Mode getMode() const { return getMode(this->getActiveBank()); }
     321             : 
     322             :     /// @copydoc    VPotState::getStartOn
     323             :     /// (For the active bank.)
     324           0 :     uint8_t getStartOn() const override {
     325           0 :         return getStartOn(this->getActiveBank());
     326             :     }
     327             :     /// @copydoc    VPotState::getStartOff
     328             :     /// (For the active bank.)
     329           0 :     uint8_t getStartOff() const override {
     330           0 :         return getStartOff(this->getActiveBank());
     331             :     }
     332             : 
     333             :     /// @}
     334             : 
     335             :     /// Reset all states to zero.
     336           0 :     void reset() override {
     337           0 :         if (!ignoreReset) {
     338           0 :             states = {{}};
     339           0 :             dirty = true;
     340             :         }
     341           0 :     }
     342             : 
     343             :   protected:
     344           4 :     void onBankSettingChange() override { dirty = true; }
     345             : 
     346             :   private:
     347             :     AH::Array<VPotState, BankSize> states = {{}};
     348             : 
     349             :   public:
     350             :     /// Don't reset the state when calling the `reset` method. This is the
     351             :     /// default, because in the original MCU, VPots don't get reset when a
     352             :     /// Reset All Controllers message is received.
     353             :     bool ignoreReset = true;
     354             : };
     355             : 
     356             : } // namespace Bankable
     357             : 
     358             : } // namespace MCU
     359             : 
     360             : END_CS_NAMESPACE

Generated by: LCOV version 1.15