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.

154 lines
5.4KB

  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. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce::midi_ci::detail
  19. {
  20. PropertyDataMessageChunker::PropertyDataMessageChunker (std::vector<std::byte>& storageIn,
  21. int chunkSizeIn,
  22. const std::byte messageKindIn,
  23. const std::byte requestIdIn,
  24. Span<const std::byte> headerIn,
  25. MUID sourceIn,
  26. MUID destIn,
  27. InputStream& bodyIn)
  28. : header (headerIn),
  29. storage (&storageIn),
  30. body (&bodyIn),
  31. source (sourceIn),
  32. dest (destIn),
  33. chunkSize (chunkSizeIn),
  34. messageKind (messageKindIn),
  35. requestId (requestIdIn)
  36. {
  37. if (hasRoomForBody())
  38. {
  39. populateStorage();
  40. }
  41. else
  42. {
  43. // Header too large! There's no way to fit this message into the requested chunk size.
  44. jassertfalse;
  45. *this = PropertyDataMessageChunker();
  46. }
  47. }
  48. PropertyDataMessageChunker& PropertyDataMessageChunker::operator++() noexcept
  49. {
  50. if (*this != PropertyDataMessageChunker())
  51. {
  52. if (body->isExhausted())
  53. {
  54. *this = PropertyDataMessageChunker();
  55. }
  56. else
  57. {
  58. ++thisChunk;
  59. populateStorage();
  60. }
  61. }
  62. return *this;
  63. }
  64. Span<const std::byte> PropertyDataMessageChunker::operator*() const noexcept
  65. {
  66. // The end of the stream was reached, no point dereferencing the iterator now!
  67. jassert (storage != nullptr && (int) storage->size() <= chunkSize);
  68. return *storage;
  69. }
  70. Span<const std::byte> PropertyDataMessageChunker::getHeaderForBlock() const
  71. {
  72. return thisChunk == 1 ? header : Span<const std::byte>{};
  73. }
  74. int PropertyDataMessageChunker::getRoomForBody() const
  75. {
  76. return chunkSize - (int) (getHeaderForBlock().size() + 22);
  77. }
  78. bool PropertyDataMessageChunker::hasRoomForBody() const
  79. {
  80. const auto bodyRoom = getRoomForBody();
  81. return (0 < bodyRoom)
  82. || (0 == bodyRoom && body->getNumBytesRemaining() == 0);
  83. }
  84. void PropertyDataMessageChunker::populateStorage() const
  85. {
  86. storage->clear();
  87. storage->resize ((size_t) getRoomForBody());
  88. // Read body data into buffer
  89. const auto numBytesRead = (uint16_t) jmax (ssize_t (0), body->read (storage->data(), storage->size()));
  90. const auto [numChunks, thisChunkNum] = [&]() -> std::tuple<uint16_t, uint16_t>
  91. {
  92. if (body->isExhausted() || body->getNumBytesRemaining() == 0)
  93. return std::tuple (thisChunk, thisChunk);
  94. const auto totalLength = body->getTotalLength();
  95. if (totalLength < 0)
  96. return std::tuple ((uint16_t) 0, thisChunk); // 0 means "unknown number"
  97. const auto roomForBody = getRoomForBody();
  98. if (roomForBody != 0)
  99. return std::tuple ((uint16_t) ((totalLength + roomForBody - 1) / roomForBody), thisChunk);
  100. // During construction, the input stream reported that it had no data remaining, so no
  101. // space was reserved for body content.
  102. // Now, the input stream reports that it has data remaining, but there's nowhere
  103. // to fit it in the message!
  104. jassertfalse;
  105. return std::tuple (thisChunk, (uint16_t) 0); // 0 means "data potentially unusable"
  106. }();
  107. // Now we know how many bytes we managed to read, write the header at the end of the buffer
  108. const auto headerForBlock = getHeaderForBlock();
  109. detail::Marshalling::Writer writer { *storage };
  110. writer (Message::Header { ChannelInGroup::wholeBlock,
  111. messageKind,
  112. detail::MessageMeta::implementationVersion,
  113. source,
  114. dest },
  115. requestId,
  116. detail::MessageMeta::makeSpanWithSizeBytes<2> (headerForBlock),
  117. numChunks,
  118. thisChunkNum,
  119. numBytesRead);
  120. // Finally, swap the header to the beginning of the buffer
  121. std::rotate (storage->begin(), storage->begin() + getRoomForBody(), storage->end());
  122. // ...and bring the storage buffer down to size, if we didn't manage to fill it
  123. const auto room = (size_t) getRoomForBody();
  124. storage->resize (storage->size() + numBytesRead - room);
  125. }
  126. } // namespace juce::midi_ci::detail