Line data Source code
1 : /* ✔ */
2 :
3 : #pragma once
4 :
5 : #include <Def/Def.hpp>
6 :
7 : BEGIN_CS_NAMESPACE
8 :
9 : /// A struct for saving a MIDI address consisting of a 7-bit address, a 4-bit
10 : /// channel, and a 4-bit cable number.
11 : /// A MIDI address can be marked "invalid". The MIDI sending functions
12 : /// (@ref MIDI_Sender) will never send messages addressed to an invalid address.
13 : struct __attribute__((packed)) RawMIDIAddress {
14 : bool valid : 1;
15 : uint8_t address : 7;
16 : uint8_t channel : 4;
17 : uint8_t cableNumber : 4;
18 : };
19 :
20 : /// A class for saving a MIDI channel and cable number.
21 : /// A MIDI channel and cable number can be marked "invalid".
22 : /// The MIDI sending functions (@ref MIDI_Sender) will never send messages
23 : /// addressed to invalid channels or cables.
24 : class MIDIChannelCable {
25 : friend class MIDIAddress;
26 :
27 : public:
28 : /// @name Constructors
29 : /// @{
30 :
31 : constexpr MIDIChannelCable() : addresses {0, 0, 0, 0} {}
32 245 : constexpr MIDIChannelCable(Channel channel, Cable cableNumber = Cable_1)
33 245 : : addresses {1, 0, channel.getRaw(), cableNumber.getRaw()} {}
34 :
35 : constexpr static MIDIChannelCable invalid() { return {}; }
36 :
37 : /// @}
38 :
39 : public:
40 : /// @name Member access
41 : /// @{
42 :
43 : /// Get the channel [1, 16].
44 20 : constexpr Channel getChannel() const { return Channel {addresses.channel}; }
45 : /// Get the channel as an integer [0, 15].
46 234 : constexpr uint8_t getRawChannel() const { return addresses.channel; }
47 :
48 : /// Get the cable number [Cable_1, Cable_16].
49 20 : constexpr Cable getCableNumber() const {
50 20 : return Cable(addresses.cableNumber);
51 : }
52 : /// Get the cable number as an integer [0, 15].
53 234 : constexpr uint8_t getRawCableNumber() const {
54 234 : return addresses.cableNumber;
55 : }
56 :
57 : /// @}
58 :
59 : public:
60 : /// @name Checks
61 : /// @{
62 :
63 : /// Check for equality: two addresses are equal if and only if they are both
64 : /// valid addresses and the MIDI channel and MIDI USB cable number are
65 : /// equal.
66 : /// @note Invalid addresses are not equal nor inequal.
67 4 : constexpr bool operator==(MIDIChannelCable rhs) const {
68 8 : return this->addresses.valid && rhs.addresses.valid &&
69 11 : this->addresses.channel == rhs.addresses.channel &&
70 7 : this->addresses.cableNumber == rhs.addresses.cableNumber;
71 : }
72 :
73 : /// Check for inequality: two addresses are not equal if and only if they
74 : /// are both valid addresses and have a MIDI channel or MIDI USB cable
75 : /// number that differs.
76 : /// @note Invalid addresses are not equal nor inequal.
77 : constexpr bool operator!=(MIDIChannelCable rhs) const {
78 : return this->addresses.valid && rhs.addresses.valid &&
79 : !(this->addresses.channel == rhs.addresses.channel &&
80 : this->addresses.cableNumber == rhs.addresses.cableNumber);
81 : }
82 :
83 : /// Check if the MIDI address is valid.
84 20 : constexpr bool isValid() const { return addresses.valid; }
85 :
86 : /// Check if the MIDI address is valid.
87 : /// @see isValid
88 20 : constexpr explicit operator bool() const { return isValid(); }
89 :
90 : /// @}
91 :
92 : public:
93 : /// @name Base functions for address pattern matching.
94 : /// @{
95 :
96 : /// Check if two addresses match (are equal).
97 4 : static bool matchSingle(MIDIChannelCable toMatch, MIDIChannelCable base) {
98 4 : return base == toMatch;
99 : }
100 :
101 : /// @}
102 :
103 : protected:
104 9 : constexpr MIDIChannelCable(RawMIDIAddress addresses)
105 9 : : addresses(addresses) {}
106 :
107 : private:
108 : RawMIDIAddress addresses;
109 : };
110 :
111 : /// A class for saving an offset to a MIDI address.
112 : /// It can be added to a MIDIAddress.
113 : class RelativeMIDIAddress {
114 : friend class MIDIAddress;
115 :
116 : public:
117 0 : constexpr RelativeMIDIAddress() : addresses {0, 0, 0, 0} {}
118 40 : constexpr RelativeMIDIAddress(int deltaAddress, int deltaChannel = 0,
119 : int deltaCableNumber = 0)
120 120 : : addresses {
121 : 1,
122 40 : static_cast<uint8_t>(deltaAddress),
123 40 : static_cast<uint8_t>(deltaChannel),
124 40 : static_cast<uint8_t>(deltaCableNumber),
125 40 : } {}
126 : constexpr bool isValid() const { return addresses.valid; }
127 :
128 : /// Compound addition. Element-wise addition, result is valid if both
129 : /// operands were valid.
130 : RelativeMIDIAddress &operator+=(RelativeMIDIAddress that);
131 :
132 : private:
133 : RawMIDIAddress addresses;
134 : static_assert(((-1) & 0x7F) == 0x7F,
135 : "Negative numbers must be two's complement");
136 : };
137 :
138 : /// A type-safe utility class for saving a MIDI address consisting of a 7-bit
139 : /// address, a 4-bit channel, and a 4-bit cable number.
140 : /// A MIDI address can be marked "invalid". The MIDI sending functions
141 : /// (@ref MIDI_Sender) will never send messages addressed to invalid addresses.
142 : ///
143 : /// See @ref midi_md-midi-addresses "MIDI Tutorial: MIDI addresses" for a
144 : /// tutorial on how to use MIDI addresses.
145 : class MIDIAddress {
146 : public:
147 : /// @name Constructors
148 : /// @{
149 :
150 : /// Default constructor, creates an invalid address.
151 : constexpr MIDIAddress()
152 : : addresses {
153 : 0,
154 : 0,
155 : 0,
156 : 0,
157 : } {}
158 :
159 : /**
160 : * @brief Constructor.
161 : *
162 : * @param address
163 : * The 7-bit MIDI address. Depending on the message type, this can
164 : * be the MIDI note number, the number of the MIDI Control Change
165 : * Controller, etc.
166 : * Must be a number in the range [0, 127].
167 : * @param channelCN
168 : * The MIDI Channel and the MIDI USB cable number.
169 : */
170 234 : constexpr MIDIAddress(int address, MIDIChannelCable channelCN)
171 702 : : addresses {
172 : 1,
173 234 : static_cast<uint8_t>(address),
174 234 : channelCN.getRawChannel(),
175 234 : channelCN.getRawCableNumber(),
176 234 : } {} // Deliberate overflow for negative numbers
177 :
178 : /**
179 : * @brief Constructor.
180 : *
181 : * @param address
182 : * The 7-bit MIDI address.
183 : * Depending on the message type, this can be the MIDI note number,
184 : * the number of the MIDI Control Change Controller, etc.
185 : * Must be a number in the range [0, 127].
186 : * @param channel
187 : * The MIDI Channel.
188 : * Use the constants @ref Channel_1 through @ref Channel_16.
189 : * @param cableNumber
190 : * The MIDI USB cable number.
191 : * Use the constants @ref Cable_1 through @ref Cable_16.
192 : */
193 82 : constexpr MIDIAddress(int address, Channel channel = Channel_1,
194 : Cable cableNumber = Cable_1)
195 246 : : addresses {
196 : 1,
197 82 : static_cast<uint8_t>(address),
198 82 : channel.getRaw(),
199 82 : cableNumber.getRaw(),
200 82 : } {} // Deliberate overflow for negative numbers
201 :
202 : /**
203 : * @brief Constructor.
204 : *
205 : * @param address
206 : * The 7-bit MIDI address.
207 : * Depending on the message type, this can be the MIDI note number,
208 : * the number of the MIDI Control Change Controller, etc.
209 : * Must be a number in the range [0, 127].
210 : * @param cableNumber
211 : * The MIDI USB cable number.
212 : * Use the constants @ref Cable_1 through @ref Cable_16.
213 : */
214 : constexpr MIDIAddress(int address, Cable cableNumber)
215 : : addresses {
216 : 1,
217 : static_cast<uint8_t>(address),
218 : 0,
219 : cableNumber.getRaw(),
220 : } {} // Deliberate overflow for negative numbers
221 :
222 : /**
223 : * @brief Constructor.
224 : *
225 : * @param channel
226 : * The MIDI Channel.
227 : * Use the constants @ref Channel_1 through @ref Channel_16.
228 : * @param cableNumber
229 : * The MIDI USB cable number.
230 : * Use the constants @ref Cable_1 through @ref Cable_16.
231 : */
232 : constexpr MIDIAddress(Channel channel, Cable cableNumber = Cable_1)
233 : : addresses {
234 : 1,
235 : 0,
236 : channel.getRaw(),
237 : cableNumber.getRaw(),
238 : } {}
239 :
240 : /**
241 : * @brief Constructor.
242 : *
243 : * @param address
244 : * The MIDI Channel and the MIDI USB cable number.
245 : */
246 14 : constexpr MIDIAddress(MIDIChannelCable address)
247 14 : : addresses(address.addresses) {}
248 :
249 : /// Return an invalid address.
250 : constexpr static MIDIAddress invalid() { return {}; }
251 :
252 : /// @}
253 :
254 : public:
255 : /// @name Adding/subtracting offsets
256 : /// @{
257 :
258 : /// Add a relative offset to this address.
259 : MIDIAddress &operator+=(RelativeMIDIAddress rhs);
260 :
261 : /// Subtract a relative offset from this address.
262 : MIDIAddress &operator-=(RelativeMIDIAddress rhs);
263 :
264 : /// Add a relative offset.
265 : MIDIAddress operator+(RelativeMIDIAddress rhs) const;
266 :
267 : /// Subtract a relative offset.
268 : MIDIAddress operator-(RelativeMIDIAddress rhs) const;
269 :
270 : /// @}
271 :
272 : public:
273 : /// @name Member access
274 : /// @{
275 :
276 : /// Get the address [0, 127].
277 400 : constexpr uint8_t getAddress() const { return addresses.address; }
278 :
279 : /// Get the channel [Channel_1, Channel_16]
280 216 : constexpr Channel getChannel() const { return Channel {addresses.channel}; }
281 : /// Get the channel as an integer [0, 15]
282 32 : constexpr uint8_t getRawChannel() const { return addresses.channel; }
283 :
284 : /// Get the cable number [Cable_1, Cable_16].
285 224 : constexpr Cable getCableNumber() const {
286 224 : return Cable(addresses.cableNumber);
287 : }
288 : /// Get the cable number as an integer [0, 15].
289 12 : constexpr uint8_t getRawCableNumber() const {
290 12 : return addresses.cableNumber;
291 : }
292 : /// Get the channel and cable number.
293 9 : constexpr MIDIChannelCable getChannelCable() const { return {addresses}; }
294 :
295 : /// @}
296 :
297 : public:
298 : /// @name Checks
299 : /// @{
300 :
301 : /// Check for equality: two addresses are equal if and only if they are both
302 : /// valid addresses and the MIDI address, MIDI channel and MIDI USB cable
303 : /// number are equal.
304 : /// @note Invalid addresses are not equal nor inequal.
305 32 : constexpr bool operator==(MIDIAddress rhs) const {
306 63 : return this->addresses.valid && rhs.addresses.valid &&
307 31 : this->addresses.address == rhs.addresses.address &&
308 92 : this->addresses.channel == rhs.addresses.channel &&
309 61 : this->addresses.cableNumber == rhs.addresses.cableNumber;
310 : }
311 :
312 : /// Check for inequality: two addresses are not equal if and only if they
313 : /// are both valid addresses and have a MIDI address, MIDI channel or MIDI
314 : /// USB cable number that differs.
315 : /// @note Invalid addresses are not equal nor inequal.
316 8 : constexpr bool operator!=(MIDIAddress rhs) const {
317 15 : return this->addresses.valid && rhs.addresses.valid &&
318 7 : !(this->addresses.address == rhs.addresses.address &&
319 5 : this->addresses.channel == rhs.addresses.channel &&
320 11 : this->addresses.cableNumber == rhs.addresses.cableNumber);
321 : }
322 :
323 : /// Check if the MIDI address is valid.
324 247 : constexpr bool isValid() const { return addresses.valid; }
325 :
326 : /// Check if the MIDI address is valid.
327 : /// @see isValid
328 134 : constexpr explicit operator bool() const { return isValid(); }
329 :
330 : /// @}
331 :
332 : public:
333 : /// @name Base functions for address pattern matching.
334 : /// @{
335 :
336 : /// Check if two addresses match (are equal).
337 26 : static bool matchSingle(MIDIAddress toMatch, MIDIAddress base) {
338 26 : return base == toMatch;
339 : }
340 :
341 : /// Check if an address falls within a range of MIDI addresses, starting
342 : /// with address `base`, with a given length.
343 : /// @retval true
344 : /// `toMatch` and `base` are both valid addresses,
345 : /// the MIDI address is within the given range, and the MIDI Channel
346 : /// and MIDI USB Cable Number of `toMatch` and `base` are the same.
347 : /// @retval false
348 : /// Otherwise.
349 : static bool matchAddressInRange(MIDIAddress toMatch, MIDIAddress base,
350 : uint8_t length);
351 :
352 : /// @}
353 :
354 : private:
355 : RawMIDIAddress addresses;
356 : };
357 :
358 : END_CS_NAMESPACE
|