Line data Source code
1 : /* ✔ */ 2 : 3 : #pragma once 4 : 5 : #include "Bank.hpp" 6 : #include "BankConfig.hpp" 7 : #include <Def/MIDICNChannelAddress.hpp> 8 : 9 : BEGIN_CS_NAMESPACE 10 : 11 : class BankableMIDIOutput_Base { 12 : protected: 13 : /** 14 : * @brief Constructor. 15 : * 16 : * @param bank 17 : * The bank to add this element to. 18 : */ 19 37 : BankableMIDIOutput_Base(const OutputBank &bank) : bank(bank) {} 20 : 21 : public: 22 : /** 23 : * @brief Get the actual bank setting (no matter whether the element is 24 : * locked or not). 25 : */ 26 22 : setting_t getRawBankSetting() const { return bank.getSelection(); } 27 : 28 : /** 29 : * @brief Get the bank setting. 30 : * 31 : * If the element is locked, the bank setting from the moment it was locked 32 : * is returned. 33 : */ 34 44 : setting_t getSelection() const { 35 44 : return lockedSetting == UNLOCKED ? getRawBankSetting() : lockedSetting; 36 : } 37 : 38 : /** 39 : * @brief Lock the bank setting. 40 : * 41 : * As long as it's locked, `getSelection` will return the current setting, 42 : * independent from the actual bank setting. 43 : */ 44 14 : void lock() { 45 14 : if (lockedSetting == UNLOCKED) 46 14 : lockedSetting = getRawBankSetting(); 47 14 : } 48 : 49 : /** 50 : * @brief Unlock the bank setting. 51 : * 52 : * After unlocking, `getSelection` will return the actual bank setting 53 : * again. 54 : */ 55 14 : void unlock() { lockedSetting = UNLOCKED; } 56 : 57 : protected: 58 : const OutputBank &bank; 59 : 60 : private: 61 : constexpr static setting_t UNLOCKED = NO_SETTING; 62 37 : setting_t lockedSetting = UNLOCKED; 63 : }; 64 : 65 : /** 66 : * @brief A base class for all MIDIOutputElement%s that can be banked. 67 : * 68 : * @note These elements don't have to be updated when the bank setting is 69 : * changed, because they poll the bank setting each time they send a 70 : * MIDI event. 71 : * They are not added to the bank, they just keep a reference to the 72 : * bank they're a part of. 73 : * 74 : * @note To prevent 'sticky' notes (i.e. a button is pressed, a note on is 75 : * sent, the bank is changed, the button is released, and the note off 76 : * is sent to a different address, causing the first note to keep on 77 : * playing indefinitely), there must be a way to lock the bank setting 78 : * while a note is playing. Then when it is no longer playing, the 79 : * bank setting is unlocked. 80 : * 81 : * @todo Create BankableMIDIElement class, make this inherit from it, as 82 : * well as ManyAddressesMIDIOutput. 83 : */ 84 : class BankableMIDIOutput : public BankableMIDIOutput_Base { 85 : public: 86 : /** 87 : * @brief Create a new BankableMIDIOutput object. 88 : * 89 : * @param bank 90 : * The bank to add this element to. 91 : * @param type 92 : * What address type to change (address, channel or cable number). 93 : */ 94 32 : BankableMIDIOutput(const OutputBank &bank, BankType type) 95 32 : : BankableMIDIOutput_Base{bank}, type(type) {} 96 : 97 : /** 98 : * @brief Create a new BankableMIDIOutput object. 99 : * 100 : * @param config 101 : * The bank and address type to change. 102 : * 103 : * @see BankableMIDIOutput::BankableMIDIOutput(Bank<N> &, BankType) 104 : */ 105 32 : BankableMIDIOutput(const OutputBankConfig &config) 106 32 : : BankableMIDIOutput(config.bank, config.type) {} 107 : 108 : /** 109 : * @brief Get the offset relative to the base address. 110 : */ 111 36 : RelativeMIDICNChannelAddress getAddressOffset() const { 112 36 : int8_t offset = getSelection() * bank.getTracksPerBank(); 113 36 : switch (type) { 114 36 : case CHANGE_ADDRESS: return {offset, 0, 0}; 115 0 : case CHANGE_CHANNEL: return {0, offset, 0}; 116 0 : case CHANGE_CABLENB: return {0, 0, offset}; 117 0 : default: return {}; 118 : } 119 36 : } 120 : 121 : private: 122 : const BankType type; 123 : }; 124 : 125 : /** 126 : * @brief A base class for all MIDIOutputElement%s that can have one of many 127 : * addresses. 128 : * 129 : * The bank setting determines the address that's being used. 130 : * 131 : * @tparam N 132 : * The number of addresses the element has. 133 : * 134 : * @note To prevent 'sticky' notes (i.e. a button is pressed, a note on is 135 : * sent, the bank is changed, the button is released, and the note off 136 : * is sent to a different address, causing the first note to keep on 137 : * playing indefinitely), there must be a way to lock the bank setting 138 : * while a note is playing. Then when it is no longer playing, the 139 : * bank setting is unlocked. 140 : * 141 : * @invariant `getSelection` and `getRawBankSetting` always return a number in 142 : * the half-open interval 143 : * @f$ \left[0, \mathrm{bank.getNumberOfBanks()}\right) \cap 144 : * \mathbb{N} @f$. 145 : */ 146 : class ManyAddressesMIDIOutput : public BankableMIDIOutput_Base { 147 : public: 148 : /** 149 : * @brief Constructor. 150 : * 151 : * @param bank 152 : * The bank to add this element to. 153 : * @tparam NumBanks 154 : * The number of bank settings @p bank has. 155 : */ 156 : template <uint8_t NumBanks> 157 5 : ManyAddressesMIDIOutput(const Bank<NumBanks> &bank) 158 5 : : BankableMIDIOutput_Base{bank} {} 159 : }; 160 : 161 : END_CS_NAMESPACE