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