The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
7.0KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. The code included in this file is provided under the terms of the ISC license
  8. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  9. To use, copy, modify, and/or distribute this software for any purpose with or
  10. without fee is hereby granted provided that the above copyright notice and
  11. this permission notice appear in all copies.
  12. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  13. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  14. DISCLAIMED.
  15. ==============================================================================
  16. */
  17. #ifndef DOXYGEN
  18. namespace juce::universal_midi_packets
  19. {
  20. /**
  21. Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using
  22. the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages.
  23. @tags{Audio}
  24. */
  25. class Midi1ToBytestreamTranslator
  26. {
  27. public:
  28. /** Ensures that there is room in the internal buffer for a sysex message of at least
  29. `initialBufferSize` bytes.
  30. */
  31. explicit Midi1ToBytestreamTranslator (int initialBufferSize)
  32. {
  33. pendingSysExData.reserve (size_t (initialBufferSize));
  34. }
  35. /** Clears the concatenator. */
  36. void reset()
  37. {
  38. pendingSysExData.clear();
  39. pendingSysExTime = 0.0;
  40. }
  41. /** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to
  42. an equivalent MidiMessage. Accumulates SysEx packets into a single
  43. MidiMessage, as appropriate.
  44. @param packet a packet which is using the MIDI 1.0 Protocol.
  45. @param time the timestamp to be applied to these messages.
  46. @param callback a callback which will be called with each converted MidiMessage.
  47. */
  48. template <typename MessageCallback>
  49. void dispatch (const View& packet, double time, MessageCallback&& callback)
  50. {
  51. const auto firstWord = *packet.data();
  52. if (! pendingSysExData.empty() && shouldPacketTerminateSysExEarly (firstWord))
  53. pendingSysExData.clear();
  54. switch (packet.size())
  55. {
  56. case 1:
  57. {
  58. // Utility messages don't translate to bytestream format
  59. if (Utils::getMessageType (firstWord) != 0x00)
  60. {
  61. const auto message = fromUmp (PacketX1 { firstWord }, time);
  62. callback (BytestreamMidiView (&message));
  63. }
  64. break;
  65. }
  66. case 2:
  67. {
  68. if (Utils::getMessageType (firstWord) == 0x3)
  69. processSysEx (PacketX2 { packet[0], packet[1] }, time, callback);
  70. break;
  71. }
  72. case 3: // no 3-word packets in the current spec
  73. case 4: // no 4-word packets translate to bytestream format
  74. default:
  75. break;
  76. }
  77. }
  78. /** Converts from a Universal MIDI Packet to MIDI 1 bytestream format.
  79. This is only capable of converting a single Universal MIDI Packet to
  80. an equivalent bytestream MIDI message. This function cannot understand
  81. multi-packet messages, like SysEx7 messages.
  82. To convert multi-packet messages, use `Midi1ToBytestreamTranslator`
  83. to convert from a UMP MIDI 1.0 stream, or `ToBytestreamDispatcher`
  84. to convert from both MIDI 2.0 and MIDI 1.0.
  85. */
  86. static MidiMessage fromUmp (const PacketX1& m, double time = 0)
  87. {
  88. const auto word = m.front();
  89. jassert (Utils::getNumWordsForMessageType (word) == 1);
  90. const std::array<uint8_t, 3> bytes { { uint8_t ((word >> 0x10) & 0xff),
  91. uint8_t ((word >> 0x08) & 0xff),
  92. uint8_t ((word >> 0x00) & 0xff) } };
  93. const auto numBytes = MidiMessage::getMessageLengthFromFirstByte (bytes.front());
  94. return MidiMessage (bytes.data(), numBytes, time);
  95. }
  96. private:
  97. template <typename MessageCallback>
  98. void processSysEx (const PacketX2& packet,
  99. double time,
  100. MessageCallback&& callback)
  101. {
  102. switch (getSysEx7Kind (packet[0]))
  103. {
  104. case SysEx7::Kind::complete:
  105. startSysExMessage (time);
  106. pushBytes (packet);
  107. terminateSysExMessage (callback);
  108. break;
  109. case SysEx7::Kind::begin:
  110. startSysExMessage (time);
  111. pushBytes (packet);
  112. break;
  113. case SysEx7::Kind::continuation:
  114. if (pendingSysExData.empty())
  115. break;
  116. pushBytes (packet);
  117. break;
  118. case SysEx7::Kind::end:
  119. if (pendingSysExData.empty())
  120. break;
  121. pushBytes (packet);
  122. terminateSysExMessage (callback);
  123. break;
  124. }
  125. }
  126. void pushBytes (const PacketX2& packet)
  127. {
  128. const auto bytes = SysEx7::getDataBytes (packet);
  129. pendingSysExData.insert (pendingSysExData.end(),
  130. bytes.data.begin(),
  131. bytes.data.begin() + bytes.size);
  132. }
  133. void startSysExMessage (double time)
  134. {
  135. pendingSysExTime = time;
  136. pendingSysExData.push_back (std::byte { 0xf0 });
  137. }
  138. template <typename MessageCallback>
  139. void terminateSysExMessage (MessageCallback&& callback)
  140. {
  141. pendingSysExData.push_back (std::byte { 0xf7 });
  142. callback (BytestreamMidiView (pendingSysExData, pendingSysExTime));
  143. pendingSysExData.clear();
  144. }
  145. static bool shouldPacketTerminateSysExEarly (uint32_t firstWord)
  146. {
  147. return ! (isSysExContinuation (firstWord)
  148. || isSystemRealTime (firstWord)
  149. || isJROrNOP (firstWord));
  150. }
  151. static SysEx7::Kind getSysEx7Kind (uint32_t word)
  152. {
  153. return SysEx7::Kind ((word >> 0x14) & 0xf);
  154. }
  155. static bool isJROrNOP (uint32_t word)
  156. {
  157. return Utils::getMessageType (word) == 0x0;
  158. }
  159. static bool isSysExContinuation (uint32_t word)
  160. {
  161. if (Utils::getMessageType (word) != 0x3)
  162. return false;
  163. const auto kind = getSysEx7Kind (word);
  164. return kind == SysEx7::Kind::continuation || kind == SysEx7::Kind::end;
  165. }
  166. static bool isSystemRealTime (uint32_t word)
  167. {
  168. return Utils::getMessageType (word) == 0x1 && ((word >> 0x10) & 0xff) >= 0xf8;
  169. }
  170. std::vector<std::byte> pendingSysExData;
  171. double pendingSysExTime = 0.0;
  172. };
  173. } // namespace juce::universal_midi_packets
  174. #endif