/* ============================================================================== 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 { //============================================================================== /** Byte values representing different addresses within a group. @tags{Audio} */ enum class ChannelInGroup : uint8_t { channel0 = 0x0, channel1 = 0x1, channel2 = 0x2, channel3 = 0x3, channel4 = 0x4, channel5 = 0x5, channel6 = 0x6, channel7 = 0x7, channel8 = 0x8, channel9 = 0x9, channelA = 0xA, channelB = 0xB, channelC = 0xC, channelD = 0xD, channelE = 0xE, channelF = 0xF, wholeGroup = 0x7e, ///< Refers to all channels in the UMP group wholeBlock = 0x7f, ///< Refers to all channels in the function block that contains the UMP group }; /** Utility functions for working with the ChannelInGroup enum. @tags{Audio} */ struct ChannelInGroupUtils { ChannelInGroupUtils() = delete; /** Converts a ChannelInGroup to a descriptive string. */ static String toString (ChannelInGroup c) { if (c == ChannelInGroup::wholeGroup) return "Group"; if (c == ChannelInGroup::wholeBlock) return "Function Block"; const auto underlying = (std::underlying_type_t) c; return "Channel " + String (underlying + 1); } }; using Profile = std::array; //============================================================================== /** Namespace containing structs representing different kinds of MIDI-CI message. @tags{Audio} */ namespace Message { /** Wraps a span, providing equality operators that compare the span contents elementwise. @tags{Audio} */ template struct ComparableRange { T& data; bool operator== (const ComparableRange& other) const { return std::equal (data.begin(), data.end(), other.data.begin(), other.data.end()); } bool operator!= (const ComparableRange& other) const { return ! operator== (other); } }; template static constexpr auto makeComparableRange ( T& t) { return ComparableRange< T> { t }; } template static constexpr auto makeComparableRange (const T& t) { return ComparableRange { t }; } //============================================================================== /** Holds fields that can be found at the beginning of every MIDI CI message. @tags{Audio} */ struct Header { ChannelInGroup deviceID{}; std::byte category{}; std::byte version{}; MUID source = MUID::makeUnchecked (0); MUID destination = MUID::makeUnchecked (0); auto tie() const { return std::tuple (deviceID, category, version, source, destination); } bool operator== (const Header& x) const { return tie() == x.tie(); } bool operator!= (const Header& x) const { return ! operator== (x); } }; /** Groups together a CI message header, and some number of trailing bytes. @tags{Audio} */ struct Generic { Header header; Span data; }; //============================================================================== /** See the MIDI-CI specification. @tags{Audio} */ struct DiscoveryResponse { ump::DeviceInfo device; std::byte capabilities{}; uint32_t maximumSysexSize{}; std::byte outputPathID{}; /**< Only valid if the message header specifies version 0x02 or greater. */ std::byte functionBlock{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (device, capabilities, maximumSysexSize, outputPathID, functionBlock); } bool operator== (const DiscoveryResponse& x) const { return tie() == x.tie(); } bool operator!= (const DiscoveryResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct Discovery { ump::DeviceInfo device; std::byte capabilities{}; uint32_t maximumSysexSize{}; std::byte outputPathID{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (device, capabilities, maximumSysexSize, outputPathID); } bool operator== (const Discovery& x) const { return tie() == x.tie(); } bool operator!= (const Discovery& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct EndpointInquiryResponse { std::byte status; Span data; auto tie() const { return std::tuple (status, makeComparableRange (data)); } bool operator== (const EndpointInquiryResponse& x) const { return tie() == x.tie(); } bool operator!= (const EndpointInquiryResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct EndpointInquiry { std::byte status; auto tie() const { return std::tuple (status); } bool operator== (const EndpointInquiry& x) const { return tie() == x.tie(); } bool operator!= (const EndpointInquiry& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct InvalidateMUID { MUID target = MUID::makeUnchecked (0); auto tie() const { return std::tuple (target); } bool operator== (const InvalidateMUID& x) const { return tie() == x.tie(); } bool operator!= (const InvalidateMUID& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ACK { std::byte originalCategory{}; std::byte statusCode{}; std::byte statusData{}; std::array details{}; Span messageText{}; /** Convenience function that returns the message's text as a String. */ String getMessageTextAsString() const { return Encodings::stringFrom7BitText (messageText); } auto tie() const { return std::tuple (originalCategory, statusCode, statusData, details, makeComparableRange (messageText)); } bool operator== (const ACK& x) const { return tie() == x.tie(); } bool operator!= (const ACK& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct NAK { std::byte originalCategory{}; /**< Only valid if the message header specifies version 0x02 or greater. */ std::byte statusCode{}; /**< Only valid if the message header specifies version 0x02 or greater. */ std::byte statusData{}; /**< Only valid if the message header specifies version 0x02 or greater. */ std::array details{}; /**< Only valid if the message header specifies version 0x02 or greater. */ Span messageText{}; /**< Only valid if the message header specifies version 0x02 or greater. */ /** Convenience function that returns the message's text as a String. */ String getMessageTextAsString() const { return Encodings::stringFrom7BitText (messageText); } auto tie() const { return std::tuple (originalCategory, statusCode, statusData, details, makeComparableRange (messageText)); } bool operator== (const NAK& x) const { return tie() == x.tie(); } bool operator!= (const NAK& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileInquiryResponse { Span enabledProfiles; Span disabledProfiles; auto tie() const { return std::tuple (makeComparableRange (enabledProfiles), makeComparableRange (disabledProfiles)); } bool operator== (const ProfileInquiryResponse& x) const { return tie() == x.tie(); } bool operator!= (const ProfileInquiryResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileInquiry { auto tie() const { return std::tuple<>(); } bool operator== (const ProfileInquiry& x) const { return tie() == x.tie(); } bool operator!= (const ProfileInquiry& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileAdded { Profile profile{}; auto tie() const { return std::tuple (profile); } bool operator== (const ProfileAdded& x) const { return tie() == x.tie(); } bool operator!= (const ProfileAdded& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileRemoved { Profile profile{}; auto tie() const { return std::tuple (profile); } bool operator== (const ProfileRemoved& x) const { return tie() == x.tie(); } bool operator!= (const ProfileRemoved& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileDetailsResponse { Profile profile{}; std::byte target{}; Span data; auto tie() const { return std::tuple (profile, target, makeComparableRange (data)); } bool operator== (const ProfileDetailsResponse& x) const { return tie() == x.tie(); } bool operator!= (const ProfileDetailsResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileDetails { Profile profile{}; std::byte target{}; auto tie() const { return std::tuple (profile, target); } bool operator== (const ProfileDetails& x) const { return tie() == x.tie(); } bool operator!= (const ProfileDetails& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileOn { Profile profile{}; uint16_t numChannels{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (profile, numChannels); } bool operator== (const ProfileOn& x) const { return tie() == x.tie(); } bool operator!= (const ProfileOn& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileOff { Profile profile{}; auto tie() const { return std::tuple (profile); } bool operator== (const ProfileOff& x) const { return tie() == x.tie(); } bool operator!= (const ProfileOff& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileEnabledReport { Profile profile{}; uint16_t numChannels{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (profile, numChannels); } bool operator== (const ProfileEnabledReport& x) const { return tie() == x.tie(); } bool operator!= (const ProfileEnabledReport& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileDisabledReport { Profile profile{}; uint16_t numChannels{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (profile, numChannels); } bool operator== (const ProfileDisabledReport& x) const { return tie() == x.tie(); } bool operator!= (const ProfileDisabledReport& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProfileSpecificData { Profile profile{}; Span data; auto tie() const { return std::tuple (profile, makeComparableRange (data)); } bool operator== (const ProfileSpecificData& x) const { return tie() == x.tie(); } bool operator!= (const ProfileSpecificData& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertyExchangeCapabilitiesResponse { std::byte numSimultaneousRequestsSupported{}; std::byte majorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */ std::byte minorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (numSimultaneousRequestsSupported, majorVersion, minorVersion); } bool operator== (const PropertyExchangeCapabilitiesResponse& x) const { return tie() == x.tie(); } bool operator!= (const PropertyExchangeCapabilitiesResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertyExchangeCapabilities { std::byte numSimultaneousRequestsSupported{}; std::byte majorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */ std::byte minorVersion{}; /**< Only valid if the message header specifies version 0x02 or greater. */ auto tie() const { return std::tuple (numSimultaneousRequestsSupported, majorVersion, minorVersion); } bool operator== (const PropertyExchangeCapabilities& x) const { return tie() == x.tie(); } bool operator!= (const PropertyExchangeCapabilities& x) const { return ! operator== (x); } }; /** A property-exchange message that has no payload, and must therefore be contained in a single chunk. @tags{Audio} */ struct StaticSizePropertyExchange { std::byte requestID{}; Span header; auto tie() const { return std::tuple (requestID, makeComparableRange (header)); } }; /** A property-exchange message that may form part of a multi-chunk message sequence. @tags{Audio} */ struct DynamicSizePropertyExchange { std::byte requestID{}; Span header; uint16_t totalNumChunks{}; uint16_t thisChunkNum{}; Span data; auto tie() const { return std::tuple (requestID, makeComparableRange (header), totalNumChunks, thisChunkNum, makeComparableRange (data)); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertyGetDataResponse : public DynamicSizePropertyExchange { bool operator== (const PropertyGetDataResponse& x) const { return tie() == x.tie(); } bool operator!= (const PropertyGetDataResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertyGetData : public StaticSizePropertyExchange { bool operator== (const PropertyGetData& x) const { return tie() == x.tie(); } bool operator!= (const PropertyGetData& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertySetDataResponse : public StaticSizePropertyExchange { bool operator== (const PropertySetDataResponse& x) const { return tie() == x.tie(); } bool operator!= (const PropertySetDataResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertySetData : public DynamicSizePropertyExchange { bool operator== (const PropertySetData& x) const { return tie() == x.tie(); } bool operator!= (const PropertySetData& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertySubscribeResponse : public DynamicSizePropertyExchange { bool operator== (const PropertySubscribeResponse& x) const { return tie() == x.tie(); } bool operator!= (const PropertySubscribeResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertySubscribe : public DynamicSizePropertyExchange { bool operator== (const PropertySubscribe& x) const { return tie() == x.tie(); } bool operator!= (const PropertySubscribe& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct PropertyNotify : public DynamicSizePropertyExchange { bool operator== (const PropertyNotify& x) const { return tie() == x.tie(); } bool operator!= (const PropertyNotify& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProcessInquiryResponse { std::byte supportedFeatures{}; auto tie() const { return std::tuple (supportedFeatures); } bool operator== (const ProcessInquiryResponse& x) const { return tie() == x.tie(); } bool operator!= (const ProcessInquiryResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProcessInquiry { auto tie() const { return std::tuple<>(); } bool operator== (const ProcessInquiry& x) const { return tie() == x.tie(); } bool operator!= (const ProcessInquiry& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProcessMidiMessageReportResponse { std::byte messageDataControl{}; std::byte requestedMessages{}; std::byte channelControllerMessages{}; std::byte noteDataMessages{}; auto tie() const { return std::tuple (messageDataControl, requestedMessages, channelControllerMessages, noteDataMessages); } bool operator== (const ProcessMidiMessageReportResponse& x) const { return tie() == x.tie(); } bool operator!= (const ProcessMidiMessageReportResponse& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProcessMidiMessageReport { std::byte messageDataControl{}; std::byte requestedMessages{}; std::byte channelControllerMessages{}; std::byte noteDataMessages{}; auto tie() const { return std::tuple (messageDataControl, requestedMessages, channelControllerMessages, noteDataMessages); } bool operator== (const ProcessMidiMessageReport& x) const { return tie() == x.tie(); } bool operator!= (const ProcessMidiMessageReport& x) const { return ! operator== (x); } }; /** See the MIDI-CI specification. @tags{Audio} */ struct ProcessEndMidiMessageReport { auto tie() const { return std::tuple<>(); } bool operator== (const ProcessEndMidiMessageReport& x) const { return tie() == x.tie(); } bool operator!= (const ProcessEndMidiMessageReport& x) const { return ! operator== (x); } }; /** A message with a header and optional body. The body may be set to std::monostate to indicate some kind of failure, such as a malformed incoming message. @tags{Audio} */ struct Parsed { using Body = std::variant; Header header; Body body; bool operator== (const Parsed& other) const { const auto tie = [] (const auto& x) { return std::tie (x.header, x.body); }; return tie (*this) == tie (other); } bool operator!= (const Parsed& other) const { return ! operator== (other); } }; } } // namespace juce::midi_ci