LCOV - code coverage report
Current view: top level - src/Banks - BankableMIDIInput.hpp (source / functions) Hit Total Coverage
Test: e224b347cd670555e44f06608ac41bd1ace9d9d8 Lines: 62 73 84.9 %
Date: 2020-09-08 17:44:46 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 <AH/Containers/LinkedList.hpp>
       6             : #include <AH/Debug/Debug.hpp>
       7             : #include <Def/MIDIAddress.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(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         176 :     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          22 :     uint8_t getBankIndex(const MIDIAddress &target,
      72             :                          const MIDIAddress &base) const {
      73          22 :         switch (type) {
      74             :             case CHANGE_ADDRESS:
      75          24 :                 return (target.getAddress() - base.getAddress()) /
      76          12 :                        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.getRawCableNumber() - base.getRawCableNumber()) /
      82           3 :                        bank.getTracksPerBank();
      83           0 :             default: return 0;
      84             :         }
      85          22 :     }
      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          20 :     bool matchBankable(uint8_t toMatch, uint8_t base) const {
     114          20 :         uint8_t diff = toMatch - base;
     115          38 :         return toMatch >= base && diff < N * bank.getTracksPerBank() &&
     116          18 :                diff % bank.getTracksPerBank() == 0;
     117          20 :     }
     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           9 :     }
     138             : 
     139             :     /**
     140             :      * @brief   If matchBankableAddressInRange returned true, get the index of
     141             :      *          the message in the range.
     142             :      */
     143           8 :     uint8_t getRangeIndex(MIDIAddress target, MIDIAddress base) const {
     144           8 :         uint8_t diff = target.getAddress() - base.getAddress();
     145           8 :         if (type == CHANGE_ADDRESS)
     146           4 :             diff %= bank.getTracksPerBank();
     147          16 :         return diff;
     148           8 :     }
     149             : 
     150             :     /**
     151             :      * @brief   Check whether a given address is within a range of given length
     152             :      *          starting from the given base address.
     153             :      * 
     154             :      * @param   toMatch
     155             :      *          The address to check
     156             :      * @param   base
     157             :      *          The base address, start of the range.
     158             :      * @param   length
     159             :      *          The length of the range.
     160             :      */
     161          10 :     static bool inRange(uint8_t toMatch, uint8_t base, uint8_t length) {
     162          10 :         return (base <= toMatch) && (toMatch - base < length);
     163             :     }
     164             : 
     165             :     /**
     166             :      * @brief   Check whether a given address is part of the bank relative to
     167             :      *          the base address.
     168             :      * 
     169             :      * @param   toMatch 
     170             :      *          The address to check.
     171             :      * @param   base
     172             :      *          The base address (the address of bank setting 0).
     173             :      */
     174          14 :     bool matchBankable(const MIDIAddress &toMatch,
     175             :                        const MIDIAddress &base) const {
     176          14 :         if (!toMatch.isValid() || !base.isValid())
     177           0 :             return false;
     178          14 :         switch (type) {
     179             :             case CHANGE_ADDRESS: {
     180          16 :                 return toMatch.getChannel() == base.getChannel() &&
     181           8 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     182           8 :                        matchBankable(toMatch.getAddress(), base.getAddress());
     183             :             }
     184             :             case CHANGE_CHANNEL: {
     185           6 :                 return toMatch.getAddress() == base.getAddress() &&
     186           3 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     187           6 :                        matchBankable(toMatch.getRawChannel(),
     188           3 :                                      base.getRawChannel());
     189             :             }
     190             :             case CHANGE_CABLENB: {
     191           6 :                 return toMatch.getAddress() == base.getAddress() &&
     192           3 :                        toMatch.getChannel() == base.getChannel() &&
     193           6 :                        matchBankable(toMatch.getRawCableNumber(),
     194           3 :                                      base.getRawCableNumber());
     195             :             }
     196           0 :             default: return false;
     197             :         }
     198          14 :     }
     199             : 
     200             :     /**
     201             :      * @brief   Check whether a given address is part of the bank relative to
     202             :      *          the base address and within a range with a given length.
     203             :      * 
     204             :      * @param   toMatch 
     205             :      *          The address to check.
     206             :      * @param   base
     207             :      *          The base address (the address of bank setting 0).
     208             :      * @param   length
     209             :      *          The length of the range.
     210             :      */
     211          20 :     bool matchBankableAddressInRange(const MIDIAddress &toMatch,
     212             :                                      const MIDIAddress &base,
     213             :                                      uint8_t length) const {
     214          20 :         if (!toMatch.isValid() || !base.isValid())
     215           0 :             return false;
     216          20 :         switch (type) {
     217             :             case CHANGE_ADDRESS: {
     218          19 :                 return toMatch.getChannel() == base.getChannel() &&
     219           9 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     220          18 :                        matchBankableInRange(toMatch.getAddress(),
     221           9 :                                             base.getAddress(), length);
     222             :             }
     223             :             case CHANGE_CHANNEL: {
     224          26 :                 return inRange(toMatch.getAddress(), base.getAddress(),
     225          20 :                                length) &&
     226           6 :                        toMatch.getCableNumber() == base.getCableNumber() &&
     227          12 :                        matchBankable(toMatch.getRawChannel(),
     228           6 :                                      base.getRawChannel());
     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.getRawCableNumber(),
     235           0 :                                      base.getRawCableNumber());
     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-6-g40580cd