1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - 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 
23 namespace juce
24 {
25 
MidiRPNDetector()26 MidiRPNDetector::MidiRPNDetector() noexcept
27 {
28 }
29 
~MidiRPNDetector()30 MidiRPNDetector::~MidiRPNDetector() noexcept
31 {
32 }
33 
parseControllerMessage(int midiChannel,int controllerNumber,int controllerValue,MidiRPNMessage & result)34 bool MidiRPNDetector::parseControllerMessage (int midiChannel,
35                                               int controllerNumber,
36                                               int controllerValue,
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 
reset()46 void MidiRPNDetector::reset() noexcept
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 //==============================================================================
ChannelState()58 MidiRPNDetector::ChannelState::ChannelState() noexcept
59     : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false)
60 {
61 }
62 
handleController(int channel,int controllerNumber,int value,MidiRPNMessage & result)63 bool 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 
resetValue()85 void MidiRPNDetector::ChannelState::resetValue() noexcept
86 {
87     valueMSB = 0xff;
88     valueLSB = 0xff;
89 }
90 
91 //==============================================================================
sendIfReady(int channel,MidiRPNMessage & result)92 bool 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 //==============================================================================
generate(MidiRPNMessage message)121 MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message)
122 {
123     return generate (message.channel,
124                      message.parameterNumber,
125                      message.value,
126                      message.isNRPN,
127                      message.is14BitValue);
128 }
129 
generate(int midiChannel,int parameterNumber,int value,bool isNRPN,bool use14BitValue)130 MidiBuffer MidiRPNGenerator::generate (int midiChannel,
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 //==============================================================================
165 #if JUCE_UNIT_TESTS
166 
167 class MidiRPNDetectorTests   : public UnitTest
168 {
169 public:
MidiRPNDetectorTests()170     MidiRPNDetectorTests()
171         : UnitTest ("MidiRPNDetector class", UnitTestCategories::midi)
172     {}
173 
runTest()174     void runTest() override
175     {
176         beginTest ("7-bit RPN");
177         {
178             MidiRPNDetector detector;
179             MidiRPNMessage rpn;
180             expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
181             expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
182             expect (detector.parseControllerMessage   (2, 6,   42, rpn));
183 
184             expectEquals (rpn.channel, 2);
185             expectEquals (rpn.parameterNumber, 7);
186             expectEquals (rpn.value, 42);
187             expect (! rpn.isNRPN);
188             expect (! rpn.is14BitValue);
189         }
190 
191         beginTest ("14-bit RPN");
192         {
193             MidiRPNDetector detector;
194             MidiRPNMessage rpn;
195             expect (! detector.parseControllerMessage (1, 100, 44, rpn));
196             expect (! detector.parseControllerMessage (1, 101, 2,  rpn));
197             expect (! detector.parseControllerMessage (1, 38,  94, rpn));
198             expect (detector.parseControllerMessage   (1, 6,   1,  rpn));
199 
200             expectEquals (rpn.channel, 1);
201             expectEquals (rpn.parameterNumber, 300);
202             expectEquals (rpn.value, 222);
203             expect (! rpn.isNRPN);
204             expect (rpn.is14BitValue);
205         }
206 
207         beginTest ("RPNs on multiple channels simultaneously");
208         {
209             MidiRPNDetector detector;
210             MidiRPNMessage rpn;
211             expect (! detector.parseControllerMessage (1, 100, 44, rpn));
212             expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
213             expect (! detector.parseControllerMessage (1, 101, 2,  rpn));
214             expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
215             expect (! detector.parseControllerMessage (1, 38,  94, rpn));
216             expect (detector.parseControllerMessage   (2, 6,   42, rpn));
217 
218             expectEquals (rpn.channel, 2);
219             expectEquals (rpn.parameterNumber, 7);
220             expectEquals (rpn.value, 42);
221             expect (! rpn.isNRPN);
222             expect (! rpn.is14BitValue);
223 
224             expect (detector.parseControllerMessage   (1, 6,   1,  rpn));
225 
226             expectEquals (rpn.channel, 1);
227             expectEquals (rpn.parameterNumber, 300);
228             expectEquals (rpn.value, 222);
229             expect (! rpn.isNRPN);
230             expect (rpn.is14BitValue);
231         }
232 
233         beginTest ("14-bit RPN with value within 7-bit range");
234         {
235             MidiRPNDetector detector;
236             MidiRPNMessage rpn;
237             expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
238             expect (! detector.parseControllerMessage (16, 101, 0,  rpn));
239             expect (! detector.parseControllerMessage (16, 38,  3,  rpn));
240             expect (detector.parseControllerMessage   (16, 6,   0,  rpn));
241 
242             expectEquals (rpn.channel, 16);
243             expectEquals (rpn.parameterNumber, 0);
244             expectEquals (rpn.value, 3);
245             expect (! rpn.isNRPN);
246             expect (rpn.is14BitValue);
247         }
248 
249         beginTest ("invalid RPN (wrong order)");
250         {
251             MidiRPNDetector detector;
252             MidiRPNMessage rpn;
253             expect (! detector.parseControllerMessage (2, 6,   42, rpn));
254             expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
255             expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
256         }
257 
258         beginTest ("14-bit RPN interspersed with unrelated CC messages");
259         {
260             MidiRPNDetector detector;
261             MidiRPNMessage rpn;
262             expect (! detector.parseControllerMessage (16, 3,   80, rpn));
263             expect (! detector.parseControllerMessage (16, 100, 0 , rpn));
264             expect (! detector.parseControllerMessage (16, 4,   81, rpn));
265             expect (! detector.parseControllerMessage (16, 101, 0,  rpn));
266             expect (! detector.parseControllerMessage (16, 5,   82, rpn));
267             expect (! detector.parseControllerMessage (16, 5,   83, rpn));
268             expect (! detector.parseControllerMessage (16, 38,  3,  rpn));
269             expect (! detector.parseControllerMessage (16, 4,   84, rpn));
270             expect (! detector.parseControllerMessage (16, 3,   85, rpn));
271             expect (detector.parseControllerMessage   (16, 6,   0,  rpn));
272 
273             expectEquals (rpn.channel, 16);
274             expectEquals (rpn.parameterNumber, 0);
275             expectEquals (rpn.value, 3);
276             expect (! rpn.isNRPN);
277             expect (rpn.is14BitValue);
278         }
279 
280         beginTest ("14-bit NRPN");
281         {
282             MidiRPNDetector detector;
283             MidiRPNMessage rpn;
284             expect (! detector.parseControllerMessage (1, 98,  44, rpn));
285             expect (! detector.parseControllerMessage (1, 99 , 2,  rpn));
286             expect (! detector.parseControllerMessage (1, 38,  94, rpn));
287             expect (detector.parseControllerMessage   (1, 6,   1,  rpn));
288 
289             expectEquals (rpn.channel, 1);
290             expectEquals (rpn.parameterNumber, 300);
291             expectEquals (rpn.value, 222);
292             expect (rpn.isNRPN);
293             expect (rpn.is14BitValue);
294         }
295 
296         beginTest ("reset");
297         {
298             MidiRPNDetector detector;
299             MidiRPNMessage rpn;
300             expect (! detector.parseControllerMessage (2, 101, 0,  rpn));
301             detector.reset();
302             expect (! detector.parseControllerMessage (2, 100, 7,  rpn));
303             expect (! detector.parseControllerMessage (2, 6,   42, rpn));
304         }
305     }
306 };
307 
308 static MidiRPNDetectorTests MidiRPNDetectorUnitTests;
309 
310 //==============================================================================
311 class MidiRPNGeneratorTests   : public UnitTest
312 {
313 public:
MidiRPNGeneratorTests()314     MidiRPNGeneratorTests()
315         : UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi)
316     {}
317 
runTest()318     void runTest() override
319     {
320         beginTest ("generating RPN/NRPN");
321         {
322             {
323                 MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true);
324                 expectContainsRPN (buffer, 1, 23, 1337, true, true);
325             }
326             {
327                 MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false);
328                 expectContainsRPN (buffer, 16, 101, 34, false, false);
329             }
330             {
331                 MidiRPNMessage message = { 16, 101, 34, false, false };
332                 MidiBuffer buffer = MidiRPNGenerator::generate (message);
333                 expectContainsRPN (buffer, message);
334             }
335         }
336     }
337 
338 private:
339     //==============================================================================
expectContainsRPN(const MidiBuffer & midiBuffer,int channel,int parameterNumber,int value,bool isNRPN,bool is14BitValue)340     void expectContainsRPN (const MidiBuffer& midiBuffer,
341                             int channel,
342                             int parameterNumber,
343                             int value,
344                             bool isNRPN,
345                             bool is14BitValue)
346     {
347         MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue };
348         expectContainsRPN (midiBuffer, expected);
349     }
350 
351     //==============================================================================
expectContainsRPN(const MidiBuffer & midiBuffer,MidiRPNMessage expected)352     void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected)
353     {
354         MidiRPNMessage result = MidiRPNMessage();
355         MidiRPNDetector detector;
356 
357         for (const auto metadata : midiBuffer)
358         {
359             const auto midiMessage = metadata.getMessage();
360 
361             if (detector.parseControllerMessage (midiMessage.getChannel(),
362                                                  midiMessage.getControllerNumber(),
363                                                  midiMessage.getControllerValue(),
364                                                  result))
365                 break;
366         }
367 
368         expectEquals (result.channel, expected.channel);
369         expectEquals (result.parameterNumber, expected.parameterNumber);
370         expectEquals (result.value, expected.value);
371         expect (result.isNRPN == expected.isNRPN);
372         expect (result.is14BitValue == expected.is14BitValue);
373     }
374 };
375 
376 static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests;
377 
378 #endif
379 
380 } // namespace juce
381