OpenShot Audio Library | OpenShotAudio  0.6.0
juce_UMP_test.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::universal_midi_packets
24 {
25 
26 constexpr uint8_t operator""_u8 (unsigned long long int i) { return static_cast<uint8_t> (i); }
27 constexpr uint16_t operator""_u16 (unsigned long long int i) { return static_cast<uint16_t> (i); }
28 constexpr uint32_t operator""_u32 (unsigned long long int i) { return static_cast<uint32_t> (i); }
29 constexpr uint64_t operator""_u64 (unsigned long long int i) { return static_cast<uint64_t> (i); }
30 
31 class UniversalMidiPacketTests final : public UnitTest
32 {
33 public:
34  UniversalMidiPacketTests()
35  : UnitTest ("Universal MIDI Packet", UnitTestCategories::midi)
36  {
37  }
38 
39  void runTest() override
40  {
41  auto random = getRandom();
42 
43  beginTest ("Short bytestream midi messages can be round-tripped through the UMP converter");
44  {
45  Midi1ToBytestreamTranslator translator (0);
46 
47  forEachNonSysExTestMessage (random, [&] (const MidiMessage& m)
48  {
49  const auto packets = toMidi1 (m);
50  expect (packets.size() == 1);
51 
52  // Make sure that the message type is correct
53  const auto msgType = Utils::getMessageType (packets.data()[0]);
54  expect (msgType == ((m.getRawData()[0] >> 0x4) == 0xf ? 0x1 : 0x2));
55 
56  translator.dispatch (View {packets.data() },
57  0,
58  [&] (const BytestreamMidiView& roundTripped)
59  {
60  expect (equal (m, roundTripped.getMessage()));
61  });
62  });
63  }
64 
65  beginTest ("Bytestream SysEx converts to universal packets");
66  {
67  {
68  // Zero length message
69  const auto packets = toMidi1 (createRandomSysEx (random, 0));
70  expect (packets.size() == 2);
71 
72  expect (packets.data()[0] == 0x30000000);
73  expect (packets.data()[1] == 0x00000000);
74  }
75 
76  {
77  const auto message = createRandomSysEx (random, 1);
78  const auto packets = toMidi1 (message);
79  expect (packets.size() == 2);
80 
81  const auto* sysEx = message.getSysExData();
82  expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 },
83  std::byte { 0x01 },
84  std::byte { sysEx[0] },
85  std::byte { 0 }));
86  expect (packets.data()[1] == 0x00000000);
87  }
88 
89  {
90  const auto message = createRandomSysEx (random, 6);
91  const auto packets = toMidi1 (message);
92  expect (packets.size() == 2);
93 
94  const auto* sysEx = message.getSysExData();
95  expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x06 }, std::byte { sysEx[0] }, std::byte { sysEx[1] }));
96  expect (packets.data()[1] == Utils::bytesToWord (std::byte { sysEx[2] }, std::byte { sysEx[3] }, std::byte { sysEx[4] }, std::byte { sysEx[5] }));
97  }
98 
99  {
100  const auto message = createRandomSysEx (random, 12);
101  const auto packets = toMidi1 (message);
102  expect (packets.size() == 4);
103 
104  const auto* sysEx = message.getSysExData();
105  expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x16 }, std::byte { sysEx[0] }, std::byte { sysEx[1] }));
106  expect (packets.data()[1] == Utils::bytesToWord (std::byte { sysEx[2] }, std::byte { sysEx[3] }, std::byte { sysEx[4] }, std::byte { sysEx[5] }));
107  expect (packets.data()[2] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x36 }, std::byte { sysEx[6] }, std::byte { sysEx[7] }));
108  expect (packets.data()[3] == Utils::bytesToWord (std::byte { sysEx[8] }, std::byte { sysEx[9] }, std::byte { sysEx[10] }, std::byte { sysEx[11] }));
109  }
110 
111  {
112  const auto message = createRandomSysEx (random, 13);
113  const auto packets = toMidi1 (message);
114  expect (packets.size() == 6);
115 
116  const auto* sysEx = message.getSysExData();
117  expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x16 }, std::byte { sysEx[0] }, std::byte { sysEx[1] }));
118  expect (packets.data()[1] == Utils::bytesToWord (std::byte { sysEx[2] }, std::byte { sysEx[3] }, std::byte { sysEx[4] }, std::byte { sysEx[5] }));
119  expect (packets.data()[2] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x26 }, std::byte { sysEx[6] }, std::byte { sysEx[7] }));
120  expect (packets.data()[3] == Utils::bytesToWord (std::byte { sysEx[8] }, std::byte { sysEx[9] }, std::byte { sysEx[10] }, std::byte { sysEx[11] }));
121  expect (packets.data()[4] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x31 }, std::byte { sysEx[12] }, std::byte { 0 }));
122  expect (packets.data()[5] == 0x00000000);
123  }
124  }
125 
126  ToBytestreamDispatcher converter (0);
127  Packets packets;
128 
129  const auto checkRoundTrip = [&] (const MidiBuffer& expected)
130  {
131  for (const auto meta : expected)
132  Conversion::toMidi1 (ump::BytestreamMidiView (meta), [&] (const auto p) { packets.add (p); });
133 
134  MidiBuffer output;
135  converter.dispatch (packets.data(),
136  packets.data() + packets.size(),
137  0,
138  [&] (const BytestreamMidiView& roundTripped)
139  {
140  output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp));
141  });
142  packets.clear();
143 
144  expect (equal (expected, output));
145  };
146 
147  beginTest ("Long SysEx bytestream midi messages can be round-tripped through the UMP converter");
148  {
149  for (auto length : { 0, 1, 2, 3, 4, 5, 6, 7, 13, 20, 100, 1000 })
150  {
151  MidiBuffer expected;
152  expected.addEvent (createRandomSysEx (random, size_t (length)), 0);
153  checkRoundTrip (expected);
154  }
155  }
156 
157  beginTest ("UMP SysEx7 messages interspersed with utility messages convert to bytestream");
158  {
159  const auto sysEx = createRandomSysEx (random, 100);
160  const auto originalPackets = toMidi1 (sysEx);
161 
162  Packets modifiedPackets;
163 
164  const auto addRandomUtilityUMP = [&]
165  {
166  const auto newPacket = createRandomUtilityUMP (random);
167  modifiedPackets.add (View (newPacket.data()));
168  };
169 
170  for (const auto& packet : originalPackets)
171  {
172  addRandomUtilityUMP();
173  modifiedPackets.add (packet);
174  addRandomUtilityUMP();
175  }
176 
177  MidiBuffer output;
178  converter.dispatch (modifiedPackets.data(),
179  modifiedPackets.data() + modifiedPackets.size(),
180  0,
181  [&] (const BytestreamMidiView& roundTripped)
182  {
183  output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp));
184  });
185 
186  // All Utility messages should have been ignored
187  expect (output.getNumEvents() == 1);
188 
189  for (const auto meta : output)
190  expect (equal (meta.getMessage(), sysEx));
191  }
192 
193  beginTest ("UMP SysEx7 messages interspersed with System Realtime messages convert to bytestream");
194  {
195  const auto sysEx = createRandomSysEx (random, 200);
196  const auto originalPackets = toMidi1 (sysEx);
197 
198  Packets modifiedPackets;
199  MidiBuffer realtimeMessages;
200 
201  const auto addRandomRealtimeUMP = [&]
202  {
203  const auto newPacket = createRandomRealtimeUMP (random);
204  modifiedPackets.add (View (newPacket.data()));
205  realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0);
206  };
207 
208  for (const auto& packet : originalPackets)
209  {
210  addRandomRealtimeUMP();
211  modifiedPackets.add (packet);
212  addRandomRealtimeUMP();
213  }
214 
215  MidiBuffer output;
216  converter.dispatch (modifiedPackets.data(),
217  modifiedPackets.data() + modifiedPackets.size(),
218  0,
219  [&] (const BytestreamMidiView& roundTripped)
220  {
221  output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp));
222  });
223 
224  const auto numOutputs = output.getNumEvents();
225  const auto numInputs = realtimeMessages.getNumEvents();
226  expect (numOutputs == numInputs + 1);
227 
228  if (numOutputs == numInputs + 1)
229  {
230  const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a,
231  const MidiMessageMetadata& b)
232  {
233  return equal (a.getMessage(), b.getMessage());
234  };
235 
236  auto it = output.begin();
237 
238  for (const auto meta : realtimeMessages)
239  {
240  if (! isMetadataEquivalent (*it, meta))
241  {
242  expect (equal ((*it).getMessage(), sysEx));
243  ++it;
244  }
245 
246  expect (isMetadataEquivalent (*it, meta));
247  ++it;
248  }
249  }
250  }
251 
252  beginTest ("UMP SysEx7 messages interspersed with System Realtime and Utility messages convert to bytestream");
253  {
254  const auto sysEx = createRandomSysEx (random, 300);
255  const auto originalPackets = toMidi1 (sysEx);
256 
257  Packets modifiedPackets;
258  MidiBuffer realtimeMessages;
259 
260  const auto addRandomRealtimeUMP = [&]
261  {
262  const auto newPacket = createRandomRealtimeUMP (random);
263  modifiedPackets.add (View (newPacket.data()));
264  realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0);
265  };
266 
267  const auto addRandomUtilityUMP = [&]
268  {
269  const auto newPacket = createRandomUtilityUMP (random);
270  modifiedPackets.add (View (newPacket.data()));
271  };
272 
273  for (const auto& packet : originalPackets)
274  {
275  addRandomRealtimeUMP();
276  addRandomUtilityUMP();
277  modifiedPackets.add (packet);
278  addRandomRealtimeUMP();
279  addRandomUtilityUMP();
280  }
281 
282  MidiBuffer output;
283  converter.dispatch (modifiedPackets.data(),
284  modifiedPackets.data() + modifiedPackets.size(),
285  0,
286  [&] (const BytestreamMidiView& roundTripped)
287  {
288  output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp));
289  });
290 
291  const auto numOutputs = output.getNumEvents();
292  const auto numInputs = realtimeMessages.getNumEvents();
293  expect (numOutputs == numInputs + 1);
294 
295  if (numOutputs == numInputs + 1)
296  {
297  const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a, const MidiMessageMetadata& b)
298  {
299  return equal (a.getMessage(), b.getMessage());
300  };
301 
302  auto it = output.begin();
303 
304  for (const auto meta : realtimeMessages)
305  {
306  if (! isMetadataEquivalent (*it, meta))
307  {
308  expect (equal ((*it).getMessage(), sysEx));
309  ++it;
310  }
311 
312  expect (isMetadataEquivalent (*it, meta));
313  ++it;
314  }
315  }
316  }
317 
318  beginTest ("SysEx messages are terminated by non-Utility, non-Realtime messages");
319  {
320  const auto noteOn = [&]
321  {
322  MidiBuffer b;
323  b.addEvent (MidiMessage::noteOn (1, uint8_t (64), uint8_t (64)), 0);
324  return b;
325  }();
326 
327  const auto noteOnPackets = [&]
328  {
329  Packets p;
330 
331  for (const auto meta : noteOn)
332  Conversion::toMidi1 (ump::BytestreamMidiView (meta), [&] (const auto packet) { p.add (packet); });
333 
334  return p;
335  }();
336 
337  const auto sysEx = createRandomSysEx (random, 300);
338 
339  const auto originalPackets = toMidi1 (sysEx);
340 
341  const auto modifiedPackets = [&]
342  {
343  Packets p;
344 
345  const auto insertionPoint = std::next (originalPackets.begin(), 10);
346  std::for_each (originalPackets.begin(),
347  insertionPoint,
348  [&] (const View& view) { p.add (view); });
349 
350  for (const auto& view : noteOnPackets)
351  p.add (view);
352 
353  std::for_each (insertionPoint,
354  originalPackets.end(),
355  [&] (const View& view) { p.add (view); });
356 
357  return p;
358  }();
359 
360  // modifiedPackets now contains some SysEx packets interrupted by a MIDI 1 noteOn
361 
362  MidiBuffer output;
363 
364  const auto pushToOutput = [&] (const Packets& p)
365  {
366  converter.dispatch (p.data(),
367  p.data() + p.size(),
368  0,
369  [&] (const BytestreamMidiView& roundTripped)
370  {
371  output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp));
372  });
373  };
374 
375  pushToOutput (modifiedPackets);
376 
377  // Interrupted sysEx shouldn't be present
378  expect (equal (output, noteOn));
379 
380  const auto newSysEx = createRandomSysEx (random, 300);
381  const auto newSysExPackets = toMidi1 (newSysEx);
382 
383  // If we push another midi event without interrupting it,
384  // it should get through without being modified,
385  // and it shouldn't be affected by the previous (interrupted) sysex.
386 
387  output.clear();
388  pushToOutput (newSysExPackets);
389 
390  expect (output.getNumEvents() == 1);
391 
392  for (const auto meta : output)
393  expect (equal (meta.getMessage(), newSysEx));
394  }
395 
396  beginTest ("Widening conversions work");
397  {
398  // This is similar to the 'slow' example code from the MIDI 2.0 spec
399  const auto baselineScale = [] (uint32_t srcVal, uint32_t srcBits, uint32_t dstBits)
400  {
401  const auto scaleBits = (uint32_t) (dstBits - srcBits);
402 
403  auto bitShiftedValue = (uint32_t) (srcVal << scaleBits);
404 
405  const auto srcCenter = (uint32_t) (1 << (srcBits - 1));
406 
407  if (srcVal <= srcCenter)
408  return bitShiftedValue;
409 
410  const auto repeatBits = (uint32_t) (srcBits - 1);
411  const auto repeatMask = (uint32_t) ((1 << repeatBits) - 1);
412 
413  auto repeatValue = (uint32_t) (srcVal & repeatMask);
414 
415  if (scaleBits > repeatBits)
416  repeatValue <<= scaleBits - repeatBits;
417  else
418  repeatValue >>= repeatBits - scaleBits;
419 
420  while (repeatValue != 0)
421  {
422  bitShiftedValue |= repeatValue;
423  repeatValue >>= repeatBits;
424  }
425 
426  return bitShiftedValue;
427  };
428 
429  const auto baselineScale7To8 = [&] (uint8_t in)
430  {
431  return baselineScale (in, 7, 8);
432  };
433 
434  const auto baselineScale7To16 = [&] (uint8_t in)
435  {
436  return baselineScale (in, 7, 16);
437  };
438 
439  const auto baselineScale14To16 = [&] (uint16_t in)
440  {
441  return baselineScale (in, 14, 16);
442  };
443 
444  const auto baselineScale7To32 = [&] (uint8_t in)
445  {
446  return baselineScale (in, 7, 32);
447  };
448 
449  const auto baselineScale14To32 = [&] (uint16_t in)
450  {
451  return baselineScale (in, 14, 32);
452  };
453 
454  for (auto i = 0; i != 100; ++i)
455  {
456  const auto rand = (uint8_t) random.nextInt (0x80);
457  expectEquals ((int64_t) Conversion::scaleTo8 (rand),
458  (int64_t) baselineScale7To8 (rand));
459  }
460 
461  expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x00), (int64_t) 0x0000);
462  expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x0a), (int64_t) 0x1400);
463  expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x40), (int64_t) 0x8000);
464  expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x57), (int64_t) 0xaeba);
465  expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x7f), (int64_t) 0xffff);
466 
467  for (auto i = 0; i != 100; ++i)
468  {
469  const auto rand = (uint8_t) random.nextInt (0x80);
470  expectEquals ((int64_t) Conversion::scaleTo16 (rand),
471  (int64_t) baselineScale7To16 (rand));
472  }
473 
474  for (auto i = 0; i != 100; ++i)
475  {
476  const auto rand = (uint16_t) random.nextInt (0x4000);
477  expectEquals ((int64_t) Conversion::scaleTo16 (rand),
478  (int64_t) baselineScale14To16 (rand));
479  }
480 
481  for (auto i = 0; i != 100; ++i)
482  {
483  const auto rand = (uint8_t) random.nextInt (0x80);
484  expectEquals ((int64_t) Conversion::scaleTo32 (rand),
485  (int64_t) baselineScale7To32 (rand));
486  }
487 
488  expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x0000), (int64_t) 0x00000000);
489  expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x2000), (int64_t) 0x80000000);
490  expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x3fff), (int64_t) 0xffffffff);
491 
492  for (auto i = 0; i != 100; ++i)
493  {
494  const auto rand = (uint16_t) random.nextInt (0x4000);
495  expectEquals ((int64_t) Conversion::scaleTo32 (rand),
496  (int64_t) baselineScale14To32 (rand));
497  }
498  }
499 
500  beginTest ("Round-trip widening/narrowing conversions work");
501  {
502  for (auto i = 0; i != 100; ++i)
503  {
504  {
505  const auto rand = (uint8_t) random.nextInt (0x80);
507  }
508 
509  {
510  const auto rand = (uint8_t) random.nextInt (0x80);
512  }
513 
514  {
515  const auto rand = (uint8_t) random.nextInt (0x80);
517  }
518 
519  {
520  const auto rand = (uint16_t) random.nextInt (0x4000);
521  expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo16 (rand)), (uint64_t) rand);
522  }
523 
524  {
525  const auto rand = (uint16_t) random.nextInt (0x4000);
526  expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo32 (rand)), (uint64_t) rand);
527  }
528  }
529  }
530 
531  beginTest ("MIDI 2 -> 1 note on conversions");
532  {
533  {
534  Packets midi2;
535  midi2.add (PacketX2 { 0x41946410, 0x12345678 });
536 
537  Packets midi1;
538  midi1.add (PacketX1 { 0x21946409 });
539 
540  checkMidi2ToMidi1Conversion (midi2, midi1);
541  }
542 
543  {
544  // If the velocity is close to 0, the output velocity should still be 1
545  Packets midi2;
546  midi2.add (PacketX2 { 0x4295327f, 0x00345678 });
547 
548  Packets midi1;
549  midi1.add (PacketX1 { 0x22953201 });
550 
551  checkMidi2ToMidi1Conversion (midi2, midi1);
552  }
553  }
554 
555  beginTest ("MIDI 2 -> 1 note off conversion");
556  {
557  Packets midi2;
558  midi2.add (PacketX2 { 0x448b0520, 0xfedcba98 });
559 
560  Packets midi1;
561  midi1.add (PacketX1 { 0x248b057f });
562 
563  checkMidi2ToMidi1Conversion (midi2, midi1);
564  }
565 
566  beginTest ("MIDI 2 -> 1 poly pressure conversion");
567  {
568  Packets midi2;
569  midi2.add (PacketX2 { 0x49af0520, 0x80dcba98 });
570 
571  Packets midi1;
572  midi1.add (PacketX1 { 0x29af0540 });
573 
574  checkMidi2ToMidi1Conversion (midi2, midi1);
575  }
576 
577  beginTest ("MIDI 2 -> 1 control change conversion");
578  {
579  Packets midi2;
580  midi2.add (PacketX2 { 0x49b00520, 0x80dcba98 });
581 
582  Packets midi1;
583  midi1.add (PacketX1 { 0x29b00540 });
584 
585  checkMidi2ToMidi1Conversion (midi2, midi1);
586  }
587 
588  beginTest ("MIDI 2 -> 1 channel pressure conversion");
589  {
590  Packets midi2;
591  midi2.add (PacketX2 { 0x40d20520, 0x80dcba98 });
592 
593  Packets midi1;
594  midi1.add (PacketX1 { 0x20d24000 });
595 
596  checkMidi2ToMidi1Conversion (midi2, midi1);
597  }
598 
599  beginTest ("MIDI 2 -> 1 nrpn rpn conversion");
600  {
601  {
602  Packets midi2;
603  midi2.add (PacketX2 { 0x44240123, 0x456789ab });
604 
605  Packets midi1;
606  midi1.add (PacketX1 { 0x24b46501 });
607  midi1.add (PacketX1 { 0x24b46423 });
608  midi1.add (PacketX1 { 0x24b40622 });
609  midi1.add (PacketX1 { 0x24b42659 });
610 
611  checkMidi2ToMidi1Conversion (midi2, midi1);
612  }
613 
614  {
615  Packets midi2;
616  midi2.add (PacketX2 { 0x48347f7f, 0xffffffff });
617 
618  Packets midi1;
619  midi1.add (PacketX1 { 0x28b4637f });
620  midi1.add (PacketX1 { 0x28b4627f });
621  midi1.add (PacketX1 { 0x28b4067f });
622  midi1.add (PacketX1 { 0x28b4267f });
623 
624  checkMidi2ToMidi1Conversion (midi2, midi1);
625  }
626  }
627 
628  beginTest ("MIDI 2 -> 1 program change and bank select conversion");
629  {
630  {
631  // If the bank valid bit is 0, just emit a program change
632  Packets midi2;
633  midi2.add (PacketX2 { 0x4cc10000, 0x70004020 });
634 
635  Packets midi1;
636  midi1.add (PacketX1 { 0x2cc17000 });
637 
638  checkMidi2ToMidi1Conversion (midi2, midi1);
639  }
640 
641  {
642  // If the bank valid bit is 1, emit bank select control changes and a program change
643  Packets midi2;
644  midi2.add (PacketX2 { 0x4bc20001, 0x70004020 });
645 
646  Packets midi1;
647  midi1.add (PacketX1 { 0x2bb20040 });
648  midi1.add (PacketX1 { 0x2bb22020 });
649  midi1.add (PacketX1 { 0x2bc27000 });
650 
651  checkMidi2ToMidi1Conversion (midi2, midi1);
652  }
653  }
654 
655  beginTest ("MIDI 2 -> 1 pitch bend conversion");
656  {
657  Packets midi2;
658  midi2.add (PacketX2 { 0x4eee0000, 0x12340000 });
659 
660  Packets midi1;
661  midi1.add (PacketX1 { 0x2eee0d09 });
662 
663  checkMidi2ToMidi1Conversion (midi2, midi1);
664  }
665 
666  beginTest ("MIDI 2 -> 1 messages which don't convert");
667  {
668  const std::byte opcodes[] { std::byte { 0x0 },
669  std::byte { 0x1 },
670  std::byte { 0x4 },
671  std::byte { 0x5 },
672  std::byte { 0x6 },
673  std::byte { 0xf } };
674 
675  for (const auto opcode : opcodes)
676  {
677  Packets midi2;
678  midi2.add (PacketX2 { Utils::bytesToWord (std::byte { 0x40 }, std::byte { opcode << 0x4 }, std::byte { 0 }, std::byte { 0 }), 0x0 });
679  checkMidi2ToMidi1Conversion (midi2, {});
680  }
681  }
682 
683  beginTest ("MIDI 2 -> 1 messages which are passed through");
684  {
685  const uint8_t typecodesX1[] { 0x0, 0x1, 0x2 };
686 
687  for (const auto typecode : typecodesX1)
688  {
689  Packets p;
690  p.add (PacketX1 { (uint32_t) ((int64_t) typecode << 0x1c | (random.nextInt64() & 0xffffff)) });
691 
692  checkMidi2ToMidi1Conversion (p, p);
693  }
694 
695  {
696  Packets p;
697  p.add (PacketX2 { (uint32_t) (0x3 << 0x1c | (random.nextInt64() & 0xffffff)),
698  (uint32_t) (random.nextInt64() & 0xffffffff) });
699 
700  checkMidi2ToMidi1Conversion (p, p);
701  }
702 
703  {
704  Packets p;
705  p.add (PacketX4 { (uint32_t) (0x5 << 0x1c | (random.nextInt64() & 0xffffff)),
706  (uint32_t) (random.nextInt64() & 0xffffffff),
707  (uint32_t) (random.nextInt64() & 0xffffffff),
708  (uint32_t) (random.nextInt64() & 0xffffffff) });
709 
710  checkMidi2ToMidi1Conversion (p, p);
711  }
712  }
713 
714  beginTest ("MIDI 2 -> 1 control changes which should be ignored");
715  {
716  const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 };
717 
718  for (const auto cc : CCs)
719  {
720  Packets midi2;
721  midi2.add (PacketX2 { (uint32_t) (0x40b00000 | (cc << 0x8)), 0x00000000 });
722 
723  checkMidi2ToMidi1Conversion (midi2, {});
724  }
725  }
726 
727  beginTest ("MIDI 1 -> 2 note on conversions");
728  {
729  {
730  Packets midi1;
731  midi1.add (PacketX1 { 0x20904040 });
732 
733  Packets midi2;
734  midi2.add (PacketX2 { 0x40904000, static_cast<uint32_t> (Conversion::scaleTo16 (0x40_u8)) << 0x10 });
735 
736  checkMidi1ToMidi2Conversion (midi1, midi2);
737  }
738 
739  // If velocity is 0, convert to a note-off
740  {
741  Packets midi1;
742  midi1.add (PacketX1 { 0x23935100 });
743 
744  Packets midi2;
745  midi2.add (PacketX2 { 0x43835100, 0x0 });
746 
747  checkMidi1ToMidi2Conversion (midi1, midi2);
748  }
749  }
750 
751  beginTest ("MIDI 1 -> 2 note off conversions");
752  {
753  Packets midi1;
754  midi1.add (PacketX1 { 0x21831020 });
755 
756  Packets midi2;
757  midi2.add (PacketX2 { 0x41831000, static_cast<uint32_t> (Conversion::scaleTo16 (0x20_u8)) << 0x10 });
758 
759  checkMidi1ToMidi2Conversion (midi1, midi2);
760  }
761 
762  beginTest ("MIDI 1 -> 2 poly pressure conversions");
763  {
764  Packets midi1;
765  midi1.add (PacketX1 { 0x20af7330 });
766 
767  Packets midi2;
768  midi2.add (PacketX2 { 0x40af7300, Conversion::scaleTo32 (0x30_u8) });
769 
770  checkMidi1ToMidi2Conversion (midi1, midi2);
771  }
772 
773  beginTest ("individual MIDI 1 -> 2 control changes which should be ignored");
774  {
775  const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 };
776 
777  for (const auto cc : CCs)
778  {
779  Packets midi1;
780  midi1.add (PacketX1 { Utils::bytesToWord (std::byte { 0x20 }, std::byte { 0xb0 }, std::byte { cc }, std::byte { 0x00 }) });
781 
782  checkMidi1ToMidi2Conversion (midi1, {});
783  }
784  }
785 
786  beginTest ("MIDI 1 -> 2 control change conversions");
787  {
788  // normal control change
789  {
790  Packets midi1;
791  midi1.add (PacketX1 { 0x29b1017f });
792 
793  Packets midi2;
794  midi2.add (PacketX2 { 0x49b10100, Conversion::scaleTo32 (0x7f_u8) });
795 
796  checkMidi1ToMidi2Conversion (midi1, midi2);
797  }
798 
799  // nrpn
800  {
801  Packets midi1;
802  midi1.add (PacketX1 { 0x20b06301 });
803  midi1.add (PacketX1 { 0x20b06223 });
804  midi1.add (PacketX1 { 0x20b00645 });
805  midi1.add (PacketX1 { 0x20b02667 });
806 
807  Packets midi2;
808  midi2.add (PacketX2 { 0x40300123, Conversion::scaleTo32 (static_cast<uint16_t> ((0x45 << 7) | 0x67)) });
809 
810  checkMidi1ToMidi2Conversion (midi1, midi2);
811  }
812 
813  // rpn
814  {
815  Packets midi1;
816  midi1.add (PacketX1 { 0x20b06543 });
817  midi1.add (PacketX1 { 0x20b06421 });
818  midi1.add (PacketX1 { 0x20b00601 });
819  midi1.add (PacketX1 { 0x20b02623 });
820 
821  Packets midi2;
822  midi2.add (PacketX2 { 0x40204321, Conversion::scaleTo32 (static_cast<uint16_t> ((0x01 << 7) | 0x23)) });
823 
824  checkMidi1ToMidi2Conversion (midi1, midi2);
825  }
826  }
827 
828  beginTest ("MIDI 1 -> MIDI 2 program change and bank select");
829  {
830  Packets midi1;
831  // program change with bank
832  midi1.add (PacketX1 { 0x2bb20030 });
833  midi1.add (PacketX1 { 0x2bb22010 });
834  midi1.add (PacketX1 { 0x2bc24000 });
835  // program change without bank (different group and channel)
836  midi1.add (PacketX1 { 0x20c01000 });
837 
838  Packets midi2;
839  midi2.add (PacketX2 { 0x4bc20001, 0x40003010 });
840  midi2.add (PacketX2 { 0x40c00000, 0x10000000 });
841 
842  checkMidi1ToMidi2Conversion (midi1, midi2);
843  }
844 
845  beginTest ("MIDI 1 -> MIDI 2 channel pressure conversions");
846  {
847  Packets midi1;
848  midi1.add (PacketX1 { 0x20df3000 });
849 
850  Packets midi2;
851  midi2.add (PacketX2 { 0x40df0000, Conversion::scaleTo32 (0x30_u8) });
852 
853  checkMidi1ToMidi2Conversion (midi1, midi2);
854  }
855 
856  beginTest ("MIDI 1 -> MIDI 2 pitch bend conversions");
857  {
858  Packets midi1;
859  midi1.add (PacketX1 { 0x20e74567 });
860 
861  Packets midi2;
862  midi2.add (PacketX2 { 0x40e70000, Conversion::scaleTo32 (static_cast<uint16_t> ((0x67 << 7) | 0x45)) });
863 
864  checkMidi1ToMidi2Conversion (midi1, midi2);
865  }
866  }
867 
868 private:
869  static Packets toMidi1 (const MidiMessage& msg)
870  {
871  Packets packets;
872  Conversion::toMidi1 (ump::BytestreamMidiView (&msg), [&] (const auto p) { packets.add (p); });
873  return packets;
874  }
875 
876  static Packets convertMidi2ToMidi1 (const Packets& midi2)
877  {
878  Packets r;
879 
880  for (const auto& packet : midi2)
881  Conversion::midi2ToMidi1DefaultTranslation (packet, [&r] (const View& v) { r.add (v); });
882 
883  return r;
884  }
885 
886  static Packets convertMidi1ToMidi2 (const Packets& midi1)
887  {
888  Packets r;
889  Midi1ToMidi2DefaultTranslator translator;
890 
891  for (const auto& packet : midi1)
892  translator.dispatch (packet, [&r] (const View& v) { r.add (v); });
893 
894  return r;
895  }
896 
897  void checkBytestreamConversion (const Packets& actual, const Packets& expected)
898  {
899  expectEquals ((int) actual.size(), (int) expected.size());
900 
901  if (actual.size() != expected.size())
902  return;
903 
904  auto actualPtr = actual.data();
905 
906  std::for_each (expected.data(),
907  expected.data() + expected.size(),
908  [&] (const uint32_t word) { expectEquals ((uint64_t) *actualPtr++, (uint64_t) word); });
909  }
910 
911  void checkMidi2ToMidi1Conversion (const Packets& midi2, const Packets& expected)
912  {
913  checkBytestreamConversion (convertMidi2ToMidi1 (midi2), expected);
914  }
915 
916  void checkMidi1ToMidi2Conversion (const Packets& midi1, const Packets& expected)
917  {
918  checkBytestreamConversion (convertMidi1ToMidi2 (midi1), expected);
919  }
920 
921  MidiMessage createRandomSysEx (Random& random, size_t sysExBytes)
922  {
923  std::vector<uint8_t> data;
924  data.reserve (sysExBytes);
925 
926  for (size_t i = 0; i != sysExBytes; ++i)
927  data.push_back (uint8_t (random.nextInt (0x80)));
928 
929  return MidiMessage::createSysExMessage (data.data(), int (data.size()));
930  }
931 
932  PacketX1 createRandomUtilityUMP (Random& random)
933  {
934  const auto status = random.nextInt (3);
935 
936  return PacketX1 { Utils::bytesToWord (std::byte { 0 },
937  std::byte (status << 0x4),
938  std::byte (status == 0 ? 0 : random.nextInt (0x100)),
939  std::byte (status == 0 ? 0 : random.nextInt (0x100))) };
940  }
941 
942  PacketX1 createRandomRealtimeUMP (Random& random)
943  {
944  const auto status = [&]
945  {
946  switch (random.nextInt (6))
947  {
948  case 0: return std::byte { 0xf8 };
949  case 1: return std::byte { 0xfa };
950  case 2: return std::byte { 0xfb };
951  case 3: return std::byte { 0xfc };
952  case 4: return std::byte { 0xfe };
953  case 5: return std::byte { 0xff };
954  }
955 
956  jassertfalse;
957  return std::byte { 0x00 };
958  }();
959 
960  return PacketX1 { Utils::bytesToWord (std::byte { 0x10 }, status, std::byte { 0x00 }, std::byte { 0x00 }) };
961  }
962 
963  template <typename Fn>
964  void forEachNonSysExTestMessage (Random& random, Fn&& fn)
965  {
966  for (uint16_t counter = 0x80; counter != 0x100; ++counter)
967  {
968  const auto firstByte = (uint8_t) counter;
969 
970  if (firstByte == 0xf0 || firstByte == 0xf7)
971  continue; // sysEx is tested separately
972 
973  const auto length = MidiMessage::getMessageLengthFromFirstByte (firstByte);
974  const auto getDataByte = [&] { return uint8_t (random.nextInt (256) & 0x7f); };
975 
976  const auto message = [&]
977  {
978  switch (length)
979  {
980  case 1: return MidiMessage (firstByte);
981  case 2: return MidiMessage (firstByte, getDataByte());
982  case 3: return MidiMessage (firstByte, getDataByte(), getDataByte());
983  }
984 
985  return MidiMessage();
986  }();
987 
988  fn (message);
989  }
990  }
991 
992  static bool equal (const MidiMessage& a, const MidiMessage& b) noexcept
993  {
994  return a.getRawDataSize() == b.getRawDataSize()
995  && std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(), b.getRawData());
996  }
997 
998  static bool equal (const MidiBuffer& a, const MidiBuffer& b) noexcept
999  {
1000  return a.data == b.data;
1001  }
1002 };
1003 
1004 static UniversalMidiPacketTests universalMidiPacketTests;
1005 
1006 } // namespace juce::universal_midi_packets
static MidiMessage createSysExMessage(const void *sysexData, int dataSize)
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
static int getMessageLengthFromFirstByte(uint8 firstByte) noexcept
void expectEquals(ValueType actual, ValueType expected, String failureMessage=String())
UnitTest(const String &name, const String &category=String())
void beginTest(const String &testName)
void expect(bool testResult, const String &failureMessage=String())
Random getRandom() const
static MidiMessage fromUmp(const PacketX1 &m, double time=0)
static uint16_t scaleTo14(uint16_t word16Bit)
static void midi2ToMidi1DefaultTranslation(const View &v, Callback &&callback)
static uint32_t scaleTo32(uint8_t word7Bit)
static uint16_t scaleTo16(uint8_t word7Bit)
static void toMidi1(const BytestreamMidiView &m, PacketCallbackFunction &&callback)
static uint8_t scaleTo8(uint8_t word7Bit)
static uint8_t scaleTo7(uint8_t word8Bit)
static constexpr uint32_t bytesToWord(std::byte a, std::byte b, std::byte c, std::byte d)
Definition: juce_UMPUtils.h:36