OpenShot Audio Library | OpenShotAudio 0.4.0
juce_MidiRPN.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
27 int controllerNumber,
28 int controllerValue,
29 MidiRPNMessage& result) noexcept
30{
31 auto parsed = tryParse (midiChannel, controllerNumber, controllerValue);
32
33 if (! parsed.has_value())
34 return false;
35
36 result = *parsed;
37 return true;
38}
39
40std::optional<MidiRPNMessage> MidiRPNDetector::tryParse (int midiChannel,
41 int controllerNumber,
42 int controllerValue)
43{
44 jassert (midiChannel > 0 && midiChannel <= 16);
45 jassert (controllerNumber >= 0 && controllerNumber < 128);
46 jassert (controllerValue >= 0 && controllerValue < 128);
47
48 return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue);
49}
50
52{
53 for (auto& state : states)
54 {
55 state.parameterMSB = 0xff;
56 state.parameterLSB = 0xff;
57 state.resetValue();
58 state.isNRPN = false;
59 }
60}
61
62//==============================================================================
63std::optional<MidiRPNMessage> MidiRPNDetector::ChannelState::handleController (int channel,
64 int controllerNumber,
65 int value) noexcept
66{
67 switch (controllerNumber)
68 {
69 case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break;
70 case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break;
71
72 case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break;
73 case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break;
74
75 case 0x06: valueMSB = uint8 (value); valueLSB = 0xff; return sendIfReady (channel);
76 case 0x26: valueLSB = uint8 (value); return sendIfReady (channel);
77 }
78
79 return {};
80}
81
82void MidiRPNDetector::ChannelState::resetValue() noexcept
83{
84 valueMSB = 0xff;
85 valueLSB = 0xff;
86}
87
88//==============================================================================
89std::optional<MidiRPNMessage> MidiRPNDetector::ChannelState::sendIfReady (int channel) noexcept
90{
91 if (parameterMSB >= 0x80 || parameterLSB >= 0x80 || valueMSB >= 0x80)
92 return {};
93
94 MidiRPNMessage result{};
95 result.channel = channel;
96 result.parameterNumber = (parameterMSB << 7) + parameterLSB;
97 result.isNRPN = isNRPN;
98
99 if (valueLSB < 0x80)
100 {
101 result.value = (valueMSB << 7) + valueLSB;
102 result.is14BitValue = true;
103 }
104 else
105 {
106 result.value = valueMSB;
107 result.is14BitValue = false;
108 }
109
110 return result;
111}
112
113//==============================================================================
115{
116 return generate (message.channel,
117 message.parameterNumber,
118 message.value,
119 message.isNRPN,
120 message.is14BitValue);
121}
122
124 int parameterNumber,
125 int value,
126 bool isNRPN,
127 bool use14BitValue)
128{
129 jassert (midiChannel > 0 && midiChannel <= 16);
130 jassert (parameterNumber >= 0 && parameterNumber < 16384);
131 jassert (value >= 0 && value < (use14BitValue ? 16384 : 128));
132
133 auto parameterLSB = uint8 (parameterNumber & 0x0000007f);
134 auto parameterMSB = uint8 (parameterNumber >> 7);
135
136 uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00;
137 uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value);
138
139 auto channelByte = uint8 (0xb0 + midiChannel - 1);
140
141 MidiBuffer buffer;
142
143 buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0);
144 buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0);
145
146 buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0);
147
148 // According to the MIDI spec, whenever a MSB is received, the corresponding LSB will
149 // be reset. Therefore, the LSB should be sent after the MSB.
150 if (use14BitValue)
151 buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0);
152
153 return buffer;
154}
155
156
157//==============================================================================
158//==============================================================================
159#if JUCE_UNIT_TESTS
160
161class MidiRPNDetectorTests final : public UnitTest
162{
163public:
164 MidiRPNDetectorTests()
165 : UnitTest ("MidiRPNDetector class", UnitTestCategories::midi)
166 {}
167
168 void runTest() override
169 {
170 // From the MIDI 1.0 spec:
171 // If 128 steps of resolution is sufficient the second byte (LSB) of the data value can be
172 // omitted. If both the MSB and LSB are sent initially, a subsequent fine adjustment only
173 // requires the sending of the LSB. The MSB does not have to be retransmitted. If a
174 // subsequent major adjustment is necessary the MSB must be transmitted again. When an MSB
175 // is received, the receiver should set its concept of the LSB to zero.
176
177 beginTest ("Individual MSB is parsed as 7-bit");
178 {
179 MidiRPNDetector detector;
180 expect (! detector.tryParse (2, 101, 0));
181 expect (! detector.tryParse (2, 100, 7));
182
183 auto parsed = detector.tryParse (2, 6, 42);
184 expect (parsed.has_value());
185
186 expectEquals (parsed->channel, 2);
187 expectEquals (parsed->parameterNumber, 7);
188 expectEquals (parsed->value, 42);
189 expect (! parsed->isNRPN);
190 expect (! parsed->is14BitValue);
191 }
192
193 beginTest ("LSB without preceding MSB is ignored");
194 {
195 MidiRPNDetector detector;
196 expect (! detector.tryParse (2, 101, 0));
197 expect (! detector.tryParse (2, 100, 7));
198 expect (! detector.tryParse (2, 38, 42));
199 }
200
201 beginTest ("LSB following MSB is parsed as 14-bit");
202 {
203 MidiRPNDetector detector;
204 expect (! detector.tryParse (1, 101, 2));
205 expect (! detector.tryParse (1, 100, 44));
206
207 expect (detector.tryParse (1, 6, 1).has_value());
208
209 auto lsbParsed = detector.tryParse (1, 38, 94);
210 expect (lsbParsed.has_value());
211
212 expectEquals (lsbParsed->channel, 1);
213 expectEquals (lsbParsed->parameterNumber, 300);
214 expectEquals (lsbParsed->value, 222);
215 expect (! lsbParsed->isNRPN);
216 expect (lsbParsed->is14BitValue);
217 }
218
219 beginTest ("Multiple LSB following MSB re-use the MSB");
220 {
221 MidiRPNDetector detector;
222 expect (! detector.tryParse (1, 101, 2));
223 expect (! detector.tryParse (1, 100, 43));
224
225 expect (detector.tryParse (1, 6, 1).has_value());
226
227 expect (detector.tryParse (1, 38, 94).has_value());
228 expect (detector.tryParse (1, 38, 95).has_value());
229 expect (detector.tryParse (1, 38, 96).has_value());
230
231 auto lsbParsed = detector.tryParse (1, 38, 97);
232 expect (lsbParsed.has_value());
233
234 expectEquals (lsbParsed->channel, 1);
235 expectEquals (lsbParsed->parameterNumber, 299);
236 expectEquals (lsbParsed->value, 225);
237 expect (! lsbParsed->isNRPN);
238 expect (lsbParsed->is14BitValue);
239 }
240
241 beginTest ("Sending a new MSB resets the LSB");
242 {
243 MidiRPNDetector detector;
244 expect (! detector.tryParse (1, 101, 3));
245 expect (! detector.tryParse (1, 100, 43));
246
247 expect (detector.tryParse (1, 6, 1).has_value());
248 expect (detector.tryParse (1, 38, 94).has_value());
249
250 auto newMsb = detector.tryParse (1, 6, 2);
251 expect (newMsb.has_value());
252
253 expectEquals (newMsb->channel, 1);
254 expectEquals (newMsb->parameterNumber, 427);
255 expectEquals (newMsb->value, 2);
256 expect (! newMsb->isNRPN);
257 expect (! newMsb->is14BitValue);
258 }
259
260 beginTest ("RPNs on multiple channels simultaneously");
261 {
262 MidiRPNDetector detector;
263 expect (! detector.tryParse (1, 100, 44));
264 expect (! detector.tryParse (2, 101, 0));
265 expect (! detector.tryParse (1, 101, 2));
266 expect (! detector.tryParse (2, 100, 7));
267 expect (detector.tryParse (1, 6, 1).has_value());
268
269 auto channelTwo = detector.tryParse (2, 6, 42);
270 expect (channelTwo.has_value());
271
272 expectEquals (channelTwo->channel, 2);
273 expectEquals (channelTwo->parameterNumber, 7);
274 expectEquals (channelTwo->value, 42);
275 expect (! channelTwo->isNRPN);
276 expect (! channelTwo->is14BitValue);
277
278 auto channelOne = detector.tryParse (1, 38, 94);
279 expect (channelOne.has_value());
280
281 expectEquals (channelOne->channel, 1);
282 expectEquals (channelOne->parameterNumber, 300);
283 expectEquals (channelOne->value, 222);
284 expect (! channelOne->isNRPN);
285 expect (channelOne->is14BitValue);
286 }
287
288 beginTest ("14-bit RPN with value within 7-bit range");
289 {
290 MidiRPNDetector detector;
291 expect (! detector.tryParse (16, 100, 0));
292 expect (! detector.tryParse (16, 101, 0));
293 expect (detector.tryParse (16, 6, 0).has_value());
294
295 auto parsed = detector.tryParse (16, 38, 3);
296 expect (parsed.has_value());
297
298 expectEquals (parsed->channel, 16);
299 expectEquals (parsed->parameterNumber, 0);
300 expectEquals (parsed->value, 3);
301 expect (! parsed->isNRPN);
302 expect (parsed->is14BitValue);
303 }
304
305 beginTest ("invalid RPN (wrong order)");
306 {
307 MidiRPNDetector detector;
308 expect (! detector.tryParse (2, 6, 42));
309 expect (! detector.tryParse (2, 101, 0));
310 expect (! detector.tryParse (2, 100, 7));
311 }
312
313 beginTest ("14-bit RPN interspersed with unrelated CC messages");
314 {
315 MidiRPNDetector detector;
316 expect (! detector.tryParse (16, 3, 80));
317 expect (! detector.tryParse (16, 100, 0));
318 expect (! detector.tryParse (16, 4, 81));
319 expect (! detector.tryParse (16, 101, 0));
320 expect (! detector.tryParse (16, 5, 82));
321 expect (! detector.tryParse (16, 5, 83));
322 expect (detector.tryParse (16, 6, 0).has_value());
323 expect (! detector.tryParse (16, 4, 84).has_value());
324 expect (! detector.tryParse (16, 3, 85).has_value());
325
326 auto parsed = detector.tryParse (16, 38, 3);
327 expect (parsed.has_value());
328
329 expectEquals (parsed->channel, 16);
330 expectEquals (parsed->parameterNumber, 0);
331 expectEquals (parsed->value, 3);
332 expect (! parsed->isNRPN);
333 expect (parsed->is14BitValue);
334 }
335
336 beginTest ("14-bit NRPN");
337 {
338 MidiRPNDetector detector;
339 expect (! detector.tryParse (1, 98, 44));
340 expect (! detector.tryParse (1, 99 , 2));
341 expect (detector.tryParse (1, 6, 1).has_value());
342
343 auto parsed = detector.tryParse (1, 38, 94);
344 expect (parsed.has_value());
345
346 expectEquals (parsed->channel, 1);
347 expectEquals (parsed->parameterNumber, 300);
348 expectEquals (parsed->value, 222);
349 expect (parsed->isNRPN);
350 expect (parsed->is14BitValue);
351 }
352
353 beginTest ("reset");
354 {
355 MidiRPNDetector detector;
356 expect (! detector.tryParse (2, 101, 0));
357 detector.reset();
358 expect (! detector.tryParse (2, 100, 7));
359 expect (! detector.tryParse (2, 6, 42));
360 }
361 }
362};
363
364static MidiRPNDetectorTests MidiRPNDetectorUnitTests;
365
366//==============================================================================
367class MidiRPNGeneratorTests final : public UnitTest
368{
369public:
370 MidiRPNGeneratorTests()
371 : UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi)
372 {}
373
374 void runTest() override
375 {
376 beginTest ("generating RPN/NRPN");
377 {
378 {
379 MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true);
380 expectContainsRPN (buffer, 1, 23, 1337, true, true);
381 }
382 {
383 MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false);
384 expectContainsRPN (buffer, 16, 101, 34, false, false);
385 }
386 {
387 MidiRPNMessage message = { 16, 101, 34, false, false };
388 MidiBuffer buffer = MidiRPNGenerator::generate (message);
389 expectContainsRPN (buffer, message);
390 }
391 }
392 }
393
394private:
395 //==============================================================================
396 void expectContainsRPN (const MidiBuffer& midiBuffer,
397 int channel,
398 int parameterNumber,
399 int value,
400 bool isNRPN,
401 bool is14BitValue)
402 {
403 MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue };
404 expectContainsRPN (midiBuffer, expected);
405 }
406
407 //==============================================================================
408 void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected)
409 {
410 std::optional<MidiRPNMessage> result;
411 MidiRPNDetector detector;
412
413 for (const auto metadata : midiBuffer)
414 {
415 const auto midiMessage = metadata.getMessage();
416
417 result = detector.tryParse (midiMessage.getChannel(),
418 midiMessage.getControllerNumber(),
419 midiMessage.getControllerValue());
420 }
421
422 expect (result.has_value());
423 expectEquals (result->channel, expected.channel);
424 expectEquals (result->parameterNumber, expected.parameterNumber);
425 expectEquals (result->value, expected.value);
426 expect (result->isNRPN == expected.isNRPN);
427 expect (result->is14BitValue == expected.is14BitValue);
428 }
429};
430
431static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests;
432
433#endif
434
435} // namespace juce
bool addEvent(const MidiMessage &midiMessage, int sampleNumber)
bool parseControllerMessage(int midiChannel, int controllerNumber, int controllerValue, MidiRPNMessage &result) noexcept
void reset() noexcept
std::optional< MidiRPNMessage > tryParse(int midiChannel, int controllerNumber, int controllerValue)
static MidiBuffer generate(MidiRPNMessage message)