cs-midi/Banks/BankableAddresses.hpp

165 lines
5.2 KiB
C++

/* ✔ */
#pragma once
#include "Bank.hpp"
#include "BankConfig.hpp"
#include <Def/MIDIAddress.hpp>
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<N> &, 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 <uint8_t NumBanks>
ManyAddresses_Base(const Bank<NumBanks> &bank)
: OutputBankableMIDIAddress_Base(bank) {}
};
END_CS_NAMESPACE