|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 7 End-User License
- Agreement and JUCE Privacy Policy.
-
- End User License Agreement: www.juce.com/juce-7-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- /*
- Utilities for converting sequences of bytes to and from
- C++ struct types.
- */
- namespace juce::midi_ci::detail::Marshalling
- {
-
- template <uint8_t> struct IntForNumBytes;
- template <> struct IntForNumBytes<1> { using Type = uint8_t; };
- template <> struct IntForNumBytes<2> { using Type = uint16_t; };
- template <> struct IntForNumBytes<4> { using Type = uint32_t; };
-
- template <uint8_t NumBytes> using IntForNumBytesT = typename IntForNumBytes<NumBytes>::Type;
-
- //==============================================================================
- /*
- Reads a sequence of bytes representing a MIDI-CI message, and populates
- structs with the information contained in the message.
- */
- class Reader
- {
- public:
- /* Constructs a reader that will parse the provided buffer, using the most
- recent known MIDI-CI version.
- */
- explicit Reader (Span<const std::byte> b)
- : Reader (b, static_cast<uint8_t> (MessageMeta::implementationVersion)) {}
-
- /* Constructs a reader for the provided MIDI-CI version that will parse
- the provided buffer. Fields introduced in later versions will be ignored,
- and so left with their default values.
- */
- Reader (Span<const std::byte> b, int v)
- : bytes (b), version (v) {}
-
- std::optional<int> getVersion() const { return version; }
-
- /* Attempts to interpret the byte sequence passed to the constructor
- as a sequence of structs 'T'.
-
- Returns true if parsing succeeds, otherwise returns false.
- */
- template <typename... T>
- bool operator() (T&&... t)
- {
- return (doArchiveChecked (std::forward<T> (t)) && ...);
- }
-
- private:
- template <typename T>
- bool doArchiveChecked (T&& t)
- {
- if (failed)
- return false;
-
- doArchive (t);
- return ! failed;
- }
-
- void doArchive (ChannelInGroup& x)
- {
- if (const auto popped = popBytes (1))
- {
- const auto p = *popped;
- x = ChannelInGroup (p[0] & std::byte { 0x7f });
- return;
- }
-
- failed = true;
- }
-
- // If we're trying to parse into a constant, then we should check that the next byte(s)
- // match that constant.
- void doArchive (const std::byte& x)
- {
- std::byte temp{};
-
- if (! doArchiveChecked (temp))
- return;
-
- failed |= x != temp;
- }
-
- void doArchive (std::byte& x)
- {
- if (const auto popped = popBytes (1))
- {
- const auto p = *popped;
- x = p[0] & std::byte { 0x7f };
- return;
- }
-
- failed = true;
- }
-
- void doArchive (const uint16_t& x)
- {
- uint16_t temp{};
-
- if (! doArchiveChecked (temp))
- return;
-
- failed |= temp != x;
- }
-
- void doArchive (uint16_t& x)
- {
- if (const auto popped = popBytes (2))
- {
- const auto p = *popped;
- x = (uint16_t) (((uint16_t) p[0] & 0x7f) << 0x00)
- | (uint16_t) (((uint16_t) p[1] & 0x7f) << 0x07);
- return;
- }
-
- failed = true;
- }
-
- void doArchive (const uint32_t& x)
- {
- uint32_t temp{};
-
- if (! doArchiveChecked (temp))
- return;
-
- failed |= temp != x;
- }
-
- void doArchive (uint32_t& x)
- {
- if (const auto popped = popBytes (4))
- {
- const auto p = *popped;
- x = (((uint32_t) p[0] & 0x7f) << 0x00)
- | (((uint32_t) p[1] & 0x7f) << 0x07)
- | (((uint32_t) p[2] & 0x7f) << 0x0e)
- | (((uint32_t) p[3] & 0x7f) << 0x15);
- return;
- }
-
- failed = true;
- }
-
- template <uint8_t NumBytes, bool B>
- void doArchive (MessageMeta::SpanWithSizeBytes<NumBytes, Span<const std::byte>, B> x)
- {
- IntForNumBytesT<NumBytes> numBytes{};
-
- // Read the number of bytes in the field
- if (! doArchiveChecked (numBytes))
- return;
-
- // Attempt to pop that many bytes
- if (const auto popped = popBytes (numBytes))
- {
- x.span = *popped;
- return;
- }
-
- failed = true;
- }
-
- template <uint8_t NumBytes, size_t N>
- void doArchive (MessageMeta::SpanWithSizeBytes<NumBytes, Span<const std::array<std::byte, N>>> x)
- {
- IntForNumBytesT<NumBytes> numItems{};
-
- // Read the number of items in the field
- if (! doArchiveChecked (numItems))
- return;
-
- if (const auto popped = popBytes (numItems * N))
- {
- x.span = Span (unalignedPointerCast<const std::array<std::byte, N>*> (popped->data()), numItems);
- return;
- }
-
- failed = true;
- }
-
- template <size_t N>
- void doArchive (Span<const std::byte, N>& x)
- {
- if (const auto popped = popBytes (bytes.size()))
- {
- x = *popped;
- return;
- }
-
- failed = true;
- }
-
- template <size_t N>
- void doArchive (std::array<std::byte, N>& x)
- {
- if (const auto popped = popBytes (x.size()))
- {
- const auto p = *popped;
- std::transform (p.begin(), p.end(), x.begin(), [] (std::byte b)
- {
- return b & std::byte { 0x7f };
- });
- return;
- }
-
- failed = true;
- }
-
- template <typename T>
- void doArchive (T& t)
- {
- juce::detail::doLoad (*this, t);
- }
-
- template <typename T>
- void doArchive (Named<T> named)
- {
- doArchiveChecked (named.value);
- }
-
- std::optional<Span<const std::byte>> popBytes (size_t num)
- {
- if (bytes.size() < num)
- return {};
-
- const Span result { bytes.data(), num };
- bytes = Span { bytes.data() + num, bytes.size() - num };
- return result;
- }
-
- Span<const std::byte> bytes; /* Bytes making up a CI message. */
- int version{}; /* The version to assume when parsing the message, specified in the message header. */
- bool failed = false;
- };
-
- //==============================================================================
- /*
- Converts one or more structs into a byte sequence suitable for transmission
- as a MIDI-CI message.
- */
- class Writer
- {
- public:
- /* Constructs a writer that will write into the provided buffer. */
- explicit Writer (std::vector<std::byte>& b)
- : Writer (b, static_cast<uint8_t> (MessageMeta::implementationVersion)) {}
-
- /* Constructs a writer that will write a MIDI-CI message of the requested
- version to the provided buffer.
-
- Fields introduced in later MIDI-CI versions will be ignored.
- */
- Writer (std::vector<std::byte>& b, int v)
- : bytes (b), version (v) {}
-
- std::optional<int> getVersion() const { return version; }
-
- /* Formats the information contained in the provided structs into a
- MIDI-CI message, and returns a bool indicating success or failure.
- */
- template <typename... T>
- bool operator() (const T&... t)
- {
- return (doArchiveChecked (t) && ...);
- }
-
- private:
- template <typename T>
- bool doArchiveChecked (T&& t)
- {
- if (failed)
- return false;
-
- doArchive (t);
- return ! failed;
- }
-
- void doArchive (ChannelInGroup x)
- {
- doArchiveChecked (std::byte (x));
- }
-
- void doArchive (std::byte x)
- {
- bytes.push_back (x);
- }
-
- void doArchive (uint16_t x)
- {
- bytes.insert (bytes.end(), { (std::byte) ((x >> 0x00) & 0x7f),
- (std::byte) ((x >> 0x07) & 0x7f) });
- }
-
- void doArchive (uint32_t x)
- {
- bytes.insert (bytes.end(), { (std::byte) ((x >> 0x00) & 0x7f),
- (std::byte) ((x >> 0x07) & 0x7f),
- (std::byte) ((x >> 0x0e) & 0x7f),
- (std::byte) ((x >> 0x15) & 0x7f) });
- }
-
- template <uint8_t NumBytes, typename T, bool B>
- void doArchive (MessageMeta::SpanWithSizeBytes<NumBytes, T, B> x)
- {
- if (x.span.size() >= (1 << (7 * NumBytes)))
- {
- // Unable to express the size of the field in the requested number of bytes
- jassertfalse;
- failed = true;
- return;
- }
-
- // Write the number of bytes, followed by the bytes themselves.
- const auto numBytes = (IntForNumBytesT<NumBytes>) x.span.size();
- doArchiveChecked (numBytes);
- doArchiveChecked (x.span);
- }
-
- template <typename T, size_t N>
- void doArchive (Span<const T, N> x)
- {
- failed = ! std::all_of (x.begin(), x.end(), [&] (const auto& item)
- {
- return doArchiveChecked (item);
- });
- }
-
- template <size_t N>
- void doArchive (const std::array<std::byte, N>& x)
- {
- bytes.insert (bytes.end(), x.begin(), x.end());
- }
-
- template <typename T>
- void doArchive (const T& t)
- {
- juce::detail::doSave (*this, t);
- }
-
- template <typename T>
- void doArchive (Named<T> named)
- {
- doArchiveChecked (named.value);
- }
-
- std::vector<std::byte>& bytes; /* The buffer that will hold the completed message. */
- int version{}; /* The version to assume when writing the message, specified in the message header. */
- bool failed = false;
- };
-
- } // namespace juce::midi_ci::detail::Marshalling
|