build(deps): vendor JUCE 7.0.12
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
@@ -0,0 +1,623 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
Namespace containing metadata about MIDI-CI message types, such as
|
||||
replies corresponding to inquiries, and serialization functions.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
namespace juce::midi_ci::detail::MessageMeta
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/* The maximum CI version that can be parsed and generated by this implementation. */
|
||||
static constexpr std::byte implementationVersion { 0x02 };
|
||||
|
||||
/* Wraps a pointer to a Span. Used to indicate to CI readers/writers that a particular field is
|
||||
of variable length, starting with a 16-bit or 32-bit byte count.
|
||||
*/
|
||||
template <uint8_t NumBytes, typename T, bool isJson = false>
|
||||
struct SpanWithSizeBytes
|
||||
{
|
||||
T& span;
|
||||
};
|
||||
|
||||
/* Creates a SpanWithSizeBytes with an appropriate template argument. */
|
||||
template <uint8_t NumBytes, typename T>
|
||||
static constexpr auto makeSpanWithSizeBytes ( Span<T>& span) { return SpanWithSizeBytes<NumBytes, Span<T>> { span }; }
|
||||
|
||||
template <uint8_t NumBytes, typename T>
|
||||
static constexpr auto makeSpanWithSizeBytes (const Span<T>& span) { return SpanWithSizeBytes<NumBytes, const Span<T>> { span }; }
|
||||
|
||||
template <uint8_t NumBytes, typename T>
|
||||
static constexpr auto makeJsonWithSizeBytes ( Span<T>& span) { return SpanWithSizeBytes<NumBytes, Span<T>, true> { span }; }
|
||||
|
||||
template <uint8_t NumBytes, typename T>
|
||||
static constexpr auto makeJsonWithSizeBytes (const Span<T>& span) { return SpanWithSizeBytes<NumBytes, const Span<T>, true> { span }; }
|
||||
|
||||
template <uint8_t SubID2, typename R = void>
|
||||
struct Metadata
|
||||
{
|
||||
static constexpr std::byte subID2 { SubID2 };
|
||||
using Reply = R;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Meta;
|
||||
|
||||
template <>
|
||||
struct Meta<Message::DiscoveryResponse> : Metadata<0x71> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::Discovery> : Metadata<0x70, Message::DiscoveryResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::EndpointInquiryResponse> : Metadata<0x73> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::EndpointInquiry> : Metadata<0x72, Message::EndpointInquiryResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::InvalidateMUID> : Metadata<0x7e> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ACK> : Metadata<0x7d> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::NAK> : Metadata<0x7f> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileInquiryResponse> : Metadata<0x21> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileInquiry> : Metadata<0x20, Message::ProfileInquiryResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileAdded> : Metadata<0x26> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileRemoved> : Metadata<0x27> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileDetailsResponse> : Metadata<0x29> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileDetails> : Metadata<0x28, Message::ProfileDetailsResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileOn> : Metadata<0x22> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileOff> : Metadata<0x23> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileEnabledReport> : Metadata<0x24> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileDisabledReport> : Metadata<0x25> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProfileSpecificData> : Metadata<0x2f> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertyExchangeCapabilitiesResponse> : Metadata<0x31> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertyExchangeCapabilities> : Metadata<0x30, Message::PropertyExchangeCapabilitiesResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::StaticSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::DynamicSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertyGetDataResponse> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x35> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertyGetData> : Meta<Message::StaticSizePropertyExchange>, Metadata<0x34, Message::PropertyGetDataResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertySetDataResponse> : Meta<Message::StaticSizePropertyExchange>, Metadata<0x37> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertySetData> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x36, Message::PropertySetDataResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertySubscribeResponse> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x39> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertySubscribe> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x38, Message::PropertySubscribeResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::PropertyNotify> : Meta<Message::DynamicSizePropertyExchange>, Metadata<0x3f> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProcessInquiryResponse> : Metadata<0x41> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProcessInquiry> : Metadata<0x40, Message::ProcessInquiryResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProcessMidiMessageReportResponse> : Metadata<0x43> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProcessMidiMessageReport> : Metadata<0x42, Message::ProcessMidiMessageReportResponse> {};
|
||||
|
||||
template <>
|
||||
struct Meta<Message::ProcessEndMidiMessageReport> : Metadata<0x44> {};
|
||||
|
||||
} // namespace juce::midi_ci::detail::MessageMeta
|
||||
|
||||
#ifndef DOXYGEN
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct VersionBase
|
||||
{
|
||||
static constexpr auto marshallingVersion = (int) juce::midi_ci::detail::MessageMeta::implementationVersion;
|
||||
};
|
||||
|
||||
template <uint8_t NumBytes, typename T, bool isJson>
|
||||
struct SerialisationTraits<midi_ci::detail::MessageMeta::SpanWithSizeBytes<NumBytes, T, isJson>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename This>
|
||||
static auto getSize (This& t)
|
||||
{
|
||||
if constexpr (NumBytes == 1)
|
||||
return (uint8_t) t.size();
|
||||
else if constexpr (NumBytes == 2)
|
||||
return (uint16_t) t.size();
|
||||
else if constexpr (NumBytes == 4)
|
||||
return (uint32_t) t.size();
|
||||
else if constexpr (NumBytes == 8)
|
||||
return (uint64_t) t.size();
|
||||
else
|
||||
static_assert (detail::delayStaticAssert<T>, "NumBytes is not a power of two");
|
||||
}
|
||||
|
||||
template <typename Archive, typename This>
|
||||
static auto load (Archive&, This&)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Archive, typename This>
|
||||
static auto save (Archive& archive, const This& t)
|
||||
{
|
||||
auto size = getSize (t.span);
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (const auto& element : t.span)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::MUID> : VersionBase
|
||||
{
|
||||
template <typename Archive>
|
||||
static auto load (Archive& archive, ci::MUID& t)
|
||||
{
|
||||
uint32_t muid{};
|
||||
auto result = archive (muid);
|
||||
t = ci::MUID::makeUnchecked (muid);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Archive>
|
||||
static auto save (Archive& archive, const ci::MUID& t)
|
||||
{
|
||||
return archive (t.get());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::Header> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
const std::byte universalSystemExclusive { 0x7e }, subID { 0x0d };
|
||||
return archive (universalSystemExclusive,
|
||||
t.deviceID,
|
||||
subID,
|
||||
t.category,
|
||||
t.version,
|
||||
t.source,
|
||||
t.destination);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::Generic> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
return archive (t.header, t.data);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::DiscoveryResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("device", t.device),
|
||||
named ("capabilities", t.capabilities),
|
||||
named ("maximumSysexSize", t.maximumSysexSize));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("outputPathID", t.outputPathID), named ("functionBlock", t.functionBlock));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::Discovery> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("device", t.device),
|
||||
named ("capabilities", t.capabilities),
|
||||
named ("maximumSysexSize", t.maximumSysexSize));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("outputPathID", t.outputPathID));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::EndpointInquiryResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("status", t.status),
|
||||
named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::EndpointInquiry> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("status", t.status));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::InvalidateMUID> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("target", t.target));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ACK> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("originalCategory", t.originalCategory),
|
||||
named ("statusCode", t.statusCode),
|
||||
named ("statusData", t.statusData),
|
||||
named ("details", t.details),
|
||||
named ("messageText", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.messageText)));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::NAK> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
if (0x02 <= archive.getVersion())
|
||||
{
|
||||
archive (named ("originalCategory", t.originalCategory),
|
||||
named ("statusCode", t.statusCode),
|
||||
named ("statusData", t.statusData),
|
||||
named ("details", t.details),
|
||||
named ("messageText", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.messageText)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileInquiryResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("enabledProfiles", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.enabledProfiles)),
|
||||
named ("disabledProfiles", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.disabledProfiles)));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileInquiry> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive&, This&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileAdded> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileRemoved> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileDetailsResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile),
|
||||
named ("target", t.target),
|
||||
named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileDetails> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile),
|
||||
named ("target", t.target));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileOn> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("numChannels", t.numChannels));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileOff> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
{
|
||||
uint16_t reserved{};
|
||||
archive (named ("reserved", reserved));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileEnabledReport> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("numChannels", t.numChannels));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileDisabledReport> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("numChannels", t.numChannels));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProfileSpecificData> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("profile", t.profile), named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<4> (t.data)));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertyExchangeCapabilitiesResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("numRequests", t.numSimultaneousRequestsSupported));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("major", t.majorVersion), named ("minor", t.minorVersion));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertyExchangeCapabilities> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("numRequests", t.numSimultaneousRequestsSupported));
|
||||
|
||||
if (0x02 <= archive.getVersion())
|
||||
archive (named ("major", t.majorVersion), named ("minor", t.minorVersion));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::StaticSizePropertyExchange> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
const uint16_t chunkNum = 1, dataLength = 0;
|
||||
archive (named ("requestID", t.requestID),
|
||||
named ("header", midi_ci::detail::MessageMeta::makeJsonWithSizeBytes<2> (t.header)),
|
||||
named ("numChunks", chunkNum),
|
||||
named ("thisChunk", chunkNum),
|
||||
named ("length", dataLength));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::DynamicSizePropertyExchange> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (named ("requestID", t.requestID),
|
||||
named ("header", midi_ci::detail::MessageMeta::makeJsonWithSizeBytes<2> (t.header)),
|
||||
named ("numChunks", t.totalNumChunks),
|
||||
named ("thisChunk", t.thisChunkNum),
|
||||
named ("data", midi_ci::detail::MessageMeta::makeSpanWithSizeBytes<2> (t.data)));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertyGetDataResponse> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertyGetData> : SerialisationTraits<ci::Message::StaticSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertySetDataResponse> : SerialisationTraits<ci::Message::StaticSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertySetData> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertySubscribeResponse> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertySubscribe> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::PropertyNotify> : SerialisationTraits<ci::Message::DynamicSizePropertyExchange> {};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProcessInquiryResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
archive (t.supportedFeatures);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProcessInquiry> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive&, This&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProcessMidiMessageReportResponse> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
std::byte reserved{};
|
||||
archive (named ("messageDataControl", t.messageDataControl),
|
||||
named ("requestedMessages", t.requestedMessages),
|
||||
named ("reserved", reserved),
|
||||
named ("channelControllerMessages", t.channelControllerMessages),
|
||||
named ("noteDataMessages", t.noteDataMessages));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProcessMidiMessageReport> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive& archive, This& t)
|
||||
{
|
||||
std::byte reserved{};
|
||||
archive (named ("messageDataControl", t.messageDataControl),
|
||||
named ("requestedMessages", t.requestedMessages),
|
||||
named ("reserved", reserved),
|
||||
named ("channelControllerMessages", t.channelControllerMessages),
|
||||
named ("noteDataMessages", t.noteDataMessages));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<ci::Message::ProcessEndMidiMessageReport> : VersionBase
|
||||
{
|
||||
template <typename Archive, typename This>
|
||||
static auto serialise (Archive&, This&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
||||
#endif // ifndef DOXYGEN
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::midi_ci::detail::MessageTypeUtils
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
An interface used for types that want to operate on parsed MIDI-CI messages.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
struct MessageVisitor
|
||||
{
|
||||
MessageVisitor() = default;
|
||||
MessageVisitor (const MessageVisitor&) = default;
|
||||
MessageVisitor (MessageVisitor&&) = default;
|
||||
MessageVisitor& operator= (const MessageVisitor&) = default;
|
||||
MessageVisitor& operator= (MessageVisitor&&) = default;
|
||||
virtual ~MessageVisitor() = default;
|
||||
|
||||
virtual void visit (const std::monostate&) const {}
|
||||
virtual void visit (const Message::Discovery&) const {}
|
||||
virtual void visit (const Message::EndpointInquiry&) const {}
|
||||
virtual void visit (const Message::ProfileInquiry&) const {}
|
||||
virtual void visit (const Message::ProfileDetails&) const {}
|
||||
virtual void visit (const Message::PropertyExchangeCapabilities&) const {}
|
||||
virtual void visit (const Message::PropertyGetData&) const {}
|
||||
virtual void visit (const Message::PropertySetData&) const {}
|
||||
virtual void visit (const Message::PropertySubscribe&) const {}
|
||||
virtual void visit (const Message::ProcessInquiry&) const {}
|
||||
virtual void visit (const Message::ProcessMidiMessageReport&) const {}
|
||||
virtual void visit (const Message::DiscoveryResponse&) const {}
|
||||
virtual void visit (const Message::EndpointInquiryResponse&) const {}
|
||||
virtual void visit (const Message::InvalidateMUID&) const {}
|
||||
virtual void visit (const Message::ACK&) const {}
|
||||
virtual void visit (const Message::NAK&) const {}
|
||||
virtual void visit (const Message::ProfileInquiryResponse&) const {}
|
||||
virtual void visit (const Message::ProfileAdded&) const {}
|
||||
virtual void visit (const Message::ProfileRemoved&) const {}
|
||||
virtual void visit (const Message::ProfileDetailsResponse&) const {}
|
||||
virtual void visit (const Message::ProfileOn&) const {}
|
||||
virtual void visit (const Message::ProfileOff&) const {}
|
||||
virtual void visit (const Message::ProfileEnabledReport&) const {}
|
||||
virtual void visit (const Message::ProfileDisabledReport&) const {}
|
||||
virtual void visit (const Message::ProfileSpecificData&) const {}
|
||||
virtual void visit (const Message::PropertyExchangeCapabilitiesResponse&) const {}
|
||||
virtual void visit (const Message::PropertyGetDataResponse&) const {}
|
||||
virtual void visit (const Message::PropertySetDataResponse&) const {}
|
||||
virtual void visit (const Message::PropertySubscribeResponse&) const {}
|
||||
virtual void visit (const Message::PropertyNotify&) const {}
|
||||
virtual void visit (const Message::ProcessInquiryResponse&) const {}
|
||||
virtual void visit (const Message::ProcessMidiMessageReportResponse&) const {}
|
||||
virtual void visit (const Message::ProcessEndMidiMessageReport&) const {}
|
||||
};
|
||||
|
||||
using ParseFn = Message::Parsed::Body (*) (Message::Generic, Parser::Status* status);
|
||||
using VisitFn = void (*) (const Message::Parsed&, const MessageVisitor&);
|
||||
|
||||
/* These return the Universal System Exclusive Sub-ID#2 for a particular message type. */
|
||||
template <typename Specific>
|
||||
static constexpr auto getParserFor (std::in_place_type_t<Specific>)
|
||||
{
|
||||
return [] (Message::Generic message, Parser::Status* status) -> Message::Parsed::Body
|
||||
{
|
||||
// Parse messages using the version specified in the header of the message
|
||||
if (Specific parsed; Marshalling::Reader { message.data, static_cast<uint8_t> (message.header.version) } (parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
if (status != nullptr)
|
||||
*status = Parser::Status::malformed;
|
||||
|
||||
return std::monostate{};
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Specific>
|
||||
static constexpr auto getVisitorFor (std::in_place_type_t<Specific>)
|
||||
{
|
||||
return [] (const Message::Parsed& parsed, const MessageVisitor& visitor)
|
||||
{
|
||||
if (auto* body = std::get_if<Specific> (&parsed.body))
|
||||
visitor.visit (*body);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
struct LookupTables
|
||||
{
|
||||
constexpr LookupTables()
|
||||
{
|
||||
for (auto& x : parsers)
|
||||
{
|
||||
x = [] (Message::Generic, Parser::Status* status) -> Message::Parsed::Body
|
||||
{
|
||||
if (status != nullptr)
|
||||
*status = Parser::Status::unrecognisedMessage;
|
||||
|
||||
return std::monostate{};
|
||||
};
|
||||
}
|
||||
|
||||
for (auto& x : visitors)
|
||||
{
|
||||
x = [] (const Message::Parsed&, const MessageVisitor& visitor)
|
||||
{
|
||||
visitor.visit (std::monostate{});
|
||||
};
|
||||
}
|
||||
|
||||
(registerTag (std::in_place_type<Ts>), ...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr void registerTag (std::in_place_type_t<T> tag)
|
||||
{
|
||||
constexpr auto category = MessageMeta::Meta<T>::subID2;
|
||||
parsers[uint8_t (category)] = getParserFor (tag);
|
||||
visitors[uint8_t (category)] = getVisitorFor (tag);
|
||||
}
|
||||
|
||||
ParseFn parsers[std::numeric_limits<uint8_t>::max()]{};
|
||||
VisitFn visitors[std::numeric_limits<uint8_t>::max()]{};
|
||||
};
|
||||
|
||||
template <typename Body>
|
||||
static void send (BufferOutput& output, uint8_t group, const Message::Header& header, const Body& body)
|
||||
{
|
||||
output.getOutputBuffer().clear();
|
||||
Marshalling::Writer { output.getOutputBuffer() } (header, body);
|
||||
output.send (group);
|
||||
}
|
||||
|
||||
template <typename Body>
|
||||
static void send (BufferOutput& output, uint8_t group, MUID targetMuid, ChannelInGroup cig, const Body& body)
|
||||
{
|
||||
Message::Header header
|
||||
{
|
||||
cig,
|
||||
MessageMeta::Meta<Body>::subID2,
|
||||
MessageMeta::implementationVersion,
|
||||
output.getMuid(),
|
||||
targetMuid,
|
||||
};
|
||||
|
||||
send (output, group, header, body);
|
||||
}
|
||||
|
||||
|
||||
template <typename Body>
|
||||
static void send (ResponderOutput& output, const Body& body)
|
||||
{
|
||||
send (output, output.getIncomingGroup(), output.getReplyHeader (MessageMeta::Meta<Body>::subID2), body);
|
||||
}
|
||||
|
||||
static void sendNAK (ResponderOutput& output, std::byte statusCode)
|
||||
{
|
||||
const auto header = output.getReplyHeader (MessageMeta::Meta<Message::NAK>::subID2);
|
||||
const Message::NAK body { output.getIncomingHeader().category,
|
||||
statusCode,
|
||||
std::byte { 0x00 },
|
||||
{}, // No additional details
|
||||
{} }; // No message text
|
||||
send (output, output.getIncomingGroup(), header, body);
|
||||
}
|
||||
|
||||
class BaseCaseDelegate : public ResponderDelegate
|
||||
{
|
||||
public:
|
||||
bool tryRespond (ResponderOutput& output, const Message::Parsed&) override
|
||||
{
|
||||
sendNAK (output, {});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto getTables()
|
||||
{
|
||||
return LookupTables<Message::Discovery,
|
||||
Message::DiscoveryResponse,
|
||||
Message::InvalidateMUID,
|
||||
Message::EndpointInquiry,
|
||||
Message::EndpointInquiryResponse,
|
||||
Message::ACK,
|
||||
Message::NAK,
|
||||
Message::ProfileInquiry,
|
||||
Message::ProfileInquiryResponse,
|
||||
Message::ProfileAdded,
|
||||
Message::ProfileRemoved,
|
||||
Message::ProfileDetails,
|
||||
Message::ProfileDetailsResponse,
|
||||
Message::ProfileOn,
|
||||
Message::ProfileOff,
|
||||
Message::ProfileEnabledReport,
|
||||
Message::ProfileDisabledReport,
|
||||
Message::ProfileSpecificData,
|
||||
Message::PropertyExchangeCapabilities,
|
||||
Message::PropertyExchangeCapabilitiesResponse,
|
||||
Message::PropertyGetData,
|
||||
Message::PropertyGetDataResponse,
|
||||
Message::PropertySetData,
|
||||
Message::PropertySetDataResponse,
|
||||
Message::PropertySubscribe,
|
||||
Message::PropertySubscribeResponse,
|
||||
Message::PropertyNotify,
|
||||
Message::ProcessInquiry,
|
||||
Message::ProcessInquiryResponse,
|
||||
Message::ProcessMidiMessageReport,
|
||||
Message::ProcessMidiMessageReportResponse,
|
||||
Message::ProcessEndMidiMessageReport>{};
|
||||
|
||||
}
|
||||
|
||||
static void visit (const Message::Parsed& msg, const MessageVisitor& visitor)
|
||||
{
|
||||
constexpr auto tables = getTables();
|
||||
const auto fn = tables.visitors[(uint8_t) msg.header.category];
|
||||
fn (msg, visitor);
|
||||
}
|
||||
|
||||
} // namespace juce::midi_ci::detail::MessageTypeUtils
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::midi_ci::detail
|
||||
{
|
||||
|
||||
PropertyDataMessageChunker::PropertyDataMessageChunker (std::vector<std::byte>& storageIn,
|
||||
int chunkSizeIn,
|
||||
const std::byte messageKindIn,
|
||||
const std::byte requestIdIn,
|
||||
Span<const std::byte> headerIn,
|
||||
MUID sourceIn,
|
||||
MUID destIn,
|
||||
InputStream& bodyIn)
|
||||
: header (headerIn),
|
||||
storage (&storageIn),
|
||||
body (&bodyIn),
|
||||
source (sourceIn),
|
||||
dest (destIn),
|
||||
chunkSize (chunkSizeIn),
|
||||
messageKind (messageKindIn),
|
||||
requestId (requestIdIn)
|
||||
{
|
||||
if (hasRoomForBody())
|
||||
{
|
||||
populateStorage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Header too large! There's no way to fit this message into the requested chunk size.
|
||||
jassertfalse;
|
||||
*this = PropertyDataMessageChunker();
|
||||
}
|
||||
}
|
||||
|
||||
PropertyDataMessageChunker& PropertyDataMessageChunker::operator++() noexcept
|
||||
{
|
||||
if (*this != PropertyDataMessageChunker())
|
||||
{
|
||||
if (body->isExhausted())
|
||||
{
|
||||
*this = PropertyDataMessageChunker();
|
||||
}
|
||||
else
|
||||
{
|
||||
++thisChunk;
|
||||
populateStorage();
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Span<const std::byte> PropertyDataMessageChunker::operator*() const noexcept
|
||||
{
|
||||
// The end of the stream was reached, no point dereferencing the iterator now!
|
||||
jassert (storage != nullptr && (int) storage->size() <= chunkSize);
|
||||
return *storage;
|
||||
}
|
||||
|
||||
Span<const std::byte> PropertyDataMessageChunker::getHeaderForBlock() const
|
||||
{
|
||||
return thisChunk == 1 ? header : Span<const std::byte>{};
|
||||
}
|
||||
|
||||
int PropertyDataMessageChunker::getRoomForBody() const
|
||||
{
|
||||
return chunkSize - (int) (getHeaderForBlock().size() + 22);
|
||||
}
|
||||
|
||||
bool PropertyDataMessageChunker::hasRoomForBody() const
|
||||
{
|
||||
const auto bodyRoom = getRoomForBody();
|
||||
return (0 < bodyRoom)
|
||||
|| (0 == bodyRoom && body->getNumBytesRemaining() == 0);
|
||||
}
|
||||
|
||||
void PropertyDataMessageChunker::populateStorage() const
|
||||
{
|
||||
storage->clear();
|
||||
storage->resize ((size_t) getRoomForBody());
|
||||
|
||||
// Read body data into buffer
|
||||
const auto numBytesRead = (uint16_t) jmax (ssize_t (0), body->read (storage->data(), storage->size()));
|
||||
|
||||
const auto [numChunks, thisChunkNum] = [&]() -> std::tuple<uint16_t, uint16_t>
|
||||
{
|
||||
if (body->isExhausted() || body->getNumBytesRemaining() == 0)
|
||||
return std::tuple (thisChunk, thisChunk);
|
||||
|
||||
const auto totalLength = body->getTotalLength();
|
||||
|
||||
if (totalLength < 0)
|
||||
return std::tuple ((uint16_t) 0, thisChunk); // 0 means "unknown number"
|
||||
|
||||
const auto roomForBody = getRoomForBody();
|
||||
|
||||
if (roomForBody != 0)
|
||||
return std::tuple ((uint16_t) ((totalLength + roomForBody - 1) / roomForBody), thisChunk);
|
||||
|
||||
// During construction, the input stream reported that it had no data remaining, so no
|
||||
// space was reserved for body content.
|
||||
// Now, the input stream reports that it has data remaining, but there's nowhere
|
||||
// to fit it in the message!
|
||||
jassertfalse;
|
||||
return std::tuple (thisChunk, (uint16_t) 0); // 0 means "data potentially unusable"
|
||||
}();
|
||||
|
||||
// Now we know how many bytes we managed to read, write the header at the end of the buffer
|
||||
const auto headerForBlock = getHeaderForBlock();
|
||||
detail::Marshalling::Writer writer { *storage };
|
||||
writer (Message::Header { ChannelInGroup::wholeBlock,
|
||||
messageKind,
|
||||
detail::MessageMeta::implementationVersion,
|
||||
source,
|
||||
dest },
|
||||
requestId,
|
||||
detail::MessageMeta::makeSpanWithSizeBytes<2> (headerForBlock),
|
||||
numChunks,
|
||||
thisChunkNum,
|
||||
numBytesRead);
|
||||
|
||||
// Finally, swap the header to the beginning of the buffer
|
||||
std::rotate (storage->begin(), storage->begin() + getRoomForBody(), storage->end());
|
||||
|
||||
// ...and bring the storage buffer down to size, if we didn't manage to fill it
|
||||
const auto room = (size_t) getRoomForBody();
|
||||
storage->resize (storage->size() + numBytesRead - room);
|
||||
}
|
||||
|
||||
} // namespace juce::midi_ci::detail
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::midi_ci::detail
|
||||
{
|
||||
|
||||
/*
|
||||
Breaks up a large property exchange message into chunks of the requested size.
|
||||
|
||||
Note that the header *must* fit inside the first block, so you must ensure
|
||||
that the header is small enough to fit inside the requested chunk size.
|
||||
*/
|
||||
class PropertyDataMessageChunker
|
||||
{
|
||||
auto tie() const { return std::tie (storage, body, source, dest, chunkSize, messageKind, requestId); }
|
||||
|
||||
public:
|
||||
/* Constructs a chunker instance.
|
||||
|
||||
@param storageIn backing storage where each chunk will be written
|
||||
@param chunkSizeIn the maximum size of each chunk
|
||||
@param messageKindIn the subID2 byte identifying the type of message in each chunk
|
||||
@param requestIdIn the id that should be included in all messages that are part of the same property exchange transaction
|
||||
@param headerIn the header bytes of the message. This is always JSON encoded as 7-bit ASCII text, see the MIDI-CI spec for full details
|
||||
@param sourceIn the MUID of the device sending the chunked messages
|
||||
@param destIn the MUID of the recipient of the chunked messages
|
||||
@param bodyIn a stream that can supply the data payload for this chunk sequence. All payload bytes *must* be 7-bit (MSB not set).
|
||||
*/
|
||||
PropertyDataMessageChunker (std::vector<std::byte>& storageIn,
|
||||
int chunkSizeIn,
|
||||
const std::byte messageKindIn,
|
||||
const std::byte requestIdIn,
|
||||
Span<const std::byte> headerIn,
|
||||
MUID sourceIn,
|
||||
MUID destIn,
|
||||
InputStream& bodyIn);
|
||||
|
||||
/* Returns true if this chunker hasn't finished producing chunks. */
|
||||
explicit operator bool() const { return *this != PropertyDataMessageChunker(); }
|
||||
|
||||
/* Allowing foreach usage. */
|
||||
auto begin() const { return *this; }
|
||||
|
||||
/* Allow foreach usage. */
|
||||
auto end() const { return PropertyDataMessageChunker{}; }
|
||||
|
||||
/* Writes the bytes of the next chunk, if any, into the storage buffer. */
|
||||
PropertyDataMessageChunker& operator++() noexcept;
|
||||
|
||||
/* Checks whether the state of this chunker matches the state of another chunker, enabling foreach usage. */
|
||||
bool operator== (const PropertyDataMessageChunker& other) const noexcept { return tie() == other.tie(); }
|
||||
bool operator!= (const PropertyDataMessageChunker& other) const noexcept { return tie() != other.tie(); }
|
||||
|
||||
/* Returns a span over the valid bytes in the output buffer. */
|
||||
Span<const std::byte> operator*() const noexcept;
|
||||
|
||||
private:
|
||||
PropertyDataMessageChunker() = default;
|
||||
|
||||
Span<const std::byte> getHeaderForBlock() const;
|
||||
int getRoomForBody() const;
|
||||
bool hasRoomForBody() const;
|
||||
void populateStorage() const;
|
||||
|
||||
Span<const std::byte> header;
|
||||
std::vector<std::byte>* storage{};
|
||||
InputStream* body{};
|
||||
MUID source = MUID::makeUnchecked (0), dest = MUID::makeUnchecked (0);
|
||||
int chunkSize{};
|
||||
uint16_t thisChunk { 0x01 };
|
||||
std::byte messageKind{};
|
||||
std::byte requestId{};
|
||||
};
|
||||
|
||||
} // namespace juce::midi_ci::detail
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::midi_ci::detail
|
||||
{
|
||||
|
||||
struct PropertyHostUtils
|
||||
{
|
||||
PropertyHostUtils() = delete;
|
||||
|
||||
static void send (BufferOutput& output,
|
||||
uint8_t group,
|
||||
std::byte subID2,
|
||||
MUID targetMuid,
|
||||
std::byte requestID,
|
||||
Span<const std::byte> header,
|
||||
Span<const std::byte> body,
|
||||
int chunkSize)
|
||||
{
|
||||
MemoryInputStream stream (body.data(), body.size(), false);
|
||||
const detail::PropertyDataMessageChunker chunker { output.getOutputBuffer(),
|
||||
std::min (chunkSize, 1 << 16),
|
||||
subID2,
|
||||
requestID,
|
||||
header,
|
||||
output.getMuid(),
|
||||
targetMuid,
|
||||
stream };
|
||||
|
||||
std::for_each (chunker.begin(), chunker.end(), [&] (auto) { output.send (group); });
|
||||
}
|
||||
};
|
||||
} // namespace juce::midi_ci::detail
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
|
||||
namespace juce::midi_ci::detail
|
||||
{
|
||||
|
||||
Parser::Status Responder::processCompleteMessage (BufferOutput& output,
|
||||
ump::BytesOnGroup message,
|
||||
Span<ResponderDelegate* const> listeners)
|
||||
{
|
||||
auto status = Parser::Status::noError;
|
||||
const auto parsed = Parser::parse (output.getMuid(), message.bytes, &status);
|
||||
|
||||
if (! parsed.has_value())
|
||||
return Parser::Status::malformed;
|
||||
|
||||
class Output : public ResponderOutput
|
||||
{
|
||||
public:
|
||||
Output (BufferOutput& o, Message::Header h, uint8_t g)
|
||||
: innerOutput (o), header (h), group (g) {}
|
||||
|
||||
MUID getMuid() const override { return innerOutput.getMuid(); }
|
||||
Message::Header getIncomingHeader() const override { return header; }
|
||||
uint8_t getIncomingGroup() const override { return group; }
|
||||
std::vector<std::byte>& getOutputBuffer() override { return innerOutput.getOutputBuffer(); }
|
||||
void send (uint8_t g) override { innerOutput.send (g); }
|
||||
|
||||
private:
|
||||
BufferOutput& innerOutput;
|
||||
Message::Header header;
|
||||
uint8_t group{};
|
||||
};
|
||||
|
||||
Output responderOutput { output, parsed->header, message.group };
|
||||
|
||||
if (status != Parser::Status::noError)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Parser::Status::collidingMUID:
|
||||
{
|
||||
const Message::Header header { ChannelInGroup::wholeBlock,
|
||||
MessageMeta::Meta<Message::InvalidateMUID>::subID2,
|
||||
MessageMeta::implementationVersion,
|
||||
output.getMuid(),
|
||||
MUID::getBroadcast() };
|
||||
const Message::InvalidateMUID body { output.getMuid() };
|
||||
MessageTypeUtils::send (responderOutput, responderOutput.getIncomingGroup(), header, body);
|
||||
break;
|
||||
}
|
||||
|
||||
case Parser::Status::unrecognisedMessage:
|
||||
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x01 });
|
||||
break;
|
||||
|
||||
case Parser::Status::reservedVersion:
|
||||
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x02 });
|
||||
break;
|
||||
|
||||
case Parser::Status::malformed:
|
||||
MessageTypeUtils::sendNAK (responderOutput, std::byte { 0x41 });
|
||||
break;
|
||||
|
||||
case Parser::Status::mismatchedMUID:
|
||||
case Parser::Status::noError:
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
for (auto* listener : listeners)
|
||||
if (listener != nullptr && listener->tryRespond (responderOutput, *parsed))
|
||||
return Parser::Status::noError;
|
||||
|
||||
MessageTypeUtils::BaseCaseDelegate base;
|
||||
|
||||
if (base.tryRespond (responderOutput, *parsed))
|
||||
return Parser::Status::noError;
|
||||
|
||||
return Parser::Status::unrecognisedMessage;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class ResponderTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
ResponderTests() : UnitTest ("Responder", UnitTestCategories::midi) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
auto random = getRandom();
|
||||
std::vector<std::byte> outgoing;
|
||||
|
||||
const auto makeOutput = [&]
|
||||
{
|
||||
struct Output : public BufferOutput
|
||||
{
|
||||
Output (Random& r, std::vector<std::byte>& b)
|
||||
: muid (MUID::makeRandom (r)), buf (b) {}
|
||||
|
||||
MUID getMuid() const override { return muid; }
|
||||
std::vector<std::byte>& getOutputBuffer() override { return buf; }
|
||||
void send (uint8_t) override { sent.push_back (buf); }
|
||||
|
||||
MUID muid;
|
||||
std::vector<std::byte>& buf;
|
||||
std::vector<std::vector<std::byte>> sent;
|
||||
};
|
||||
|
||||
return Output { random, outgoing };
|
||||
};
|
||||
|
||||
beginTest ("An endpoint message with a matching MUID provokes an endpoint response");
|
||||
{
|
||||
constexpr auto version = MessageMeta::implementationVersion;
|
||||
|
||||
auto output = makeOutput();
|
||||
const auto initialMUID = output.getMuid();
|
||||
|
||||
const auto bytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* endpoint message */ 0x72,
|
||||
/* version */ version,
|
||||
/* source MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
|
||||
/* status, product instance ID */ 0x00);
|
||||
|
||||
const Message::Parsed expectedInput { Message::Header { ChannelInGroup::wholeBlock,
|
||||
std::byte { 0x72 },
|
||||
version,
|
||||
MUID::makeUnchecked (0x80c101),
|
||||
initialMUID },
|
||||
Message::EndpointInquiry { std::byte { 0x00 } } };
|
||||
EndpointResponderListener listener;
|
||||
processCompleteMessage (output, { 0, bytes }, listener);
|
||||
expect (listener == SilentResponderListener (expectedInput));
|
||||
|
||||
const auto expectedOutputBytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* endpoint reply */ 0x73,
|
||||
/* version */ version,
|
||||
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
|
||||
/* destination MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* status */ 0x00,
|
||||
/* 16-bit length of following data */ 0x04,
|
||||
/* ... */ 0x00,
|
||||
/* info */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00);
|
||||
|
||||
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
|
||||
}
|
||||
|
||||
beginTest ("An endpoint message directed at a different MUID does not provoke a response");
|
||||
{
|
||||
const auto destMUID = MUID::makeRandom (random);
|
||||
constexpr auto version = MessageMeta::implementationVersion;
|
||||
|
||||
const auto bytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* endpoint message */ 0x72,
|
||||
/* version */ version,
|
||||
/* source MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* destination MUID */ (destMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (destMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (destMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (destMUID.get() >> 0x15) & 0x7f,
|
||||
/* status, product instance ID */ 0x00);
|
||||
|
||||
auto output = makeOutput();
|
||||
EndpointResponderListener listener;
|
||||
processCompleteMessage (output, { 0, bytes }, listener);
|
||||
expect (listener == SilentResponderListener());
|
||||
expect (output.sent.empty());
|
||||
}
|
||||
|
||||
beginTest ("If the listener fails to compose an endpoint response, a NAK is emitted");
|
||||
{
|
||||
auto output = makeOutput();
|
||||
const auto initialMUID = output.getMuid();
|
||||
|
||||
SilentResponderListener listener;
|
||||
constexpr auto version = MessageMeta::implementationVersion;
|
||||
|
||||
const auto bytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* endpoint message */ 0x72,
|
||||
/* version */ version,
|
||||
/* source MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
|
||||
/* status, product instance ID */ 0x00);
|
||||
|
||||
processCompleteMessage (output, { 0, bytes }, listener);
|
||||
|
||||
const auto expectedOutputBytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* nak */ 0x7f,
|
||||
/* version */ version,
|
||||
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
|
||||
/* destination MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* original transaction sub-id #2 */ 0x72,
|
||||
/* nak status code */ 0x00,
|
||||
/* nak status data */ 0x00,
|
||||
/* details */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* message text length */ 0x00,
|
||||
/* ... */ 0x00);
|
||||
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
|
||||
}
|
||||
|
||||
beginTest ("If a message is sent with reserved bits set in the Message Format Version, a NAK is emitted");
|
||||
{
|
||||
auto output = makeOutput();
|
||||
const auto initialMUID = output.getMuid();
|
||||
|
||||
const auto bytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* endpoint message */ 0x72,
|
||||
/* version, reserved bit set */ 0x12,
|
||||
/* source MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* destination MUID */ (initialMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
|
||||
/* status, product instance ID */ 0x00);
|
||||
SilentResponderListener listener;
|
||||
processCompleteMessage (output, { 0, bytes }, listener);
|
||||
|
||||
expect (listener == SilentResponderListener{});
|
||||
|
||||
const auto expectedOutputBytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* nak */ 0x7f,
|
||||
/* version */ MessageMeta::implementationVersion,
|
||||
/* source MUID */ (initialMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (initialMUID.get() >> 0x15) & 0x7f,
|
||||
/* destination MUID */ 0x01,
|
||||
/* ... */ 0x02,
|
||||
/* ... */ 0x03,
|
||||
/* ... */ 0x04,
|
||||
/* original transaction sub-id #2 */ 0x72,
|
||||
/* nak status code */ 0x02,
|
||||
/* nak status data */ 0x00,
|
||||
/* details */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* message text length */ 0x00,
|
||||
/* ... */ 0x00);
|
||||
|
||||
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
|
||||
}
|
||||
|
||||
beginTest ("If the message body is malformed, a NAK with a status of 0x41 is emitted");
|
||||
{
|
||||
const auto sourceMUID = MUID::makeRandom (random);
|
||||
|
||||
Message::Header header;
|
||||
header.deviceID = ChannelInGroup::wholeBlock;
|
||||
header.category = std::byte { 0x7e };
|
||||
header.version = MessageMeta::implementationVersion;
|
||||
header.source = sourceMUID;
|
||||
header.destination = MUID::getBroadcast();
|
||||
|
||||
Message::InvalidateMUID invalidate;
|
||||
invalidate.target = MUID::makeRandom (random);
|
||||
|
||||
std::vector<std::byte> message;
|
||||
Marshalling::Writer { message } (header, invalidate);
|
||||
|
||||
// Remove a byte from the end of the message
|
||||
message.pop_back();
|
||||
|
||||
auto output = makeOutput();
|
||||
const auto ourMUID = output.getMuid();
|
||||
|
||||
SilentResponderListener listener;
|
||||
processCompleteMessage (output, { 0, message }, listener);
|
||||
|
||||
const auto expectedOutputBytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* nak */ 0x7f,
|
||||
/* version */ MessageMeta::implementationVersion,
|
||||
/* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (ourMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (ourMUID.get() >> 0x15) & 0x7f,
|
||||
/* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
|
||||
/* original transaction sub-id #2 */ 0x7e,
|
||||
/* nak status code */ 0x41,
|
||||
/* nak status data */ 0x00,
|
||||
/* details */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* message text length */ 0x00,
|
||||
/* ... */ 0x00);
|
||||
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
|
||||
}
|
||||
|
||||
beginTest ("If an unrecognised message is received, a NAK with a status of 0x01 is emitted");
|
||||
{
|
||||
const auto sourceMUID = MUID::makeRandom (random);
|
||||
|
||||
Message::Header header;
|
||||
header.deviceID = ChannelInGroup::wholeBlock;
|
||||
header.category = std::byte { 0x50 }; // reserved
|
||||
header.version = MessageMeta::implementationVersion;
|
||||
header.source = sourceMUID;
|
||||
header.destination = MUID::getBroadcast();
|
||||
|
||||
std::vector<std::byte> message;
|
||||
Marshalling::Writer { message } (header);
|
||||
message.emplace_back();
|
||||
|
||||
auto output = makeOutput();
|
||||
const auto ourMUID = output.getMuid();
|
||||
|
||||
SilentResponderListener listener;
|
||||
processCompleteMessage (output, { 0, message }, listener);
|
||||
|
||||
const auto expectedOutputBytes = makeByteArray (0x7e,
|
||||
/* to function block */ 0x7f,
|
||||
/* midi CI */ 0x0d,
|
||||
/* nak */ 0x7f,
|
||||
/* version */ MessageMeta::implementationVersion,
|
||||
/* source MUID */ (ourMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (ourMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (ourMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (ourMUID.get() >> 0x15) & 0x7f,
|
||||
/* destination MUID */ (sourceMUID.get() >> 0x00) & 0x7f,
|
||||
/* ... */ (sourceMUID.get() >> 0x07) & 0x7f,
|
||||
/* ... */ (sourceMUID.get() >> 0x0e) & 0x7f,
|
||||
/* ... */ (sourceMUID.get() >> 0x15) & 0x7f,
|
||||
/* original transaction sub-id #2 */ 0x50,
|
||||
/* nak status code */ 0x01,
|
||||
/* nak status data */ 0x00,
|
||||
/* details */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* ... */ 0x00,
|
||||
/* message text length */ 0x00,
|
||||
/* ... */ 0x00);
|
||||
expect (rangesEqual (output.sent.front(), expectedOutputBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename... Ts>
|
||||
static std::array<std::byte, sizeof... (Ts)> makeByteArray (Ts&&... ts)
|
||||
{
|
||||
jassert (((0 <= (int) ts && (int) ts <= std::numeric_limits<uint8_t>::max()) && ...));
|
||||
return { std::byte (ts)... };
|
||||
}
|
||||
|
||||
struct SilentResponderListener : public ResponderDelegate
|
||||
{
|
||||
SilentResponderListener() = default;
|
||||
explicit SilentResponderListener (const Message::Parsed& p) : parsed (p) {}
|
||||
|
||||
bool tryRespond (ResponderOutput&, const Message::Parsed& p) override
|
||||
{
|
||||
parsed = p;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returning false indicates that the message was not handled
|
||||
bool operator== (const SilentResponderListener& other) const { return parsed == other.parsed; }
|
||||
bool operator!= (const SilentResponderListener& other) const { return ! operator== (other); }
|
||||
|
||||
std::optional<Message::Parsed> parsed;
|
||||
};
|
||||
|
||||
struct EndpointResponderListener : public SilentResponderListener
|
||||
{
|
||||
bool tryRespond (ResponderOutput& output, const Message::Parsed& message) override
|
||||
{
|
||||
parsed = message;
|
||||
|
||||
if (std::holds_alternative<Message::EndpointInquiry> (message.body))
|
||||
{
|
||||
std::array<std::byte, 4> data{};
|
||||
Message::EndpointInquiryResponse response;
|
||||
response.status = std::byte{};
|
||||
response.data = data;
|
||||
|
||||
MessageTypeUtils::send (output, output.getIncomingGroup(), output.getReplyHeader (std::byte { 0x73 }), response);
|
||||
return true;
|
||||
}
|
||||
|
||||
return SilentResponderListener::tryRespond (output, message);
|
||||
|
||||
}
|
||||
|
||||
using SilentResponderListener::operator==, SilentResponderListener::operator!=;
|
||||
};
|
||||
|
||||
struct OutputCallback
|
||||
{
|
||||
void operator() (Span<const std::byte> bytes)
|
||||
{
|
||||
output = std::vector<std::byte> (bytes.begin(), bytes.end());
|
||||
}
|
||||
|
||||
std::vector<std::byte> output;
|
||||
};
|
||||
|
||||
template <typename A, typename B>
|
||||
static bool rangesEqual (A&& a, B&& b)
|
||||
{
|
||||
using std::begin, std::end;
|
||||
return std::equal (begin (a), end (a), begin (b), end (b));
|
||||
}
|
||||
|
||||
|
||||
static Parser::Status processCompleteMessage (BufferOutput& output,
|
||||
ump::BytesOnGroup message,
|
||||
ResponderDelegate& listener)
|
||||
{
|
||||
ResponderDelegate* const listeners[] { &listener };
|
||||
return Responder::processCompleteMessage (output, message, listeners);
|
||||
}
|
||||
};
|
||||
|
||||
static ResponderTests responderTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce::midi_ci::detail
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::midi_ci::detail
|
||||
{
|
||||
|
||||
/*
|
||||
Parses individual messages, and additionally gives ResponderDelegates a chance to formulate
|
||||
a response to any message that would normally necessitate a reply.
|
||||
*/
|
||||
struct Responder
|
||||
{
|
||||
Responder() = delete;
|
||||
|
||||
/* Parses the message, then calls tryParse on each ResponderDelegate in
|
||||
turn until one returns true, indicating that the message has been
|
||||
handled. Most 'inquiry' messages should emit one or more reply messages.
|
||||
These replies will be written to the provided BufferOutput.
|
||||
If none of the provided delegates are able to handle the message, then
|
||||
a generic NAK will be written to the BufferOutput.
|
||||
*/
|
||||
static Parser::Status processCompleteMessage (BufferOutput& output,
|
||||
ump::BytesOnGroup message,
|
||||
Span<ResponderDelegate* const> delegates);
|
||||
};
|
||||
|
||||
} // namespace juce::midi_ci
|
||||
Reference in New Issue
Block a user