OpenShot Audio Library | OpenShotAudio  0.6.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 
23 namespace 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 
40 std::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 
51 void MidiRPNDetector::reset() noexcept
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 //==============================================================================
63 std::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 
82 void MidiRPNDetector::ChannelState::resetValue() noexcept
83 {
84  valueMSB = 0xff;
85  valueLSB = 0xff;
86 }
87 
88 //==============================================================================
89 std::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 
161 class MidiRPNDetectorTests final : public UnitTest
162 {
163 public:
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 
364 static MidiRPNDetectorTests MidiRPNDetectorUnitTests;
365 
366 //==============================================================================
367 class MidiRPNGeneratorTests final : public UnitTest
368 {
369 public:
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 
394 private:
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 
431 static 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)