LCOV - code coverage report
Current view: top level - src/Banks - BankableMIDIInput.hpp (source / functions) Hit Total Coverage
Test: 90a1b9beff85a60dc6ebcea034a947a845e56960 Lines: 58 69 84.1 %
Date: 2019-11-30 15:53:32 Functions: 20 26 76.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include "Bank.hpp"
       4             : #include "BankConfig.hpp"
       5             : #include <Def/MIDICNChannelAddress.hpp>
       6             : #include <AH/Debug/Debug.hpp>
       7             : #include <AH/Containers/LinkedList.hpp>
       8             : 
       9             : BEGIN_CS_NAMESPACE
      10             : 
      11             : /**
      12             :  * @brief   A base class for all MIDIInputElement%s that can be banked.
      13             :  * 
      14             :  * @note    These elements must be updated when the bank setting is changed, so 
      15             :  *          they are added to a linked list of the bank.
      16             :  * 
      17             :  * @tparam  N
      18             :  *          The number of banks.
      19             :  */
      20             : template <setting_t N>
      21             : class BankableMIDIInput : public DoublyLinkable<BankableMIDIInput<N>> {
      22             :     friend class Bank<N>;
      23             : 
      24             :   protected:
      25             :     /**
      26             :      * @brief   Create a new BankableMIDIInput object, and add it to the bank.
      27             :      * 
      28             :      * @param   bank
      29             :      *          The bank to add this element to.
      30             :      * @param   type
      31             :      *          What address type to change (address, channel or cable number).
      32             :      */
      33          16 :     BankableMIDIInput(Bank<N> &bank, BankType type) : bank(bank), type(type) {
      34          16 :         bank.add(this);
      35          16 :     }
      36             : 
      37             :     /**
      38             :      * @brief   Create a new BankableMIDIInput object, and add it to the bank.
      39             :      * 
      40             :      * @param   config
      41             :      *          The bank and address type to change.
      42             :      * 
      43             :      * @see     BankableMIDIInput::BankableMIDIInput(Bank<N> &, BankType)
      44             :      */
      45          13 :     BankableMIDIInput(const BankConfig<N> &config)
      46          13 :         : BankableMIDIInput<N>(config.bank, config.type) {}
      47             : 
      48             :   public:
      49             :     /** 
      50             :      * @brief   Destructor: remove element from the bank.
      51             :      */
      52          16 :     virtual ~BankableMIDIInput() { bank.remove(this); }
      53             : 
      54             :     /** 
      55             :      * @brief   Get the current bank setting.
      56             :      * 
      57             :      * @see     Bank<N>::getSelection()
      58             :      */
      59         164 :     setting_t getSelection() const { return bank.getSelection(); }
      60             : 
      61             :     /**
      62             :      * @brief   Calculate the bank setting of a given MIDI address, relative to
      63             :      *          a base address.
      64             :      * 
      65             :      * @param   target 
      66             :      *          The MIDI address to calculate the bank setting of.
      67             :      * @param   base
      68             :      *          The base address to compare it to (the address of bank setting 
      69             :      *          0).
      70             :      */
      71          21 :     uint8_t getBankIndex(const MIDICNChannelAddress &target,
      72             :                          const MIDICNChannelAddress &base) const {
      73          21 :         switch (type) {
      74             :             case CHANGE_ADDRESS:
      75          22 :                 return (target.getAddress() - base.getAddress()) /
      76          11 :                        bank.getTracksPerBank();
      77             :             case CHANGE_CHANNEL:
      78          14 :                 return (target.getRawChannel() - base.getRawChannel()) /
      79           7 :                        bank.getTracksPerBank();
      80             :             case CHANGE_CABLENB:
      81           6 :                 return (target.getCableNumber() - base.getCableNumber()) /
      82           3 :                        bank.getTracksPerBank();
      83           0 :             default: return 0;
      84             :         }
      85          21 :     }
      86             : 
      87             :   protected:
      88             :     /**
      89             :      * @brief   Check if the given address is part of the bank relative to the
      90             :      *          base address.
      91             :      * 
      92             :      * Consider the following example:  
      93             :      * A Bank with 4 tracks per bank (T), 2 bank settings (N), 
      94             :      * and a base address of 3.
      95             :      * 
      96             :      * ```
      97             :      * 0   1   2   3   4   5   6   7   8   9  10  11  12  ...
      98             :      * F   F   F   T   F   F   F   T   F   F   F   F   F  ...
      99             :      * ```
     100             :      * 
     101             :      * Addresses before the base adddress are not matched (0, 1, 2).  
     102             :      * Addresses after N * T are not matched (8, 9, 10, 11, 12).  
     103             :      * Addresses with a distance to the base address that is not a multiple of N
     104             :      * are not matched (4, 5, 6).
     105             :      * 
     106             :      * @param   toMatch 
     107             :      *          The address to check.
     108             :      * @param   base
     109             :      *          The base address (the address of bank setting 0).
     110             :      * 
     111             :      * @note    Equivalent to `matchBankableInRange(toMatch, base, 1)`.
     112             :      */
     113          19 :     bool matchBankable(uint8_t toMatch, uint8_t base) const {
     114          19 :         uint8_t diff = toMatch - base;
     115          36 :         return toMatch >= base && diff < N * bank.getTracksPerBank() &&
     116          17 :                diff % bank.getTracksPerBank() == 0;
     117             :     }
     118             : 
     119             :     /**
     120             :      * @brief   Check if the given address is part of the bank relative to the
     121             :      *          base address.
     122             :      * 
     123             :      * @todo    This is very hard to explain without a specific example ...
     124             :      * 
     125             :      * @param   toMatch 
     126             :      *          The address to check.
     127             :      * @param   base
     128             :      *          The base address (the address of bank setting 0).
     129             :      * @param   length
     130             :      *          The length of the range.
     131             :      */
     132           9 :     bool matchBankableInRange(uint8_t toMatch, uint8_t base,
     133             :                               uint8_t length) const {
     134           9 :         uint8_t diff = toMatch - base;
     135          15 :         return toMatch >= base && diff < N * bank.getTracksPerBank() &&
     136           6 :                diff % bank.getTracksPerBank() < length;
     137             :     }
     138             : 
     139             :     /**
     140             :      * @brief   If matchBankableAddressInRange returned true, get the index of
     141             :      *          the message in the range.
     142             :      */
     143           8 :     uint8_t getRangeIndex(MIDICNChannelAddress target,
     144             :                           MIDICNChannelAddress base) const {
     145           8 :         uint8_t diff = target.getAddress() - base.getAddress();
     146           8 :         if (type == CHANGE_ADDRESS)
     147           4 :             diff %= bank.getTracksPerBank();
     148           8 :         return diff;
     149             :     }
     150             : 
     151             :     /**
     152             :      * @brief   Check whether a given address is within a range of given length
     153             :      *          starting from the given base address.
     154             :      * 
     155             :      * @param   toMatch
     156             :      *          The address to check
     157             :      * @param   base
     158             :      *          The base address, start of the range.
     159             :      * @param   length
     160             :      *          The length of the range.
     161             :      */
     162          10 :     static bool inRange(uint8_t toMatch, uint8_t base, uint8_t length) {
     163          10 :         return (base <= toMatch) && (toMatch - base < length);
     164             :     }
     165             : 
     166             :     /**
     167             :      * @brief   Check whether a given address is part of the bank relative to
     168             :      *          the base address.
     169             :      * 
     170             :      * @param   toMatch 
     171             :      *          The address to check.
     172             :      * @param   base
     173             :      *          The base address (the address of bank setting 0).
     174             :      */
     175          19 :     bool matchBankable(const MIDICNChannelAddress &toMatch,
     176             :                        const MIDICNChannelAddress &base) const {
     177          19 :         if (!toMatch.isValid() || !base.isValid())
     178           0 :             return false;
     179          19 :         switch (type) {
     180             :             case CHANGE_ADDRESS: {
     181          14 :                 return toMatch.getChannel() == base.getChannel() &&
     182           7 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     183           7 :                        matchBankable(toMatch.getAddress(), base.getAddress());
     184             :             }
     185             :             case CHANGE_CHANNEL: {
     186          18 :                 return toMatch.getAddress() == base.getAddress() &&
     187           9 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     188          18 :                        matchBankable(toMatch.getRawChannel(),
     189           9 :                                      base.getRawChannel());
     190             :             }
     191             :             case CHANGE_CABLENB: {
     192           6 :                 return toMatch.getAddress() == base.getAddress() &&
     193           3 :                        toMatch.getChannel() == base.getChannel() &&
     194           6 :                        matchBankable(toMatch.getCableNumber(),
     195           3 :                                      base.getCableNumber());
     196             :             }
     197           0 :             default: return false;
     198             :         }
     199          19 :     }
     200             : 
     201             :     /**
     202             :      * @brief   Check whether a given address is part of the bank relative to
     203             :      *          the base address and within a range with a given length.
     204             :      * 
     205             :      * @param   toMatch 
     206             :      *          The address to check.
     207             :      * @param   base
     208             :      *          The base address (the address of bank setting 0).
     209             :      * @param   length
     210             :      *          The length of the range.
     211             :      */
     212          20 :     bool matchBankableAddressInRange(const MIDICNChannelAddress &toMatch,
     213             :                                      const MIDICNChannelAddress &base,
     214             :                                      uint8_t length) const {
     215          20 :         if (!toMatch.isValid() || !base.isValid())
     216           0 :             return false;
     217          20 :         switch (type) {
     218             :             case CHANGE_ADDRESS: {
     219          19 :                 return toMatch.getChannel() == base.getChannel() &&
     220           9 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     221          18 :                        matchBankableInRange(toMatch.getAddress(),
     222           9 :                                             base.getAddress(), length);
     223             :             }
     224             :             case CHANGE_CHANNEL: {
     225          26 :                 return inRange(toMatch.getAddress(), base.getAddress(),
     226          20 :                                length) &&
     227           6 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     228           6 :                        matchBankable(toMatch.getChannel(), base.getChannel());
     229             :             }
     230             :             case CHANGE_CABLENB: {
     231           0 :                 return inRange(toMatch.getAddress(), base.getAddress(),
     232           0 :                                length) &&
     233           0 :                        toMatch.getChannel() == base.getChannel() &&
     234           0 :                        matchBankable(toMatch.getCableNumber(),
     235           0 :                                      base.getCableNumber());
     236             :             }
     237           0 :             default: return false;
     238             :         }
     239          20 :     }
     240             : 
     241             :   private:
     242             :     Bank<N> &bank;
     243             :     const BankType type;
     244             : 
     245             :     /**
     246             :      * @brief   A function to be executed each time the bank setting changes.
     247             :      * 
     248             :      * Think of an LED that indicates whether a track is muted or not. If this 
     249             :      * LED is bankable, let's say with 4 tracks per bank, 2 banks, and a base
     250             :      * address of 3, then this LED object keeps the state of tracks 3 and 7.
     251             :      * When the bank setting is 0, the LED displays the state of track 3, when
     252             :      * the bank setting is 1, the LED displays the state of track 7.  
     253             :      * To know when to update the LED, this callback is used.
     254             :      */
     255           0 :     virtual void onBankSettingChange() {}
     256             : };
     257             : 
     258             : END_CS_NAMESPACE

Generated by: LCOV version 1.14-5-g4ff2ed6