mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
MIDI: Add support for MIDI 2.0 I/O using Universal MIDI Packets
Includes support for communication with USB and Bluetooth devices, as well as virtual devices.
This commit is contained in:
parent
3636f2c666
commit
ba7593df26
65 changed files with 16609 additions and 5301 deletions
|
|
@ -36,7 +36,7 @@
|
|||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/** Represents a MIDI message that happened at a particular time.
|
||||
/** Represents a MIDI message on bytestream transport that happened at a particular time.
|
||||
|
||||
Unlike MidiMessage, BytestreamMidiView is non-owning.
|
||||
*/
|
||||
|
|
@ -63,15 +63,18 @@ struct BytestreamMidiView
|
|||
return MidiMessage (bytes.data(), (int) bytes.size(), timestamp);
|
||||
}
|
||||
|
||||
bool isSysEx() const
|
||||
MidiMessageMetadata getMidiMessageMetadata() const
|
||||
{
|
||||
return ! bytes.empty() && bytes.front() == std::byte { 0xf0 };
|
||||
return MidiMessageMetadata { reinterpret_cast<const uint8*> (bytes.data()),
|
||||
(int) bytes.size(),
|
||||
(int) timestamp };
|
||||
}
|
||||
|
||||
Span<const std::byte> bytes;
|
||||
double timestamp = 0.0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Functions to assist conversion of UMP messages to/from other formats,
|
||||
especially older 'bytestream' formatted MidiMessages.
|
||||
|
|
@ -80,19 +83,39 @@ struct BytestreamMidiView
|
|||
*/
|
||||
struct Conversion
|
||||
{
|
||||
/** Converts from a MIDI 1 bytestream to MIDI 1 on Universal MIDI Packets.
|
||||
|
||||
`callback` is a function which accepts a single View argument.
|
||||
/** Converts 7-bit data (the most significant bit of each byte must be unset) to a series of
|
||||
Universal MIDI Packets.
|
||||
*/
|
||||
template <typename PacketCallbackFunction>
|
||||
static void toMidi1 (const BytesOnGroup& m, PacketCallbackFunction&& callback)
|
||||
static void umpFrom7BitData (BytesOnGroup msg, PacketCallbackFunction&& callback)
|
||||
{
|
||||
const auto size = m.bytes.size();
|
||||
// If this is hit, non-7-bit data was supplied.
|
||||
// Maybe you forgot to trim the leading/trailing bytes that delimit a bytestream SysEx message.
|
||||
jassert (std::all_of (msg.bytes.begin(), msg.bytes.end(), [] (std::byte b) { return (b & std::byte { 0x80 }) == std::byte{}; }));
|
||||
|
||||
Factory::splitIntoPackets (msg.bytes, 6, [&] (SysEx7::Kind kind, Span<const std::byte> bytesThisTime)
|
||||
{
|
||||
const auto packet = Factory::Detail::makeSysEx (msg.group, kind, bytesThisTime);
|
||||
callback (View (packet.data()));
|
||||
});
|
||||
}
|
||||
|
||||
/** Converts from a MIDI 1 bytestream to MIDI 1 on Universal MIDI Packets.
|
||||
|
||||
@param bytes the bytes in a single well-formed bytestream MIDI message
|
||||
@param callback a function that accepts a single View argument. This may be called several
|
||||
times for each invocation of toMidi1 if the bytestream message converts
|
||||
to multiple Universal MIDI Packets.
|
||||
*/
|
||||
template <typename PacketCallbackFunction>
|
||||
static void toMidi1 (const BytesOnGroup& groupBytes, PacketCallbackFunction&& callback)
|
||||
{
|
||||
const auto size = groupBytes.bytes.size();
|
||||
|
||||
if (size <= 0)
|
||||
return;
|
||||
|
||||
const auto* data = m.bytes.data();
|
||||
const auto* data = groupBytes.bytes.data();
|
||||
const auto firstByte = data[0];
|
||||
|
||||
if (firstByte != std::byte { 0xf0 })
|
||||
|
|
@ -107,45 +130,19 @@ struct Conversion
|
|||
case 3: return 0xffffffff;
|
||||
}
|
||||
|
||||
// This function can only handle a single bytestream MIDI message at a time!
|
||||
jassertfalse;
|
||||
return 0x00000000;
|
||||
}();
|
||||
|
||||
const auto extraByte = ((((firstByte & std::byte { 0xf0 }) == std::byte { 0xf0 }) ? std::byte { 0x1 } : std::byte { 0x2 }) << 0x4);
|
||||
const PacketX1 packet { mask & Utils::bytesToWord (extraByte, data[0], data[1], data[2]) };
|
||||
const std::byte group { (uint8_t) (groupBytes.group & 0xf) };
|
||||
const PacketX1 packet { mask & Utils::bytesToWord (extraByte | group, data[0], data[1], data[2]) };
|
||||
callback (View (packet.data()));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto numSysExBytes = (ssize_t) (size - 2);
|
||||
const auto numMessages = SysEx7::getNumPacketsRequiredForDataSize ((uint32_t) numSysExBytes);
|
||||
auto* dataOffset = data + 1;
|
||||
|
||||
if (numMessages <= 1)
|
||||
{
|
||||
const auto packet = Factory::makeSysExIn1Packet (0, { dataOffset, (size_t) numSysExBytes });
|
||||
callback (View (packet.data()));
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr ssize_t byteIncrement = 6;
|
||||
|
||||
for (auto i = static_cast<ssize_t> (numSysExBytes); i > 0; i -= byteIncrement, dataOffset += byteIncrement)
|
||||
{
|
||||
const auto func = [&]
|
||||
{
|
||||
if (i == numSysExBytes)
|
||||
return Factory::makeSysExStart;
|
||||
|
||||
if (i <= byteIncrement)
|
||||
return Factory::makeSysExEnd;
|
||||
|
||||
return Factory::makeSysExContinue;
|
||||
}();
|
||||
|
||||
const auto bytesNow = std::min (byteIncrement, i);
|
||||
const auto packet = func (0, { dataOffset, (size_t) bytesNow });
|
||||
callback (View (packet.data()));
|
||||
}
|
||||
umpFrom7BitData ({ groupBytes.group, Span (data + 1, size - 2) }, std::forward<PacketCallbackFunction> (callback));
|
||||
}
|
||||
|
||||
/** Widens a 7-bit MIDI 1.0 value to a 8-bit MIDI 2.0 value. */
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ namespace juce::universal_midi_packets
|
|||
*/
|
||||
struct DeviceInfo
|
||||
{
|
||||
std::array<std::byte, 3> manufacturer; ///< LSB first
|
||||
std::array<std::byte, 3> manufacturer;
|
||||
std::array<std::byte, 2> family; ///< LSB first
|
||||
std::array<std::byte, 2> modelNumber; ///< LSB first
|
||||
std::array<std::byte, 4> revision;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,216 @@
|
|||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/**
|
||||
Holds the data from a stream configuration notification message, with strong types.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class StreamConfiguration
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] StreamConfiguration withProtocol (PacketProtocol p) const { return withFlag (isMidi2, p == PacketProtocol::MIDI_2_0); }
|
||||
[[nodiscard]] StreamConfiguration withTransmitTimestamp (bool b) const { return withFlag (transmitTimestamp, b); }
|
||||
[[nodiscard]] StreamConfiguration withReceiveTimestamp (bool b) const { return withFlag (receiveTimestamp, b); }
|
||||
|
||||
/** The protocol in use by the endpoint. This protocol will be used for sending and receiving messages. */
|
||||
[[nodiscard]] PacketProtocol getProtocol() const { return getFlag (isMidi2) ? PacketProtocol::MIDI_2_0 : PacketProtocol::MIDI_1_0; }
|
||||
/** True if this endpoint intends to send JR timestamps. */
|
||||
[[nodiscard]] bool getTransmitTimestamp() const { return getFlag (transmitTimestamp); }
|
||||
/** True if this endpoint expects to receive JR timestamps. */
|
||||
[[nodiscard]] bool getReceiveTimestamp() const { return getFlag (receiveTimestamp); }
|
||||
|
||||
bool operator== (const StreamConfiguration& other) const { return options == other.options; }
|
||||
bool operator!= (const StreamConfiguration& other) const { return options != other.options; }
|
||||
|
||||
private:
|
||||
enum Flags
|
||||
{
|
||||
isMidi2 = 1 << 0,
|
||||
transmitTimestamp = 1 << 1,
|
||||
receiveTimestamp = 1 << 2,
|
||||
};
|
||||
|
||||
StreamConfiguration withFlag (Flags f, bool value) const
|
||||
{
|
||||
return withMember (*this, &StreamConfiguration::options, value ? (options | f) : (options & ~f));
|
||||
}
|
||||
|
||||
bool getFlag (Flags f) const
|
||||
{
|
||||
return (options & f) != 0;
|
||||
}
|
||||
|
||||
int options = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
Holds the data from an endpoint info notification message, with strong types.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class EndpointInfo
|
||||
{
|
||||
auto tie() const { return std::tie (versionMajor, versionMinor, numFunctionBlocks, flags); }
|
||||
|
||||
public:
|
||||
[[nodiscard]] EndpointInfo withVersion (uint8_t major, uint8_t minor) const
|
||||
{
|
||||
return withMember (withMember (*this, &EndpointInfo::versionMinor, minor), &EndpointInfo::versionMajor, major);
|
||||
}
|
||||
|
||||
[[nodiscard]] EndpointInfo withNumFunctionBlocks (uint8_t x) const
|
||||
{
|
||||
return withMember (*this, &EndpointInfo::numFunctionBlocks, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] EndpointInfo withStaticFunctionBlocks (bool b) const { return withFlag (staticFunctionBlocks, b); }
|
||||
[[nodiscard]] EndpointInfo withMidi1Support (bool b) const { return withFlag (supportsMidi1, b); }
|
||||
[[nodiscard]] EndpointInfo withMidi2Support (bool b) const { return withFlag (supportsMidi2, b); }
|
||||
[[nodiscard]] EndpointInfo withReceiveJRSupport (bool b) const { return withFlag (supportsReceiveJR, b); }
|
||||
[[nodiscard]] EndpointInfo withTransmitJRSupport (bool b) const { return withFlag (supportsTransmitJR, b); }
|
||||
|
||||
/** The major version byte. */
|
||||
[[nodiscard]] uint8_t getVersionMajor() const { return versionMajor; }
|
||||
/** The minor version byte. */
|
||||
[[nodiscard]] uint8_t getVersionMinor() const { return versionMinor; }
|
||||
/** The number of function blocks declared on this endpoint. */
|
||||
[[nodiscard]] uint8_t getNumFunctionBlocks() const { return numFunctionBlocks; }
|
||||
/** True if the function block configuration cannot change. */
|
||||
[[nodiscard]] bool hasStaticFunctionBlocks() const { return getFlag (staticFunctionBlocks); }
|
||||
/** True if this endpoint is capable of supporting the MIDI 1.0 protocol. */
|
||||
[[nodiscard]] bool hasMidi1Support() const { return getFlag (supportsMidi1); }
|
||||
/** True if this endpoint is capable of supporting the MIDI 2.0 protocol. */
|
||||
[[nodiscard]] bool hasMidi2Support() const { return getFlag (supportsMidi2); }
|
||||
/** True if this endpoint is capable of receiving JR timestamps. */
|
||||
[[nodiscard]] bool hasReceiveJRSupport() const { return getFlag (supportsReceiveJR); }
|
||||
/** True if this endpoint is capable of transmitting JR timestamps. */
|
||||
[[nodiscard]] bool hasTransmitJRSupport() const { return getFlag (supportsTransmitJR); }
|
||||
|
||||
bool operator== (const EndpointInfo& other) const { return tie() == other.tie(); }
|
||||
bool operator!= (const EndpointInfo& other) const { return tie() != other.tie(); }
|
||||
|
||||
private:
|
||||
enum Flags
|
||||
{
|
||||
staticFunctionBlocks = 1 << 0,
|
||||
supportsMidi1 = 1 << 1,
|
||||
supportsMidi2 = 1 << 2,
|
||||
supportsReceiveJR = 1 << 3,
|
||||
supportsTransmitJR = 1 << 4,
|
||||
};
|
||||
|
||||
EndpointInfo withFlag (Flags f, bool value) const
|
||||
{
|
||||
return withMember (*this, &EndpointInfo::flags, (uint8_t) (value ? (flags | f) : (flags & ~f)));
|
||||
}
|
||||
|
||||
bool getFlag (Flags f) const
|
||||
{
|
||||
return (flags & f) != 0;
|
||||
}
|
||||
|
||||
uint8_t versionMajor, versionMinor, numFunctionBlocks, flags;
|
||||
};
|
||||
|
||||
/** Directions that can apply to a Function Block or Group Terminal Block. */
|
||||
enum class BlockDirection : uint8_t
|
||||
{
|
||||
unknown = 0b00, ///< Block direction is unknown or undeclared
|
||||
receiver = 0b01, ///< Block is a receiver of messages
|
||||
sender = 0b10, ///< Block is a sender of messages
|
||||
bidirectional = 0b11, ///< Block both sends and receives messages
|
||||
};
|
||||
|
||||
/** UI hints that can apply to a Function Block or Group Terminal Block. */
|
||||
enum class BlockUiHint : uint8_t
|
||||
{
|
||||
unknown = 0b00, ///< Block direction is unknown or undeclared
|
||||
receiver = 0b01, ///< Block is a receiver of messages
|
||||
sender = 0b10, ///< Block is a sender of messages
|
||||
bidirectional = 0b11, ///< Block both sends and receives messages
|
||||
};
|
||||
|
||||
/** Describes how a MIDI 1.0 port maps to a given Block, if applicable. */
|
||||
enum class BlockMIDI1ProxyKind : uint8_t
|
||||
{
|
||||
inapplicable = 0b00, ///< Block does not represent a MIDI 1.0 port
|
||||
unrestrictedBandwidth = 0b01, ///< Block represents a MIDI 1.0 port and can handle high bandwidth
|
||||
restrictedBandwidth = 0b10, ///< Block represents a MIDI 1.0 port that requires restricted bandwidth
|
||||
};
|
||||
|
||||
/**
|
||||
Holds the data from a function block info notification message, with strong types.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class BlockInfo
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] BlockInfo withEnabled (bool x) const { return withMember (*this, &BlockInfo::enabled, x); }
|
||||
[[nodiscard]] BlockInfo withUiHint (BlockUiHint x) const { return withMember (*this, &BlockInfo::flags, replaceBits<4, 2> (flags, (uint8_t) x)); }
|
||||
[[nodiscard]] BlockInfo withMIDI1ProxyKind (BlockMIDI1ProxyKind x) const { return withMember (*this, &BlockInfo::flags, replaceBits<2, 2> (flags, (uint8_t) x)); }
|
||||
[[nodiscard]] BlockInfo withDirection (BlockDirection x) const { return withMember (*this, &BlockInfo::flags, replaceBits<0, 2> (flags, (uint8_t) x)); }
|
||||
[[nodiscard]] BlockInfo withFirstGroup (uint8_t x) const { return withMember (*this, &BlockInfo::firstGroup, x); }
|
||||
[[nodiscard]] BlockInfo withNumGroups (uint8_t x) const { return withMember (*this, &BlockInfo::numGroups, x); }
|
||||
[[nodiscard]] BlockInfo withCiVersion (uint8_t x) const { return withMember (*this, &BlockInfo::ciVersion, x); }
|
||||
[[nodiscard]] BlockInfo withMaxSysex8Streams (uint8_t x) const { return withMember (*this, &BlockInfo::numSysex8Streams, x); }
|
||||
|
||||
/** True if the block is enabled/active, false otherwise. */
|
||||
bool isEnabled() const { return enabled; }
|
||||
/** The directionality of the block, for display to the user. */
|
||||
BlockUiHint getUiHint() const { return (BlockUiHint) getBits<4, 2> (flags); }
|
||||
/** The kind of MIDI 1.0 proxy represented by this block, if any. */
|
||||
BlockMIDI1ProxyKind getMIDI1ProxyKind() const { return (BlockMIDI1ProxyKind) getBits<2, 2> (flags); }
|
||||
/** The actual directionality of the block. */
|
||||
BlockDirection getDirection() const { return (BlockDirection) getBits<0, 2> (flags); }
|
||||
/** The zero-based index of the first group in the block. */
|
||||
uint8_t getFirstGroup() const { return firstGroup; }
|
||||
/** The number of groups contained in the block, must be one or greater. */
|
||||
uint8_t getNumGroups() const { return numGroups; }
|
||||
/** The CI version supported by this block. Implies a bidirectional block. */
|
||||
uint8_t getCiVersion() const { return ciVersion; }
|
||||
/** The number of simultaneous SysEx8 streams supported on this block. */
|
||||
uint8_t getMaxSysex8Streams() const { return numSysex8Streams; }
|
||||
|
||||
bool operator== (const BlockInfo& other) const
|
||||
{
|
||||
const auto tie = [] (auto& x)
|
||||
{
|
||||
return std::tuple (x.enabled, x.flags, x.firstGroup, x.numGroups, x.ciVersion, x.numSysex8Streams);
|
||||
};
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const BlockInfo& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
private:
|
||||
template <auto position, auto numBits, typename Value>
|
||||
static Value replaceBits (Value value, Value replacement)
|
||||
{
|
||||
constexpr auto mask = ((Value) 1 << numBits) - 1;
|
||||
const auto maskedValue = value & ~(mask << position);
|
||||
return (Value) (maskedValue | (replacement << position));
|
||||
}
|
||||
|
||||
template <auto position, auto numBits, typename Value>
|
||||
static Value getBits (Value value)
|
||||
{
|
||||
constexpr auto mask = ((Value) 1 << numBits) - 1;
|
||||
return (value >> position) & mask;
|
||||
}
|
||||
|
||||
uint8_t enabled{};
|
||||
uint8_t flags{};
|
||||
uint8_t firstGroup{};
|
||||
uint8_t numGroups{};
|
||||
uint8_t ciVersion{};
|
||||
uint8_t numSysex8Streams{};
|
||||
};
|
||||
|
||||
/**
|
||||
This struct holds functions that can be used to create different kinds
|
||||
of Universal MIDI Packet.
|
||||
|
|
@ -44,6 +254,36 @@ namespace juce::universal_midi_packets
|
|||
*/
|
||||
struct Factory
|
||||
{
|
||||
template <typename Callback>
|
||||
static void splitIntoPackets (Span<const std::byte> bytes, size_t bytesPerPacket, Callback&& callback)
|
||||
{
|
||||
const auto numPackets = (bytes.size() / bytesPerPacket) + ((bytes.size() % bytesPerPacket) != 0);
|
||||
auto* dataOffset = bytes.data();
|
||||
|
||||
if (numPackets <= 1)
|
||||
{
|
||||
callback (SysEx7::Kind::complete, bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = static_cast<ssize_t> (bytes.size()); i > 0; i -= (ssize_t) bytesPerPacket, dataOffset += bytesPerPacket)
|
||||
{
|
||||
const auto kind = [&]
|
||||
{
|
||||
if (i == (ssize_t) bytes.size())
|
||||
return SysEx7::Kind::begin;
|
||||
|
||||
if (i <= (ssize_t) bytesPerPacket)
|
||||
return SysEx7::Kind::end;
|
||||
|
||||
return SysEx7::Kind::continuation;
|
||||
}();
|
||||
|
||||
const auto bytesNow = std::min ((ssize_t) bytesPerPacket, i);
|
||||
callback (kind, Span (dataOffset, (size_t) bytesNow));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
struct Detail
|
||||
{
|
||||
|
|
@ -90,6 +330,41 @@ struct Factory
|
|||
|
||||
return PacketX4 { words };
|
||||
}
|
||||
|
||||
static PacketX4 makePacketX4 (Span<const std::byte> header,
|
||||
Span<const std::byte> data)
|
||||
{
|
||||
jassert (data.size() <= 14);
|
||||
|
||||
std::array<std::byte, 16> bytes{{}};
|
||||
std::copy (header.begin(), header.end(), bytes.begin());
|
||||
std::copy (data.begin(), data.end(), std::next (bytes.begin(), (ptrdiff_t) header.size()));
|
||||
|
||||
std::array<uint32_t, 4> words{};
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for (auto& word : words)
|
||||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++);
|
||||
|
||||
return PacketX4 { words };
|
||||
}
|
||||
|
||||
static PacketX4 makeStreamSubpacket (std::byte status,
|
||||
SysEx7::Kind kind,
|
||||
Span<const std::byte> data)
|
||||
{
|
||||
jassert (data.size() <= 14);
|
||||
const std::byte header[] { std::byte (0xf0) | std::byte ((uint8_t) kind << 2), status, };
|
||||
return makePacketX4 (header, data);
|
||||
}
|
||||
|
||||
static PacketX4 makeStreamConfiguration (StreamConfiguration options)
|
||||
{
|
||||
return Detail::makeStream().withU8<0x2> (options.getProtocol() == PacketProtocol::MIDI_2_0 ? 0x2 : 0x1)
|
||||
.withU8<0x3> ((options.getReceiveTimestamp() ? 0x2 : 0x0) | (options.getTransmitTimestamp() ? 0x1 : 0x0));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static PacketX1 makeNoop (uint8_t group)
|
||||
|
|
@ -520,6 +795,23 @@ struct Factory
|
|||
.withU8<7> ((uint8_t) filterBitmap);
|
||||
}
|
||||
|
||||
static PacketX4 makeEndpointInfoNotification (const EndpointInfo& info)
|
||||
{
|
||||
return Detail::makeStream().withU8<1> (1)
|
||||
.withU8<2> (info.getVersionMajor())
|
||||
.withU8<3> (info.getVersionMinor())
|
||||
.withU8<4> (info.getNumFunctionBlocks() | (info.hasStaticFunctionBlocks() ? 0x80 : 0x00))
|
||||
.withU8<6> ((info.hasMidi1Support() ? 0x1 : 0x0) | (info.hasMidi2Support() ? 0x2 : 0x0))
|
||||
.withU8<7> ((info.hasTransmitJRSupport() ? 0x1 : 0x0) | (info.hasReceiveJRSupport() ? 0x2 : 0x0));
|
||||
}
|
||||
|
||||
static PacketX4 makeFunctionBlockDiscovery (uint8_t block, std::byte filterBitmap)
|
||||
{
|
||||
return Detail::makeStream().withU8<1> (0x10)
|
||||
.withU8<2> (block)
|
||||
.withU8<3> ((uint8_t) filterBitmap);
|
||||
}
|
||||
|
||||
static PacketX4 makeDeviceIdentityNotification (DeviceInfo info)
|
||||
{
|
||||
return Detail::makeStream().withU8<0x1> (2)
|
||||
|
|
@ -535,6 +827,87 @@ struct Factory
|
|||
.withU8<0xe> ((uint8_t) info.revision[2])
|
||||
.withU8<0xf> ((uint8_t) info.revision[3]);
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static bool makeEndpointNameNotification (const String& bytes, Fn&& fn)
|
||||
{
|
||||
constexpr auto maxSize = 98;
|
||||
|
||||
if (maxSize <= bytes.getNumBytesAsUTF8())
|
||||
return false;
|
||||
|
||||
const Span byteSpan { reinterpret_cast<const std::byte*> (bytes.toRawUTF8()), bytes.getNumBytesAsUTF8() };
|
||||
|
||||
splitIntoPackets (byteSpan, 14, [&] (SysEx7::Kind kind, Span<const std::byte> bytesThisTime)
|
||||
{
|
||||
const auto packet = Detail::makeStreamSubpacket (std::byte (3), kind, bytesThisTime);
|
||||
fn (View (packet.data()));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static bool makeProductInstanceIdNotification (const String& bytes, Fn&& fn)
|
||||
{
|
||||
constexpr auto maxSize = 42;
|
||||
|
||||
if (maxSize < bytes.getNumBytesAsUTF8())
|
||||
return false;
|
||||
|
||||
const Span byteSpan { reinterpret_cast<const std::byte*> (bytes.toRawUTF8()), bytes.getNumBytesAsUTF8() };
|
||||
|
||||
splitIntoPackets (byteSpan, 14, [&] (SysEx7::Kind kind, Span<const std::byte> bytesThisTime)
|
||||
{
|
||||
const auto packet = Detail::makeStreamSubpacket (std::byte (4), kind, bytesThisTime);
|
||||
fn (View (packet.data()));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Fn>
|
||||
static bool makeFunctionBlockNameNotification (uint8_t index, const String& bytes, Fn&& fn)
|
||||
{
|
||||
constexpr auto maxSize = 91;
|
||||
|
||||
if (maxSize < bytes.getNumBytesAsUTF8())
|
||||
return false;
|
||||
|
||||
const Span byteSpan { reinterpret_cast<const std::byte*> (bytes.toRawUTF8()), bytes.getNumBytesAsUTF8() };
|
||||
|
||||
splitIntoPackets (byteSpan, 13, [&] (SysEx7::Kind kind, Span<const std::byte> bytesThisTime)
|
||||
{
|
||||
const std::byte header[] { std::byte (0xf0) | std::byte ((uint8_t) kind << 2), std::byte (0x12), std::byte (index) };
|
||||
fn (View (Detail::makePacketX4 (header, bytesThisTime).data()));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static PacketX4 makeFunctionBlockInfoNotification (uint8_t index, const BlockInfo& info)
|
||||
{
|
||||
const auto flags = ((uint8_t) info.getDirection() << 0)
|
||||
| ((uint8_t) info.getMIDI1ProxyKind() << 2)
|
||||
| ((uint8_t) info.getUiHint() << 4);
|
||||
return Detail::makeStream().withU8<0x1> (0x11)
|
||||
.withU8<0x2> ((uint8_t) (index | (info.isEnabled() << 7)))
|
||||
.withU8<0x3> ((uint8_t) flags)
|
||||
.withU8<0x4> (info.getFirstGroup())
|
||||
.withU8<0x5> (info.getNumGroups())
|
||||
.withU8<0x6> (info.getCiVersion())
|
||||
.withU8<0x7> (info.getMaxSysex8Streams());
|
||||
}
|
||||
|
||||
static PacketX4 makeStreamConfigurationRequest (StreamConfiguration options)
|
||||
{
|
||||
return Detail::makeStreamConfiguration (options).withU8<0x1> (5);
|
||||
}
|
||||
|
||||
static PacketX4 makeStreamConfigurationNotification (StreamConfiguration options)
|
||||
{
|
||||
return Detail::makeStreamConfiguration (options).withU8<0x1> (6);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::universal_midi_packets
|
||||
|
|
|
|||
|
|
@ -36,15 +36,25 @@
|
|||
namespace juce::universal_midi_packets
|
||||
{
|
||||
|
||||
/** The kinds of MIDI protocol that can be formatted into Universal MIDI Packets. */
|
||||
enum class PacketProtocol
|
||||
/** Kinds of MIDI message transport.
|
||||
*/
|
||||
enum class Transport : uint8_t
|
||||
{
|
||||
bytestream, ///< A stream of variable-length messages. Suitable for MIDI 1.0.
|
||||
ump, ///< A stream of 32-bit words. Suitable for MIDI-1UP and MIDI 2.0.
|
||||
};
|
||||
|
||||
/** The kinds of MIDI protocol that can be formatted into Universal MIDI Packets.
|
||||
*/
|
||||
enum class PacketProtocol : uint8_t
|
||||
{
|
||||
MIDI_1_0,
|
||||
MIDI_2_0,
|
||||
};
|
||||
|
||||
/** All kinds of MIDI protocol understood by JUCE. */
|
||||
enum class MidiProtocol
|
||||
/** All kinds of MIDI protocol understood by JUCE.
|
||||
*/
|
||||
enum class MidiProtocol : uint8_t
|
||||
{
|
||||
bytestream,
|
||||
UMP_MIDI_1_0,
|
||||
|
|
|
|||
|
|
@ -128,10 +128,20 @@ struct Utils
|
|||
stream = 0xf,
|
||||
};
|
||||
|
||||
static constexpr bool hasGroup (MessageKind k)
|
||||
{
|
||||
return ! isGroupless (k);
|
||||
}
|
||||
|
||||
static constexpr bool isGroupless (MessageKind k)
|
||||
{
|
||||
return k == MessageKind::utility || k == MessageKind::stream;
|
||||
}
|
||||
|
||||
static constexpr MessageKind getMessageType (uint32_t w) noexcept { return MessageKind { U4<0>::get (w) }; }
|
||||
static constexpr uint8_t getGroup (uint32_t w) noexcept { return U4<1>::get (w); }
|
||||
static constexpr std::byte getStatus (uint32_t w) noexcept { return std::byte { U4<2>::get (w) }; }
|
||||
static constexpr uint8_t getChannel (uint32_t w) noexcept { return U4<3>::get (w); }
|
||||
static constexpr uint8_t getGroup (uint32_t w) noexcept { return U4<1>::get (w); }
|
||||
};
|
||||
|
||||
} // namespace juce::universal_midi_packets
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public:
|
|||
*/
|
||||
const uint32_t* data() const noexcept { return ptr; }
|
||||
|
||||
/** Get the number of 32-words (between 1 and 4 inclusive) in the Universal
|
||||
/** Get the number of 32 bit words (between 1 and 4 inclusive) in the Universal
|
||||
MIDI Packet currently pointed-to by this view.
|
||||
*/
|
||||
uint32_t size() const noexcept;
|
||||
|
|
|
|||
|
|
@ -169,6 +169,16 @@ public:
|
|||
return std::get<index> (contents);
|
||||
}
|
||||
|
||||
bool operator== (const Packet& other) const
|
||||
{
|
||||
return contents == other.contents;
|
||||
}
|
||||
|
||||
bool operator!= (const Packet& other) const
|
||||
{
|
||||
return contents != other.contents;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
using Contents = std::array<uint32_t, numWords>;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue