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
|