/* ✔ */ #pragma once #include "Bank.hpp" #include "BankConfig.hpp" #include BEGIN_CS_NAMESPACE /// Class for keeping track of the active bank, and allows locking of the /// bank setting. /// /// @see @ref OutputBankableMIDIAddress class OutputBankableMIDIAddress_Base { protected: /** * @brief Constructor. * * @param bank * The bank to add this element to. */ OutputBankableMIDIAddress_Base(const OutputBank &bank) : bank(bank) {} public: /** * @brief Get the actual bank setting (no matter whether the element is * locked or not). */ setting_t getRawBankSetting() const { return bank.getSelection(); } /** * @brief Get the bank setting. * * If the element is locked, the bank setting from the moment it was locked * is returned. */ setting_t getSelection() const { return lockedSetting == Unlocked ? getRawBankSetting() : lockedSetting; } /** * @brief Lock the bank setting. * * As long as it's locked, `getSelection` will return the current setting, * independent from the actual bank setting. */ void lock() { if (lockedSetting == Unlocked) lockedSetting = getRawBankSetting(); } /** * @brief Unlock the bank setting. * * After unlocking, @ref getSelection will return the actual bank setting * again. */ void unlock() { lockedSetting = Unlocked; } protected: const OutputBank &bank; private: constexpr static setting_t Unlocked = NoSetting; setting_t lockedSetting = Unlocked; }; /** * @brief A base class for all MIDIOutputElement%s that can be banked. * * @note These elements don't have to be updated when the bank setting is * changed, because they poll the bank setting each time they send a * MIDI event. * They are not added to the bank, they just keep a reference to the * bank they're a part of. * * @note To prevent 'sticky' notes (i.e. a button is pressed, a note on is * sent, the bank is changed, the button is released, and the note off * is sent to a different address, causing the first note to keep on * playing indefinitely), there must be a way to lock the bank setting * while a note is playing. Then when it is no longer playing, the * bank setting is unlocked. */ class OutputBankableMIDIAddress : public OutputBankableMIDIAddress_Base { public: /** * @brief Create a new OutputBankableMIDIAddress object. * * @param bank * The bank to add this element to. * @param type * What address type to change (address, channel or cable number). */ OutputBankableMIDIAddress(const OutputBank &bank, BankType type) : OutputBankableMIDIAddress_Base(bank), type(type) {} /** * @brief Create a new OutputBankableMIDIAddress object. * * @param config * The bank and address type to change. * * @see OutputBankableMIDIAddress::OutputBankableMIDIAddress(Bank &, BankType) */ OutputBankableMIDIAddress(BaseOutputBankConfig config) : OutputBankableMIDIAddress(config.bank, config.type) {} /** * @brief Get the offset relative to the base address. */ RelativeMIDIAddress getAddressOffset(setting_t bankindex) const { int8_t offset = bank.getOffsetOfSetting(bankindex); switch (type) { case ChangeAddress: return {offset, 0, 0}; case ChangeChannel: return {0, offset, 0}; case ChangeCable: return {0, 0, offset}; default: return {}; } } /** * @brief Get the offset relative to the base address for the active bank. */ RelativeMIDIAddress getAddressOffset() const { return getAddressOffset(getSelection()); } private: const BankType type; }; /** * @brief A base class for all MIDIOutputElement%s that can have one of many * addresses. * * The bank setting determines the address that's being used. * * @note To prevent 'sticky' notes (i.e. a button is pressed, a note on is * sent, the bank is changed, the button is released, and the note off * is sent to a different address, causing the first note to keep on * playing indefinitely), there must be a way to lock the bank setting * while a note is playing. Then when it is no longer playing, the * bank setting is unlocked. * * @invariant `getSelection` and `getRawBankSetting` always return a number in * the half-open interval * @f$ \left[0, \mathrm{bank.getNumberOfBanks()}\right) \cap * \mathbb{N} @f$. */ class ManyAddresses_Base : public OutputBankableMIDIAddress_Base { public: /** * @brief Constructor. * * @param bank * The bank to add this element to. * @tparam NumBanks * The number of bank settings @p bank has. */ template ManyAddresses_Base(const Bank &bank) : OutputBankableMIDIAddress_Base(bank) {} }; END_CS_NAMESPACE