OpenShot Library | libopenshot-audio 0.2.0
juce_MidiRPN.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
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
29
33
37 MidiRPNMessage& result) noexcept
38{
39 jassert (midiChannel >= 1 && midiChannel <= 16);
40 jassert (controllerNumber >= 0 && controllerNumber < 128);
41 jassert (controllerValue >= 0 && controllerValue < 128);
42
43 return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result);
44}
45
47{
48 for (int i = 0; i < 16; ++i)
49 {
50 states[i].parameterMSB = 0xff;
51 states[i].parameterLSB = 0xff;
52 states[i].resetValue();
53 states[i].isNRPN = false;
54 }
55}
56
57//==============================================================================
58MidiRPNDetector::ChannelState::ChannelState() noexcept
59 : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false)
60{
61}
62
63bool MidiRPNDetector::ChannelState::handleController (int channel,
64 int controllerNumber,
65 int value,
66 MidiRPNMessage& result) noexcept
67{
68 switch (controllerNumber)
69 {
70 case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break;
71 case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break;
72
73 case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break;
74 case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break;
75
76 case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result);
77 case 0x26: valueLSB = uint8 (value); break;
78
79 default: break;
80 }
81
82 return false;
83}
84
85void MidiRPNDetector::ChannelState::resetValue() noexcept
86{
87 valueMSB = 0xff;
88 valueLSB = 0xff;
89}
90
91//==============================================================================
92bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept
93{
94 if (parameterMSB < 0x80 && parameterLSB < 0x80)
95 {
96 if (valueMSB < 0x80)
97 {
98 result.channel = channel;
99 result.parameterNumber = (parameterMSB << 7) + parameterLSB;
100 result.isNRPN = isNRPN;
101
102 if (valueLSB < 0x80)
103 {
104 result.value = (valueMSB << 7) + valueLSB;
105 result.is14BitValue = true;
106 }
107 else
108 {
109 result.value = valueMSB;
110 result.is14BitValue = false;
111 }
112
113 return true;
114 }
115 }
116
117 return false;
118}
119
120//==============================================================================
122{
123 return generate (message.channel,
124 message.parameterNumber,
125 message.value,
126 message.isNRPN,
127 message.is14BitValue);
128}
129
131 int parameterNumber,
132 int value,
133 bool isNRPN,
134 bool use14BitValue)
135{
136 jassert (midiChannel > 0 && midiChannel <= 16);
137 jassert (parameterNumber >= 0 && parameterNumber < 16384);
138 jassert (value >= 0 && value < (use14BitValue ? 16384 : 128));
139
140 uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f);
141 uint8 parameterMSB = uint8 (parameterNumber >> 7);
142
143 uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00;
144 uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value);
145
146 uint8 channelByte = uint8 (0xb0 + midiChannel - 1);
147
148 MidiBuffer buffer;
149
150 buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0);
151 buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0);
152
153 // sending the value LSB is optional, but must come before sending the value MSB:
154 if (use14BitValue)
155 buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0);
156
157 buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0);
158
159 return buffer;
160}
161
162//==============================================================================
163//==============================================================================
164#if JUCE_UNIT_TESTS
165
166class MidiRPNDetectorTests : public UnitTest
167{
168public:
169 MidiRPNDetectorTests() : UnitTest ("MidiRPNDetector class", "MIDI/MPE") {}
170
171 void runTest() override
172 {
173 beginTest ("7-bit RPN");
174 {
175 MidiRPNDetector detector;
176 MidiRPNMessage rpn;
177 expect (! detector.parseControllerMessage (2, 101, 0, rpn));
178 expect (! detector.parseControllerMessage (2, 100, 7, rpn));
179 expect (detector.parseControllerMessage (2, 6, 42, rpn));
180
181 expectEquals (rpn.channel, 2);
182 expectEquals (rpn.parameterNumber, 7);
183 expectEquals (rpn.value, 42);
184 expect (! rpn.isNRPN);
185 expect (! rpn.is14BitValue);
186 }
187
188 beginTest ("14-bit RPN");
189 {
190 MidiRPNDetector detector;
191 MidiRPNMessage rpn;
192 expect (! detector.parseControllerMessage (1, 100, 44, rpn));
193 expect (! detector.parseControllerMessage (1, 101, 2, rpn));
194 expect (! detector.parseControllerMessage (1, 38, 94, rpn));
195 expect (detector.parseControllerMessage (1, 6, 1, rpn));
196
197 expectEquals (rpn.channel, 1);
198 expectEquals (rpn.parameterNumber, 300);
199 expectEquals (rpn.value, 222);
200 expect (! rpn.isNRPN);
201 expect (rpn.is14BitValue);
202 }
203
204 beginTest ("RPNs on multiple channels simultaneously");
205 {
206 MidiRPNDetector detector;
207 MidiRPNMessage rpn;
208 expect (! detector.parseControllerMessage (1, 100, 44, rpn));
209 expect (! detector.parseControllerMessage (2, 101, 0, rpn));
210 expect (! detector.parseControllerMessage (1, 101, 2, rpn));
211 expect (! detector.parseControllerMessage (2, 100, 7, rpn));
212 expect (! detector.parseControllerMessage (1, 38, 94, rpn));
213 expect (detector.parseControllerMessage (2, 6, 42, rpn));
214
215 expectEquals (rpn.channel, 2);
216 expectEquals (rpn.parameterNumber, 7);
217 expectEquals (rpn.value, 42);
218 expect (! rpn.isNRPN);
219 expect (! rpn.is14BitValue);
220
221 expect (detector.parseControllerMessage (1, 6, 1, rpn));
222
223 expectEquals (rpn.channel, 1);
224 expectEquals (rpn.parameterNumber, 300);
225 expectEquals (rpn.value, 222);
226 expect (! rpn.isNRPN);
227 expect (rpn.is14BitValue);
228 }
229
230 beginTest ("14-bit RPN with value within 7-bit range");
231 {
232 MidiRPNDetector detector;
233 MidiRPNMessage rpn;
234 expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
235 expect (! detector.parseControllerMessage (16, 101, 0, rpn));
236 expect (! detector.parseControllerMessage (16, 38, 3, rpn));
237 expect (detector.parseControllerMessage (16, 6, 0, rpn));
238
239 expectEquals (rpn.channel, 16);
240 expectEquals (rpn.parameterNumber, 0);
241 expectEquals (rpn.value, 3);
242 expect (! rpn.isNRPN);
243 expect (rpn.is14BitValue);
244 }
245
246 beginTest ("invalid RPN (wrong order)");
247 {
248 MidiRPNDetector detector;
249 MidiRPNMessage rpn;
250 expect (! detector.parseControllerMessage (2, 6, 42, rpn));
251 expect (! detector.parseControllerMessage (2, 101, 0, rpn));
252 expect (! detector.parseControllerMessage (2, 100, 7, rpn));
253 }
254
255 beginTest ("14-bit RPN interspersed with unrelated CC messages");
256 {
257 MidiRPNDetector detector;
258 MidiRPNMessage rpn;
259 expect (! detector.parseControllerMessage (16, 3, 80, rpn));
260 expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
261 expect (! detector.parseControllerMessage (16, 4, 81, rpn));
262 expect (! detector.parseControllerMessage (16, 101, 0, rpn));
263 expect (! detector.parseControllerMessage (16, 5, 82, rpn));
264 expect (! detector.parseControllerMessage (16, 5, 83, rpn));
265 expect (! detector.parseControllerMessage (16, 38, 3, rpn));
266 expect (! detector.parseControllerMessage (16, 4, 84, rpn));
267 expect (! detector.parseControllerMessage (16, 3, 85, rpn));
268 expect (detector.parseControllerMessage (16, 6, 0, rpn));
269
270 expectEquals (rpn.channel, 16);
271 expectEquals (rpn.parameterNumber, 0);
272 expectEquals (rpn.value, 3);
273 expect (! rpn.isNRPN);
274 expect (rpn.is14BitValue);
275 }
276
277 beginTest ("14-bit NRPN");
278 {
279 MidiRPNDetector detector;
280 MidiRPNMessage rpn;
281 expect (! detector.parseControllerMessage (1, 98, 44, rpn));
282 expect (! detector.parseControllerMessage (1, 99 , 2, rpn));
283 expect (! detector.parseControllerMessage (1, 38, 94, rpn));
284 expect (detector.parseControllerMessage (1, 6, 1, rpn));
285
286 expectEquals (rpn.channel, 1);
287 expectEquals (rpn.parameterNumber, 300);
288 expectEquals (rpn.value, 222);
289 expect (rpn.isNRPN);
290 expect (rpn.is14BitValue);
291 }
292
293 beginTest ("reset");
294 {
295 MidiRPNDetector detector;
296 MidiRPNMessage rpn;
297 expect (! detector.parseControllerMessage (2, 101, 0, rpn));
298 detector.reset();
299 expect (! detector.parseControllerMessage (2, 100, 7, rpn));
300 expect (! detector.parseControllerMessage (2, 6, 42, rpn));
301 }
302 }
303};
304
305static MidiRPNDetectorTests MidiRPNDetectorUnitTests;
306
307//==============================================================================
308class MidiRPNGeneratorTests : public UnitTest
309{
310public:
311 MidiRPNGeneratorTests() : UnitTest ("MidiRPNGenerator class", "MIDI/MPE") {}
312
313 void runTest() override
314 {
315 beginTest ("generating RPN/NRPN");
316 {
317 {
318 MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true);
319 expectContainsRPN (buffer, 1, 23, 1337, true, true);
320 }
321 {
322 MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false);
323 expectContainsRPN (buffer, 16, 101, 34, false, false);
324 }
325 {
326 MidiRPNMessage message = { 16, 101, 34, false, false };
327 MidiBuffer buffer = MidiRPNGenerator::generate (message);
328 expectContainsRPN (buffer, message);
329 }
330 }
331 }
332
333private:
334 //==============================================================================
335 void expectContainsRPN (const MidiBuffer& midiBuffer,
336 int channel,
337 int parameterNumber,
338 int value,
339 bool isNRPN,
340 bool is14BitValue)
341 {
342 MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue };
344 }
345
346 //==============================================================================
347 void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected)
348 {
349 MidiBuffer::Iterator iter (midiBuffer);
350 MidiMessage midiMessage;
351 MidiRPNMessage result = MidiRPNMessage();
352 MidiRPNDetector detector;
353 int samplePosition; // not actually used, so no need to initialise.
354
355 while (iter.getNextEvent (midiMessage, samplePosition))
356 {
357 if (detector.parseControllerMessage (midiMessage.getChannel(),
358 midiMessage.getControllerNumber(),
359 midiMessage.getControllerValue(),
360 result))
361 break;
362 }
363
364 expectEquals (result.channel, expected.channel);
365 expectEquals (result.parameterNumber, expected.parameterNumber);
366 expectEquals (result.value, expected.value);
367 expect (result.isNRPN == expected.isNRPN);
368 expect (result.is14BitValue == expected.is14BitValue);
369 }
370};
371
372static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests;
373
374#endif // JUCE_UNIT_TESTS
375
376} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
Array()=default
Creates an empty array.
Holds a sequence of time-stamped midi events.
void addEvent(const MidiMessage &midiMessage, int sampleNumber)
Adds an event to the buffer.
Encapsulates a MIDI message.
MidiRPNDetector() noexcept
Constructor.
bool parseControllerMessage(int midiChannel, int controllerNumber, int controllerValue, MidiRPNMessage &result) noexcept
Takes the next in a stream of incoming MIDI CC messages and returns true if it forms the last of a se...
void reset() noexcept
Resets the RPN detector's internal state, so that it forgets about previously received MIDI CC messag...
~MidiRPNDetector() noexcept
Destructor.
static MidiBuffer generate(MidiRPNMessage message)
Generates a MIDI sequence representing the given RPN or NRPN message.
This is a base class for classes that perform a unit test.
bool isNRPN
True if this message is an NRPN; false if it is an RPN.
bool is14BitValue
True if the value uses 14-bit resolution (LSB + MSB); false if the value is 7-bit (MSB only).
int channel
Midi channel of the message, in the range 1 to 16.
int parameterNumber
The 14-bit parameter index, in the range 0 to 16383 (0x3fff).
int value
The parameter value, in the range 0 to 16383 (0x3fff).
Represents a MIDI RPN (registered parameter number) or NRPN (non-registered parameter number) message...